get_option( 'manage_capability', 'manage_options' ) ); if ( current_user_can( $manage_cap ) ) { $table = $_POST['table-to-export']; $type = $_POST['export-file-type']; // Validate the table name the user passed to us. $allow_tbl = array( "useronline", "visit", "visitor", "exclusions", "pages", "search" ); if ( ! in_array( $table, $allow_tbl ) ) { $table = false; } // Validate the file type the user passed to us. if ( ! ( $type == "xml" || $type == "csv" || $type == "tsv" ) ) { $table = false; } if ( $table && $type ) { $file_name = 'wp-statistics' . '-' . $WP_Statistics->Current_Date( 'Y-m-d-H-i' ); switch ( $type ) { case 'xml': $exporter = new ExportDataExcel( 'browser', "{$file_name}.xml" ); break; case 'csv': $exporter = new ExportDataCSV( 'browser', "{$file_name}.csv" ); break; case 'tsv': $exporter = new ExportDataTSV( 'browser', "{$file_name}.tsv" ); break; } $exporter->initialize(); // We need to limit the number of results we retrieve to ensure we don't run out of memory $query_base = "SELECT * FROM {$wpdb->prefix}statistics_{$table}"; $query = $query_base . ' LIMIT 0,1000'; $i = 1; $more_results = true; $result = $wpdb->get_results( $query, ARRAY_A ); // If we didn't get any rows, don't output anything. if ( count( $result ) < 1 ) { echo "No data in table!"; exit; } if ( isset( $_POST['export-headers'] ) and $_POST['export-headers'] ) { foreach ( $result[0] as $key => $col ) { $columns[] = $key; } $exporter->addRow( $columns ); } while ( $more_results ) { foreach ( $result as $row ) { $exporter->addRow( $row ); // Make sure we've flushed the output buffer so we don't run out of memory on large exports. ob_flush(); flush(); } unset( $result ); $wpdb->flush(); $query = $query_base . ' LIMIT ' . ( $i * 1000 ) . ',1000'; $result = $wpdb->get_results( $query, ARRAY_A ); if ( count( $result ) == 0 ) { $more_results = false; } $i ++; } $exporter->finalize(); exit; } } } } } /** * ExportData is the base class for exporters to specific file formats. See other * classes below. */ abstract class ExportData { protected $exportTo; // Set in constructor to one of 'browser', 'file', 'string' protected $stringData; // stringData so far, used if export string mode protected $tempFile; // handle to temp file (for export file mode) protected $tempFilename; // temp file name and path (for export file mode) public $filename; // file mode: the output file name; browser mode: file name for download; string mode: not used public function __construct( $exportTo = "browser", $filename = "exportdata" ) { if ( ! in_array( $exportTo, array( 'browser', 'file', 'string' ) ) ) { throw new Exception( "$exportTo is not a valid ExportData export type" ); } $this->exportTo = $exportTo; $this->filename = $filename; } public function initialize() { switch ( $this->exportTo ) { case 'browser': $this->sendHttpHeaders(); break; case 'string': $this->stringData = ''; break; case 'file': $this->tempFilename = tempnam( sys_get_temp_dir(), 'exportdata' ); $this->tempFile = fopen( $this->tempFilename, "w" ); break; } $this->write( $this->generateHeader() ); } public function addRow( $row ) { $this->write( $this->generateRow( $row ) ); } public function finalize() { $this->write( $this->generateFooter() ); switch ( $this->exportTo ) { case 'browser': flush(); break; case 'string': // do nothing break; case 'file': // close temp file and move it to correct location fclose( $this->tempFile ); rename( $this->tempFilename, $this->filename ); break; } } public function getString() { return $this->stringData; } abstract public function sendHttpHeaders(); protected function write( $data ) { switch ( $this->exportTo ) { case 'browser': echo $data; break; case 'string': $this->stringData .= $data; break; case 'file': fwrite( $this->tempFile, $data ); break; } } protected function generateHeader() { // can be overridden by subclass to return any data that goes at the top of the exported file } protected function generateFooter() { // can be overridden by subclass to return any data that goes at the bottom of the exported file } // In subclasses generateRow will take $row array and return string of it formatted for export type abstract protected function generateRow( $row ); } /** * ExportDataTSV - Exports to TSV (tab separated value) format. */ class ExportDataTSV extends ExportData { function generateRow( $row ) { foreach ( $row as $key => $value ) { // Escape inner quotes and wrap all contents in new quotes. // Note that we are using \" to escape double quote not "" $row[ $key ] = '"' . str_replace( '"', '\"', $value ) . '"'; } return implode( "\t", $row ) . "\n"; } function sendHttpHeaders() { header( "Content-type: text/tab-separated-values" ); header( "Content-Disposition: attachment; filename=" . basename( $this->filename ) ); } } /** * ExportDataCSV - Exports to CSV (comma separated value) format. */ class ExportDataCSV extends ExportData { function generateRow( $row ) { foreach ( $row as $key => $value ) { // Escape inner quotes and wrap all contents in new quotes. // Note that we are using \" to escape double quote not "" $row[ $key ] = '"' . str_replace( '"', '\"', $value ) . '"'; } return implode( ",", $row ) . "\n"; } function sendHttpHeaders() { header( "Content-type: text/csv" ); header( "Content-Disposition: attachment; filename=" . basename( $this->filename ) ); } } /** * Class ExportDataExcel */ class ExportDataExcel extends ExportData { const XmlHeader = "\n"; const XmlFooter = ""; public $encoding = 'UTF-8'; // encoding type to specify in file. // Note that you're on your own for making sure your data is actually encoded to this encoding public $title = 'Sheet1'; // title for Worksheet function generateHeader() { // workbook header $output = stripslashes( sprintf( self::XmlHeader, $this->encoding ) ) . "\n"; // Set up styles $output .= "\n"; $output .= "\n"; $output .= "\n"; // worksheet header $output .= sprintf( "\n \n", htmlentities( $this->title ) ); return $output; } function generateFooter() { $output = ''; // worksheet footer $output .= "
\n
\n"; // workbook footer $output .= self::XmlFooter; return $output; } function generateRow( $row ) { $output = ''; $output .= " \n"; foreach ( $row as $k => $v ) { $output .= $this->generateCell( $v ); } $output .= " \n"; return $output; } private function generateCell( $item ) { $output = ''; $style = ''; // Tell Excel to treat as a number. Note that Excel only stores roughly 15 digits, so keep // as text if number is longer than that. if ( preg_match( "/^-?\d+(?:[.,]\d+)?$/", $item ) && ( strlen( $item ) < 15 ) ) { $type = 'Number'; } // Sniff for valid dates; should look something like 2010-07-14 or 7/14/2010 etc. Can // also have an optional time after the date. // // Note we want to be very strict in what we consider a date. There is the possibility // of really screwing up the data if we try to reformat a string that was not actually // intended to represent a date. elseif ( preg_match( "/^(\d{1,2}|\d{4})[\/\-]\d{1,2}[\/\-](\d{1,2}|\d{4})([^\d].+)?$/", $item ) && ( $timestamp = strtotime( $item ) ) && ( $timestamp > 0 ) && ( $timestamp < strtotime( '+500 years' ) ) ) { $type = 'DateTime'; $item = strftime( "%Y-%m-%dT%H:%M:%S", $timestamp ); $style = 'sDT'; // defined in header; tells excel to format date for display } else { $type = 'String'; } $item = str_replace( ''', ''', htmlspecialchars( $item, ENT_QUOTES ) ); $output .= " "; $output .= $style ? "" : ""; $output .= sprintf( "%s", $type, $item ); $output .= "\n"; return $output; } function sendHttpHeaders() { header( "Content-Type: application/vnd.ms-excel; charset=" . $this->encoding ); header( "Content-Disposition: inline; filename=\"" . basename( $this->filename ) . "\"" ); } }