3
\$\begingroup\$

I have some custom tables in a Wordpress database that I need to export to CSV. I created a dashboard widget to help with this based on some assistance given to me on on one of my SO questions. It basically consists of a single button that posts back and does some output buffering to render the CSV.

I've had it working correctly for a few weeks now, but when I went to push it live on our launch date, I ran into some odd behavior. The gist is that when I export locally on my dev box (Windows 7/Xampp) or on my personal hosted site (Linux) everything exports fine, but when I export on the client's server (Linux) the data is exported, but the CSV also contains the markup for the rest of the wp-admin dashboard page that follows after the widget is rendered. For example:

joe, blow
john, smith
<div><strong>Last entry: </strong>Hank Jones</div>
<div><strong>Total submissions: </strong> 7</div>
.. remaining markup removed for brevity

You can see that the CSV contains the <div><strong>Last entry: markup (and a whole lot more). I have no idea why this would be different based on different servers, but I wanted to post my plugin code in hopes that someone can see what's going on (and tips for improvement if applicable).

<?php
/*
Plugin Name: My Plugin
Description: Exporting Stuff
Author: tjans
Version: 1.0
Author URI: http://example.com
 */
add_action('init', 'myplugin_export_buffer_start');
add_action('in_admin_footer', 'myplugin_export_buffer_end');

function myplugin_export_ob_callback($buffer) {
    return $buffer;
}

function myplugin_export_buffer_start() 
{ 
    ob_start("myplugin_export_ob_callback"); 
}

function myplugin_export_buffer_end() 
{ 
    ob_end_flush(); 
}

function myplugin_display_mydata_export_widget_process()
{
    $errors = array();

    if ( 'POST' == $_SERVER['REQUEST_METHOD'] && isset ( $_POST['myplugin_export_button'] ))
    {
        ob_clean(); // erase the output buffer...blow away all the code up to this point

        global $wpdb;
        $tablePrefix = $wpdb->prefix;

        $qry = "QUERY TO PULL EXPORT DATA";

        $result = $wpdb->get_results($qry, ARRAY_A);
        if ($wpdb->num_rows)
        {
            $date = new DateTime();
            $ts = $date->format("Y-m-d-G-i-s");
            $filename = "mydata-export-$ts.csv";
            header( 'Content-Type: text/csv' );
            header( 'Content-Disposition: attachment;filename='.$filename);

            $fp = fopen('php://output', 'w');

            // show a header
            $headRow = $result[0];
            fputcsv($fp, array_keys($headRow));

            foreach ($result as $data) 
            {
                foreach($data as $field=>$value) 
                {
                    $data[$field] = stripslashes($value);
                }
                fputcsv($fp, $data);
            }
            fclose($fp);

            $contLength = ob_get_length();
            header( 'Content-Length: '.$contLength);
        }
    }

    return myplugin_display_mydata_export_form_widget();
}   

function myplugin_display_mydata_export_form_widget()
{
    global $wpdb;
    $tableName = $wpdb->prefix . "myplugin_mydata";

    $submissionCount = $wpdb->get_var("select count(Id) from $tableName");
    $lastUser = $wpdb->get_row("QUERY TO RETRIEVE LAST RECORD", ARRAY_A);
?>
    <?php if($lastUser) { ?><div><strong>Last entry: </strong><?php echo $lastUser['FirstName'] ?> <?php echo $lastUser['LastName'] ?> (<?php echo date('F jS, Y - g:i a', $lastUser['DateStamp']) ?>)</div><?php } ?>
    <div><strong>Total submissions: </strong> <?php echo $submissionCount ?></div>

    <form id="myplugin_mydata_export_widget" method="post" action="">
        <input type="submit" name="myplugin_export_button" value="Export All" />
    </form>
<?php
}

function myplugin_add_mydata_export_dashboard_widgets()
{
    // widget_id, widget_name, callback, control_callback
    wp_add_dashboard_widget(
        'myplugin-mydata-export-form-widget', 
        'Form Submissions', 
        'myplugin_display_mydata_export_widget_process'
    );  
}

add_action('wp_dashboard_setup', 'myplugin_add_mydata_export_dashboard_widgets' );
\$\endgroup\$
5
  • \$\begingroup\$ I suspect is has something to do with the content-length header being ignored... \$\endgroup\$
    – tjans
    Commented Jun 24, 2013 at 18:46
  • \$\begingroup\$ I think I fixed it by adding "exit();" to the end of the myplugin_display_mydata_export_widget_process function \$\endgroup\$
    – tjans
    Commented Jun 24, 2013 at 18:50
  • \$\begingroup\$ Seems you fixed it already, i'v also added the exit in mu SO answer, that was a mistake. All the best. \$\endgroup\$
    – The Alpha
    Commented Jun 24, 2013 at 19:01
  • \$\begingroup\$ Yes I did, thank you. Though I'm really curious to know why the "exit" wasn't needed locally or on my personal live web server but not on my client's...some kind of buffering setting or something on their server that wasn't playing nice? \$\endgroup\$
    – tjans
    Commented Jun 25, 2013 at 12:28
  • \$\begingroup\$ Same question has arose on my mind too, because even in my local environment it's working without exit, I'll let you know if can find it out. \$\endgroup\$
    – The Alpha
    Commented Jun 25, 2013 at 18:35

1 Answer 1

2
\$\begingroup\$

Normally, Output Buffer functions are not needed in a WordPress context, here neither. We'll just hook on admin_init to intercept our $_POST action, output the export file and exit. The browser doesn't even reloads the page.

The form needs a security field, with wp_nonce_field(), and a hidden one with our control action <input type="hidden" name="my_action" value="my_export" />.

The export code is based on a Pippin Plugins tutorial. He uses two functions I didn't know, ignore_user_abort() (PHP) and nocache_headers() (WP).

You're using myplugin as prefix, but it's better to personalize that, like tjs_ for example. But here I'll use a suffix, _CODEREVIEW-SE_QUESTION-ID (_crse_27734). Anyway, it's better to go OOP and drop this pre-suf-fixing.

I prefer to declare all actions first and then write the functions. I've stripped your data and replaced with simple/dummy info for replication purposes.

<?php
/**
 * Plugin Name: (CR) Export DB as CSV
 * Description: Exporting a database Query as CSV
 * Plugin URI: http://codereview.stackexchange.com/a/52051/14985
 * Author: brasofilo
 * Version: 1.0
 */

add_action( 'wp_dashboard_setup', 'add_widgets_crse_27734' );
add_action( 'admin_init', 'csv_export_crse_27734' );


/**
 * Create Dashboard Widget
 *
 * @return void
 **/
function add_widgets_crse_27734()
{
    wp_add_dashboard_widget(
        'export-form-widget', 
        'Form Submissions', 
        'display_widget_crse_27734'
    );  
}

/**
 * Print Dashboard Widget
 *
 * @return void
 **/
function display_widget_crse_27734()
{
    # Dummy data
    $submissionCount = 10;
    $lastUser = array('FirstName' => 'John', 'LastName' => 'Smith', 'DateStamp' => '12569537329' );
    ?>
    <?php if($lastUser) { 
        ?><div><strong>Last entry: </strong>
        <?php echo $lastUser['FirstName'] ?> 
        <?php echo $lastUser['LastName'] ?> 
        (<?php echo date('F jS, Y - g:i a', $lastUser['DateStamp']) ?>)</div>
    <?php } ?>
    <div><strong>Total submissions: </strong> <?php echo $submissionCount ?></div>
    <form method="post">
        <?php wp_nonce_field( 'export_nonce_crse_27734', 'export_nonce_crse_27734' ); ?>
        <input type="hidden" name="action_crse_27734" value="export_csv" />
        <input type="submit" name="myplugin_export_button_crse_27734" value="Export All" />
    </form>
    <?php
}


/**
 * Export CSV File
 *
 * @return void
 **/
function csv_export_crse_27734() 
{
    if( !isset( $_POST['action_crse_27734'] ) || 'export_csv' != $_POST['action_crse_27734'] )
        return;

    if( ! wp_verify_nonce( $_POST['export_nonce_crse_27734'], 'export_nonce_crse_27734' ) )
        return;

    if( ! current_user_can( 'manage_options' ) )
        return;

    $output = db_data_crse_27734();
    if( empty( $output ) )
        return;

    ignore_user_abort( true );
    nocache_headers();
    header( 'Content-Type: text/csv; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename=code-review-export-' . date( 'Y-m-d-G-i-s' ) . '.csv' );
    header( "Expires: 0" );
    echo $output;
    exit;
}

/**
 * Query DB and build CSV string
 *
 * @return string
 **/
function db_data_crse_27734()
{
    global $wpdb;

    # Dummy query
    $args = array( 'ID', 'post_title', 'post_date', 'post_type' );
    $sql_select = implode( ', ', $args );
    $result = $wpdb->get_results("
            SELECT $sql_select
                FROM $wpdb->posts
                WHERE post_status = 'publish' 
    ");

    $output = '';
    if( $result )
    {
        foreach ( $result as $data ) 
        {
            $out_data = array();
            foreach( $data as $field => $value ) 
            {
                $out_data[$field] = stripslashes( $value );
            }
            $output .= implode( '|', $out_data ) . "\r\n";
        }
    }
    return $output;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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