I want to rewrite the code below to generate a string. This string I want to attach to an eamil. It should appear as a csv file.
$fh = fopen($file,'w');
function __outputCSV(&$vals, $key, $filehandler) {
fputcsv($filehandler, $vals, ';', '"');
}
array_walk($aData, '__outputCSV', $fh);
fclose($fh);
Any idea's?
The obvious and lazy approach would be not to rewrite it, but to simply create a temp file and call file_get_contents() on it afterwards.
This, however, would be boring, so I'm going to suggest what is arguably an over complicated approach, but it should work nicely and will be done entirely in memory:
Use this class that is provided in the PHP manual as an example of how to write a custom protocol handler, and the following example code:
function __outputCSV(&$vals, $key, $filehandler) {
fputcsv($filehandler, $vals, ';', '"');
}
stream_wrapper_register("var", "VariableStream");
$myCSVdata = "";
$fh = fopen('var://myCSVdata', 'w');
array_walk($aData, '__outputCSV', $fh);
fclose($fh);
echo $myCSVdata; // Ta Da!
Your best bet is to get a MIME compatible email class that can send emails with attachments. I recall using RMail or PHPMimeMail a while back, you'll have to google it.
Else, you'll need to learn how to build MIME mail yourself. But for all the trouble that it's worth i'll really recommend you to get a class that already does that for you.
EDIT:
I didn't understand your question at first, maybe you should completly reformat and change your title. Here is my suggestion:
$data = '';
foreach($aData as $aDataLine) {
foreach($aDataLine as $aDataKey => $aDataField) {
$aDataLine[$aDataKey] = '"'.str_replace('"', '\\"', $aDataField).'"';
}
$data .= implode(',', $aDataLine)."\n";
}
//$data contains your CSV
I dont think i forgot anything, it SHOULD work out of the box...
Related
I am trying to write content from a form to a text file, and don't know why it is not working. I have uploaded the code on a free host like 000webhost and everything works fine. So not sure if there is some misconfiguration to my CentOS server which is CentOS release 6.5 (Final). Or if there is something wrong with the code, any help would be much appreciated.
<?php
header("Location: http://www.example.com");
$handle = fopen("data.txt", "a");
foreach($_POST as $variable => $value)
{
fwrite($handle, $variable);
fwrite($handle, "=");
fwrite($handle, $value);
fwrite($handle, "\r\n");
}
fwrite($handle, "\r\n");
fclose($handle);
exit;
?>
Make sure you have the correct permissions to the folder you're trying to write to. It's probably getting by the apache user (www-data) or equiv.
Also, PHP has some nicer methods of writing to files. Try something like:
$output = '';
foreach($_POST as $key => $val)
$output .= sprintf("%s = %s\n", $key, $val);
file_put_contents('data.txt', $output);
That should be clear as long as $_POST isn't 2D. If it's 2D, or more for debugging purposes, why not use print_r() (as it's recursive), eg.
file_put_contents('data.txt', print_r($_POST, true));
The second argument makes it return a string rather than actually print.
For clarity, I'd consider putting the header('Location: xxx') call at the end (even though it won't make a functional difference).
I seem to be in a catch-22 with a small app I'm developing in PHP on Google App Engine using Quercus;
I have a remote csv-file which I can download & store in a string
To parse that string I'd ideally use str_getcsv, but Quercus doesn't have that function yet
Quercus does seem to know fgetcsv, but that function expects a file handle which I don't have (and I can't make a new one as GAE doesn't allow files to be created)
Anyone got an idea of how to solve this without having to dismiss the built-in PHP csv-parser functions and write my own parser instead?
I think the simplest solution really is to write your own parser . it's a piece of cake anyway and will get you to learn more regex- it makes no sense that there is no csv string to array parser in PHP so it's totally justified to write your own. Just make sure it's not too slow ;)
You might be able to create a new stream wrapper using stream_wrapper_register.
Here's an example from the manual which reads global variables: http://www.php.net/manual/en/stream.streamwrapper.example-1.php
You could then use it like a normal file handle:
$csvStr = '...';
$fp = fopen('var://csvStr', 'r+');
while ($row = fgetcsv($fp)) {
// ...
}
fclose($fp);
this shows a simple manual parser i wrote with example input with qualifed, non-qualified, escape feature. it can be used for the header and data rows and included an assoc array function to make your data into a kvp style array.
//example data
$fields = strparser('"first","second","third","fourth","fifth","sixth","seventh"');
print_r(makeAssocArray($fields, strparser('"asdf","bla\"1","bl,ah2","bl,ah\"3",123,34.234,"k;jsdfj ;alsjf;"')));
//do something like this
$fields = strparser(<csvfirstline>);
foreach ($lines as $line)
$data = makeAssocArray($fields, strparser($line));
function strparser($string, $div = ",", $qual = "\"", $esc = "\\") {
$buff = "";
$data = array();
$isQual = false; //the result will be a qualifier
$inQual = false; //currently parseing inside qualifier
//itereate through string each byte
for ($i = 0; $i < strlen($string); $i++) {
switch ($string[$i]) {
case $esc:
//add next byte to buffer and skip it
$buff .= $string[$i+1];
$i++;
break;
case $qual:
//see if this is escaped qualifier
if (!$inQual) {
$isQual = true;
$inQual = true;
break;
} else {
$inQual = false; //done parseing qualifier
break;
}
case $div:
if (!$inQual) {
$data[] = $buff; //add value to data
$buff = ""; //reset buffer
break;
}
default:
$buff .= $string[$i];
}
}
//get last item as it doesnt have a divider
$data[] = $buff;
return $data;
}
function makeAssocArray($fields, $data) {
foreach ($fields as $key => $field)
$array[$field] = $data[$key];
return $array;
}
if it can be dirty and quick. I would just use the
http://php.net/manual/en/function.exec.php
to pass it in and use sed and awk (http://shop.oreilly.com/product/9781565922259.do) to parse it. I know you wanted to use the php parser. I've tried before and failed simply because its not vocal about its errors.
Hope this helps.
Good luck.
You might be able to use fopen with php://temp or php://memory (php.net) to get it to work. What you would do is open either php://temp or php://memory, write to it, then rewind it (php.net), and then pass it to fgetcsv. I didn't test this, but it might work.
I need a way to make the fputscv function write data to the browser on-the-fly instead of creating a temporary file, saving data into that file and doing a echo file_get_contents().
Found this on the PHP docs website, first comment under the function reference:
function outputCSV($data) {
$outstream = fopen("php://output", 'w');
function __outputCSV(&$vals, $key, $filehandler) {
fputcsv($filehandler, $vals, ';', '"');
}
array_walk($data, '__outputCSV', $outstream);
fclose($outstream);
}
And a second option:
$csv = fopen('php://temp/maxmemory:'. (5*1024*1024), 'r+');
fputcsv($csv, array('blah','blah'));
rewind($csv);
// put it all in a variable
$output = stream_get_contents($csv);
Hope this helps!
BTW the PHP docs should always be your first stop when trying to figure things out. :-)
By a comment on the PHP site
<?php
$out = fopen('php://output', 'w');
fputcsv($out, array('this','is some', 'csv "stuff", you know.'));
fclose($out);
?>
As the original asker wanted to "write to the browser on the fly", maybe is worth noting (as was my case and noone mentioned it) that if you want to force a file name and a dialog asking to download a file in the browser, you must set the proper headers before outputting anything with fputcsv:
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=myFile.csv');
Producing a CSV is actually not all that difficult (parsing a CSV is a little bit more involved).
Sample code for writing a 2D Array as CSV:
$array = [
[1,2,3],
[4,5,6],
[7,8,9]
];
// If this CSV is a HTTP response you will need to set the right content type
header("Content-Type: text/csv");
// If you need to force download or set a filename (you can also do this with
// the download attribute in HTML5 instead)
header('Content-Disposition: attachment; filename="example.csv"')
// Column heading row, if required.
echo "Column heading 1,Column heading 2,Column heading 3\n";
foreach ($array as $row) {
$row = array_map(function($cell) {
// Cells containing a quote, a comma or a new line will need to be
// contained in double quotes.
if (preg_match('/["\n,]/', $cell)) {
// double quotes within cells need to be escaped.
return '"' . preg_replace('/"/', '""', $cell) . '"';
}
return $cell;
}, $row);
echo implode(',', $row) . "\n";
}
I'm using fputcsv in PHP to output a comma-delimited file of a database query. When opening the file in gedit in Ubuntu, it looks correct - each record has a line break (no visible line break characters, but you can tell each record is separated,and opening it in OpenOffice spreadsheet allows me to view the file correctly.)
However, we're sending these files on to a client on Windows, and on their systems, the file comes in as one big, long line. Opening it in Excel, it doesn't recognize multiple lines at all.
I've read several questions on here that are pretty similar, including this one, which includes a link to the really informative Great Newline Schism explanation.
Unfortunately, we can't just tell our clients to open the files in a "smarter" editor. They need to be able to open them in Excel. Is there any programmatic way to ensure that the correct newline characters are added so the file can be opened in a spreadsheet program on any OS?
I'm already using a custom function to force quotes around all values, since fputcsv is selective about it. I've tried doing something like this:
function my_fputcsv($handle, $fieldsarray, $delimiter = "~", $enclosure ='"'){
$glue = $enclosure . $delimiter . $enclosure;
return fwrite($handle, $enclosure . implode($glue,$fieldsarray) . $enclosure."\r\n");
}
But when the file is opened in a Windows text editor, it still shows up as a single long line.
// Writes an array to an open CSV file with a custom end of line.
//
// $fp: a seekable file pointer. Most file pointers are seekable,
// but some are not. example: fopen('php://output', 'w') is not seekable.
// $eol: probably one of "\r\n", "\n", or for super old macs: "\r"
function fputcsv_eol($fp, $array, $eol) {
fputcsv($fp, $array);
if("\n" != $eol && 0 === fseek($fp, -1, SEEK_CUR)) {
fwrite($fp, $eol);
}
}
This is an improved version of #John Douthat's great answer, preserving the possibility of using custom delimiters and enclosures and returning fputcsv's original output:
function fputcsv_eol($handle, $array, $delimiter = ',', $enclosure = '"', $eol = "\n") {
$return = fputcsv($handle, $array, $delimiter, $enclosure);
if($return !== FALSE && "\n" != $eol && 0 === fseek($handle, -1, SEEK_CUR)) {
fwrite($handle, $eol);
}
return $return;
}
Using the php function fputcsv writes only \n and cannot be customized. This makes the function worthless for microsoft environment although some packages will detect the linux newline also.
Still the benefits of fputcsv kept me digging into a solution to replace the newline character just before sending to the file. This can be done by streaming the fputcsv to the build in php temp stream first. Then adapt the newline character(s) to whatever you want and then save to file. Like this:
function getcsvline($list, $seperator, $enclosure, $newline = "" ){
$fp = fopen('php://temp', 'r+');
fputcsv($fp, $list, $seperator, $enclosure );
rewind($fp);
$line = fgets($fp);
if( $newline and $newline != "\n" ) {
if( $line[strlen($line)-2] != "\r" and $line[strlen($line)-1] == "\n") {
$line = substr_replace($line,"",-1) . $newline;
} else {
// return the line as is (literal string)
//die( 'original csv line is already \r\n style' );
}
}
return $line;
}
/* to call the function with the array $row and save to file with filehandle $fp */
$line = getcsvline( $row, ",", "\"", "\r\n" );
fwrite( $fp, $line);
As webbiedave pointed out (thx!) probably the cleanest way is to use a stream filter.
It is a bit more complex than other solutions, but even works on streams that are not editable after writing to them (like a download using $handle = fopen('php://output', 'w'); )
Here is my approach:
class StreamFilterNewlines extends php_user_filter {
function filter($in, $out, &$consumed, $closing) {
while ( $bucket = stream_bucket_make_writeable($in) ) {
$bucket->data = preg_replace('/([^\r])\n/', "$1\r\n", $bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register("newlines", "StreamFilterNewlines");
stream_filter_append($handle, "newlines");
fputcsv($handle, $list, $seperator, $enclosure);
...
alternatively, you can output in native unix format (\n only) then run unix2dos on the resulting file to convert to \r\n in the appropriate places. Just be careful that your data contains no \n's . Also, I see you are using a default separator of ~ . try a default separator of \t .
I've been dealing with a similiar situation. Here's a solution I've found that outputs CSV files with windows friendly line-endings.
http://www.php.net/manual/en/function.fputcsv.php#90883
I wasn't able to use the since I'm trying to stream a file to the client and can't use the fseeks.
windows needs \r\n as the linebreak/carriage return combo in order to show separate lines.
I did eventually get an answer over at experts-exchange; here's what worked:
function my_fputcsv($handle, $fieldsarray, $delimiter = "~", $enclosure ='"'){
$glue = $enclosure . $delimiter . $enclosure;
return fwrite($handle, $enclosure . implode($glue,$fieldsarray) . $enclosure.PHP_EOL);
}
to be used in place of standard fputcsv.
I am writing an email module for my web app that sends a html email to a user on completion of a task such as signing up. Now as the formatting of this email may change I've decided to have a template html page that is the email, with custom tags in it that need to be replaced such as %fullname%.
My function has an array in the format of array(%fullname% => 'Joe Bloggs'); with the key as the tag identifier and the value of what needs to replace it.
I've tried the following:
$fp = #fopen('email.html', 'r');
if($fp)
{
while(!feof($fp)){
$line = fgets($fp);
foreach($data as $value){
echo $value;
$repstr = str_replace(key($data), $value, $line);
}
$content .= $repstr;
}
fclose($fp);
}
Is this the best way to do this? as only 1 tag get replaced at the moment... am I on the right path or miles off??
thanks...
I think the problem is in your foreach. This should fix it:
foreach($data as $key => $value){
$repstr = str_replace($key, $value, $line);
}
Alternatively, I think this should be more effective:
$file = #file_get_contents("email.html");
if($file) {
$file = str_replace(array_keys($data), array_values($data), $file);
print $file;
}
//read the entire string
$str=implode("\n",file('somefile.txt'));
$fp=fopen('somefile.txt','w');
//replace something in the file string - this is a VERY simple example
$str=str_replace('Yankees','Cardinals',$str);
//now, TOTALLY rewrite the file
fwrite($fp,$str,strlen($str));
That looks like it should work, but I'd use "file_get_contents()" and do it in one big blast.
A slightly different approach is to use PHP's heredocs combined with string interpolation i.e.:
$email = <<<EOD
<HTML><BODY>
Hi $fullname,
You have just signed up.
</BODY></HTML>
EOD;
This avoids a separate file, and should make things beyond simple substitution easier later.