<?php 
 
/** 
 * LICENSE 
 * 
 * This source file is subject to the new BSD license 
 * It is  available through the world-wide-web at this URL: 
 * http://www.petala-azul.com/bsd.txt 
 * If you did not receive a copy of the license and are unable to 
 * obtain it through the world-wide-web, please send an email 
 * to [email protected] so we can send you a copy immediately. 
 * 
 * @package   Bvb_Grid 
 * @author    Bento Vilas Boas <[email protected]> 
 * @copyright 2010 ZFDatagrid 
 * @license   http://www.petala-azul.com/bsd.txt   New BSD License 
 * @version   $Id$ 
 * @link      http://zfdatagrid.com 
 */ 
 
class Bvb_Grid_Deploy_Csv extends Bvb_Grid implements Bvb_Grid_Deploy_DeployInterface 
{ 
    /** 
     * Storing file 
     */ 
    protected $_outFile = null; 
 
    /** 
     * We don't want to display hidden fields 
     * 
     * @var $_removeHiddenFields boolean 
     */ 
    protected $_removeHiddenFields = true; 
 
    /** 
     * Set to true if setForceRecordsPerPage was called with number 
     * 
     * @var boolean 
     */ 
    protected $_isRecordsPerPageForced = false; 
 
    /** 
     * Optimize performance by setting best value for $this->setPagination(?); 
     * 
     * Options (deploy.csv.<option>): 
     * set_time_limit - time out for php script 
     * memory_limit - PHP memory limit 
     * download - send data directly to user 
     * save - save data to file 
     * fileName - filename used as suggestion when downloading and to save data 
     * dir - directory where to store data 
     * 
     * @param array $options options 
     * @see _prepareOptions 
     * @return void 
     */ 
    public function __construct (array $options = array()) 
    { 
        $this->_setRemoveHiddenFields(true); 
 
        parent::__construct($options); 
 
        // default pagination, should be adjusted based on data processed to improve speed 
        $this->setForceRecordsPerPage(5000); 
 
        // fix configuration options 
        $deploy = $this->getDeployOption($this->_deployName, array()); 
        $defaults = array( 
            'dir'       => '', 
            'store'     => false, 
            'download'  => (!isset($deploy['download']) && !isset($deploy['store'])), 
        ); 
        $deploy = array_merge($defaults, $deploy); 
 
        if (!empty($deploy['dir'])) { 
            $deploy['dir'] = rtrim($deploy['dir'], "/") . "/"; 
        } 
 
        // set the changed options 
        $this->setDeployOption($this->_deployName, $deploy); 
 
        // TODO I don't understand why parent::__constructor will not set this automaticaly, 
        // what if it would be loaded from config ? 
        $this->_deploy = $this->getDeployOption($this->_deployName); 
        $this->checkExportRights(); 
    } 
    /** 
     * Force to use given value as records per page 
     * 
     * Csv should work with as high number of rows as possible to deliver good export speed. On the otherside high number could reach PHP memory limit. 
     * 
     * @param int|boolean $number will not accept any other changes made by setRecordsPerPage if not FALSE 
     * 
     * @return Bvb_Grid 
     */ 
    public function setForceRecordsPerPage($number) 
    { 
        if (false===$number) { 
            $this->_isRecordsPerPageForced = false; 
        } else { 
            $this->_isRecordsPerPageForced = true; 
            $this->_recordsPerPage = (int) $number; 
        } 
 
        return $this; 
    } 
    /** 
     * Number of records to show per page 
     * 
     * @param int $number Records to show 
     * 
     * @return Bvb_Grid 
     */ 
    public function setRecordsPerPage($number = 15) 
    { 
        if (!$this->_isRecordsPerPageForced) { 
            // will be ignore if setForceRecordsPerPage was used to set value 
            return parent::setRecordsPerPage($number); 
        } 
        return $this; 
    } 
    /** 
     * Return name of file 
     * 
     * @return string 
     */ 
    public function getFileName() 
    { 
        $fileName = $this->getInfo('fileName'); 
        if (!$fileName) { 
            $fileName = $this->getInfo('title'); 
        } 
        if (!$fileName) { 
            $fileName = Zend_Controller_Front::getInstance()->getRequest()->getParam('controller') . '-' . date("Ymd"); 
        } 
        return $fileName . '.csv'; 
    } 
 
    /** 
     * Build list of column names for header row 
     * 
     * @return string 
     */ 
    public function buildTitles() 
    { 
        return $this->formatLine($this->_buildTitles()); 
    } 
 
    /** 
     * Create line with values build based on sql expressions 
     * 
     * @return string 
     */ 
    public function buildSqlexp() 
    { 
        return $this->formatLine($this->_buildSqlExp()); 
    } 
 
    /** 
     * Build data rows 
     * 
     * @return string 
     */ 
    public function buildGrid() 
    { 
        return implode("\n", array_map(array($this, 'formatLine'), $this->_buildGrid())); 
    } 
 
    /** 
     * Escape/format an array of row data as a single line of CSV 
     * @param array $row 
     * @return string 
     */ 
    protected function formatLine($row) 
    { 
        // TODO: _buildGrid should be refactored so we don't have to call arrayPluck here 
        $s = implode(',', array_map(array($this, 'formatCell'), $this->arrayPluck($row))); 
        return $s; 
    } 
 
    /** 
     * Format CSV cell 
     * @param string $value 
     * @return string 
     */ 
    protected function formatCell($value) 
    { 
        return '"' . strip_tags($value) . '"'; 
    } 
 
    /** 
     * Add row to results 
     * 
     * Depending on settings store to file and/or directly upload 
     * 
     * @param array $data data 
     * @return void 
     */ 
    protected function csvAddData($data) 
    { 
        if (0==strlen($data)) { 
            return; 
        } 
        if ($this->getDeployOption('download')) { 
            // send first headers 
            echo $data."\n"; 
            flush(); 
            ob_flush(); 
        } 
        if ($this->getDeployOption('store')) { 
            // open file handler 
            fwrite($this->_outFile, $data."\n"); 
        } 
    } 
 
    /** 
     * Deploy method 
     * 
     * @return boolean FALSE if error 
     */ 
    public function deploy() 
    { 
        // prepare data 
        $this->_prepareOptions(); 
        parent::deploy(); 
 
        if ($this->getDeployOption('download')) { 
            // send first headers 
            ob_end_clean(); 
 
            header('Content-Description: File Transfer'); 
            header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1 
            header('Pragma: public'); 
            header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past 
            header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); 
            header("Content-Type: application/csv"); 
            header('Content-Disposition: attachment; filename="' . $this->getFileName() . '"'); 
            header('Content-Transfer-Encoding: binary'); 
        } 
        if ($this->getDeployOption('store')) { 
            // open file handler 
            $this->_outFile = fopen($this->_deploy['dir'] . $this->getFileName(), "w"); 
        } 
 
        // export header 
        if (!$this->getDeployOption('skipHeaders')) { 
            $this->csvAddData($this->buildTitles()); 
        } 
        $i = 0; 
        do { 
            $i += $this->_recordsPerPage; 
            $this->csvAddData($this->buildGrid()); 
            $this->csvAddData($this->buildSqlexp()); 
 
            // get next page 
            $this->getSource()->buildQueryLimit($this->_recordsPerPage, $i); 
            $this->_result = $this->getSource()->execute(); 
        } while (count($this->_result)); 
 
        if ($this->getDeployOption('store')) { 
            // close file handler 
            fclose($this->_outFile); 
        } 
        if ($this->getDeployOption('download')) { 
            // we set special headers and uploaded data, there is nothing more we could do 
            die(); 
        } 
 
        return true; 
    } 
 
    /** 
     * Set some deploy settings 
     * 
     * @return void 
     */ 
    protected function _prepareOptions() 
    { 
        // apply options 
        if (isset($this->_deploy['set_time_limit'])) { 
            // script needs time to proces huge amount of data (important) 
            set_time_limit($this->_deploy['set_time_limit']); 
        } 
        if (isset($this->_deploy['memory_limit'])) { 
            // adjust memory_limit if needed (not very important) 
            ini_set('memory_limit', $this->_deploy['memory_limit']); 
        } 
    } 
 
    /** 
     * Return file if download requested without the need to continue up to deploy() method 
     * 
     * @return Bvb_Grid_Deploy_Csv 
     */ 
    public function setAjax() 
    { 
        if ($this->getDeployOption('download')) { 
            // if we want to upload data then we should do it now, deploy will die if needed 
            $this->deploy(); 
        } 
        return $this; 
    } 
}
 
 |