1

I have some code, implemented by a shortcode within a custom plugin. The code generates a CSV file from some data tables. When I run the shortcode within the admin section, it works perfectly. BUT when I attempt to run it on the front end, it fills the top of the CSV file with the pages HTML data such as:

<?DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
.......

#  ID    Product      Price
1   2a   keys        10.00
2   2b   doors       55.00

The table of data does export, as it did in the admin section of the plugin. Just when the shortcode to the function is placed on the front end, the results is HTML code at the top of the file (as seen above). Can not for the life of me figure out how to stop this. The goal is to be able to have the user output, in CSV format, all their purchases. Here is the code:


function ShowResults($Name)
{
$rows = mmd_GetProducts($Name);

ob_start();
ob_end_flush();
header('Pragma: public');
header( 'Expires: 0' );
header( 'Cache-Control: private', false );
header( 'Content-Type: text/csv');
header( 'Content-Disposition: attachment; filename="'. 'Results.csv"');
$fp = fopen('php://output', 'w');

$header_row = array(
        0 => '#',
        1 => 'ID',
        2 => 'Product',
        3 => 'Price',
    );
fputcsv($fp, $header_row);

foreach($rows as $Record)
  { 
     if($Record['bDoNotDisplay'] == 1 )
        continue;
  
  $OutputRecord = array($cnt
                        $Record['id'], 
                        $Record['product'],  
                        $Record['price']);
    $cnt ++;
    fputcsv($fp, $OutputRecord);         
    }
unset($rows);

fclose( $fp );
ob_end_clean(); 
exit();
}

UPDATE BUT STILL A PROBLEM

Similar issue, except now the HTML code is at the bottom of the file. I changed the code to use add_action instead of calling a short code. I use this wordpress action call and then detect if this is the page that is loading

add_action( 'template_redirect', 'ShowResults', 10); 
function ShowResults()
{
 global $post;
 
$title = get_the_title($post); 

if($title == "Export")
   {
   OutputCSV();
   }
 }


function OutputCSV()
{
$rows = mmd_GetProducts($Name);

ob_start();
ob_end_flush();
header('Pragma: public');
header( 'Expires: 0' );
header( 'Cache-Control: private', false );
header( 'Content-Type: text/csv');
header( 'Content-Disposition: attachment; filename="'. 'Results.csv"');
$fp = fopen('php://output', 'w');

$header_row = array(
        0 => '#',
        1 => 'ID',
        2 => 'Product',
        3 => 'Price',
    );
fputcsv($fp, $header_row);

foreach($rows as $Record)
  { 
     if($Record['bDoNotDisplay'] == 1 )
        continue;
  
  $OutputRecord = array($cnt
                        $Record['id'], 
                        $Record['product'],  
                        $Record['price']);
    $cnt ++;
    fputcsv($fp, $OutputRecord);         
    }
unset($rows);

fclose( $fp );
ob_end_clean(); 
exit();

}

Now the HTML code appears at the bottom of the file and not the top. Any suggestions?

6
  • Note that the shortcode's output must be returned by the callback and the page headers are already sent when the shortcode is rendered in the post content.
    – birgire
    Mar 2 at 9:01
  • birgire...what would you suggest? Mar 2 at 20:24
  • 1
    to output the download link/button you can use the shortcode but for the actual csv download I think you would need to halt the flow before the theme's output, e.g. earlier or same as the template_redirect hook
    – birgire
    Mar 3 at 15:48
  • Ironic...that is EXACTLY what I started working with late yesterday. Yes it works! Howeer, now I am getting the HTML after the table output and the template_redirect is called twice. Suggestions? Mar 3 at 15:53
  • hard to say what's going on there :-) but in general the shortcode callback must return the download link, not echo it. The download code itself must be outside of the shortcode and it must use some logic and exit within some early hook. best of luck.
    – birgire
    Mar 3 at 15:58
0

Well this was a journey! The solution was 'die' and not exit AND use the add_action call that resulted in a notification PRIOR to any header call being called. The die forced the system to quit before attempting to write out the headers at the end of file.

The final code that works:

add_action( 'template_redirect', 'ShowResults', 10); 
function ShowResults($Name)
{
 global $post;
 $title = get_the_title($post);

if($title == "Export")
   OutputCSV();
}

function ExportCSV()
{
$rows = mmd_GetProducts($Name);

header('Pragma: public');
header( 'Expires: 0' );
header( 'Cache-Control: private', false );
header( 'Content-Type: text/csv');
header( 'Content-Disposition: attachment; filename="'. 'Results.csv"');
$fp = fopen('php://output', 'w');

$header_row = array(
        0 => '#',
        1 => 'ID',
        2 => 'Product',
        3 => 'Price',
    );
fputcsv($fp, $header_row);

foreach($rows as $Record)
  { 
     if($Record['bDoNotDisplay'] == 1 )
        continue;
  
  $OutputRecord = array($cnt
                        $Record['id'], 
                        $Record['product'],  
                        $Record['price']);
    $cnt ++;
    fputcsv($fp, $OutputRecord);         
    }
unset($rows);

fclose( $fp );
die;                  <<<=========== die NOT exit.
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.