Structures_DataGrid
[ class tree: Structures_DataGrid ] [ index: Structures_DataGrid ] [ all elements ]

Source for file DataGrid.php

Documentation is available at DataGrid.php

  1. <?php
  2. /**
  3.  * Structures_DataGrid Class
  4.  * 
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE:
  8.  * 
  9.  * Copyright (c) 1997-2007, Andrew Nagy <asnagy@webitecture.org>,
  10.  *                          Olivier Guilyardi <olivier@samalyse.com>,
  11.  *                          Mark Wiesemann <wiesemann@php.net>
  12.  *                          Sascha Grossenbacher <saschagros@bluewin.ch>
  13.  * All rights reserved.
  14.  *
  15.  * Redistribution and use in source and binary forms, with or without
  16.  * modification, are permitted provided that the following conditions
  17.  * are met:
  18.  *
  19.  *    * Redistributions of source code must retain the above copyright
  20.  *      notice, this list of conditions and the following disclaimer.
  21.  *    * Redistributions in binary form must reproduce the above copyright
  22.  *      notice, this list of conditions and the following disclaimer in the
  23.  *      documentation and/or other materials provided with the distribution.
  24.  *    * The names of the authors may not be used to endorse or promote products
  25.  *      derived from this software without specific prior written permission.
  26.  *
  27.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  28.  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  29.  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  30.  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  31.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  32.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  33.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  34.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  35.  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  36.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  37.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  38.  *
  39.  * CSV file id: $Id: DataGrid.php,v 1.144 2007/11/05 16:17:15 olivierg Exp $
  40.  * 
  41.  * @version  $Revision: 1.144 $
  42.  * @package  Structures_DataGrid
  43.  * @category Structures
  44.  * @license  http://opensource.org/licenses/bsd-license.php New BSD License
  45.  */
  46.  
  47.  
  48. require_once 'PEAR.php';
  49.  
  50. require_once 'Structures/DataGrid/Column.php';
  51.  
  52. // Rendering Drivers
  53. define('DATAGRID_RENDER_TABLE',    'HTMLTable');
  54. define('DATAGRID_RENDER_SMARTY',   'Smarty');
  55. define('DATAGRID_RENDER_XML',      'XML');
  56. define('DATAGRID_RENDER_XLS',      'XLS');
  57. define('DATAGRID_RENDER_XUL',      'XUL');
  58. define('DATAGRID_RENDER_CSV',      'CSV');
  59. define('DATAGRID_RENDER_CONSOLE',  'Console');
  60. define('DATAGRID_RENDER_PAGER',    'Pager');
  61. define('DATAGRID_RENDER_SORTFORM''HTMLSortForm');
  62.  
  63. define('DATAGRID_RENDER_DEFAULT',  DATAGRID_RENDER_TABLE);
  64.  
  65. // DataSource Drivers
  66. define('DATAGRID_SOURCE_ARRAY',     'Array');
  67. define('DATAGRID_SOURCE_DATAOBJECT','DataObject');
  68. define('DATAGRID_SOURCE_DB',        'DB');
  69. define('DATAGRID_SOURCE_XML',       'XML');
  70. define('DATAGRID_SOURCE_RSS',       'RSS');
  71. define('DATAGRID_SOURCE_CSV',       'CSV');
  72. define('DATAGRID_SOURCE_DBQUERY',   'DBQuery');
  73. define('DATAGRID_SOURCE_DBTABLE',   'DBTable');
  74. define('DATAGRID_SOURCE_MDB2',      'MDB2');
  75.  
  76. // PEAR_Error code for unsupported features
  77. define('DATAGRID_ERROR_UNSUPPORTED'1);
  78.  
  79. /**
  80.  * Structures_DataGrid Class
  81.  *
  82.  * A PHP class to implement the functionality provided by the .NET Framework's
  83.  * DataGrid control.  This class can produce a data driven list in many formats
  84.  * based on a defined record set.  Commonly, this is used for outputting an HTML
  85.  * table based on a record set from a database or an XML document. It allows
  86.  * for the output to be published in many ways, including an HTML table,
  87.  * an HTML Template, an Excel spreadsheet, an XML document. The data can
  88.  * be sorted and paged, each cell can have custom output, and the table can be
  89.  * custom designed with alternating color rows.
  90.  *
  91.  * Quick Example:
  92.  * <code>
  93.  * <?php
  94.  * require 'Structures/DataGrid.php';
  95.  * $datagrid =& new Structures_DataGrid();
  96.  * $options = array('dsn' => 'mysql://user:password@host/db_name');
  97.  * $datagrid->bind("SELECT * FROM my_table", $options);
  98.  * $datagrid->render();
  99.  * ?>
  100.  * </code>
  101.  *
  102.  * @author   Andrew S. Nagy <asnagy@webitecture.org>
  103.  * @author   Olivier Guilyardi <olivier@samalyse.com>
  104.  * @author   Mark Wiesemann <wiesemann@php.net>
  105.  * @author   Sascha Grossenbacher <saschagros@bluewin.ch>
  106.  * @access   public
  107.  * @package  Structures_DataGrid
  108.  * @category Structures
  109.  */
  110. {
  111.     /**
  112.      * Renderer driver
  113.      * @var object Structures_DataGrid_Renderer_* family
  114.      * @access private
  115.      */ 
  116.     var $_renderer;
  117.  
  118.     /**
  119.      * Renderer driver type
  120.      * @var int DATAGRID_RENDER_* constant family
  121.      * @access private
  122.      */ 
  123.     var $_rendererType = null;
  124.  
  125.     /**
  126.      * Options to use for all renderers
  127.      * @var array 
  128.      * @access private
  129.      */
  130.     var $_rendererCommonOptions = array();
  131.  
  132.     /**
  133.      * Renderer driver backup
  134.      * @var object Structures_DataGrid_Renderer_* family
  135.      * @access private
  136.      */ 
  137.     var $_rendererBackup;
  138.  
  139.     /**
  140.      * Renderer driver type backup
  141.      * @var int DATAGRID_RENDER_* constant family
  142.      * @access private
  143.      */ 
  144.     var $_rendererTypeBackup = null;
  145.  
  146.     /**
  147.      * Whether the backup is an empty renderer
  148.      * 
  149.      * This property is set to true when _saveRenderer() is called and there
  150.      * is no renderer loaded.
  151.      * 
  152.      * @var bool 
  153.      * @access private
  154.      */ 
  155.     var $_rendererEmptyBackup = false;
  156.  
  157.     /**
  158.      * Array of columns.  Columns are defined as a DataGridColumn object.
  159.      * @var array 
  160.      * @access private
  161.      */
  162.     var $columnSet = array();
  163.  
  164.     /**
  165.      * Array of records.
  166.      * @var array 
  167.      * @access private
  168.      */
  169.     var $recordSet = array();
  170.  
  171.     /**
  172.      * The Data Source Driver object
  173.      * @var object Structures_DataGrid_DataSource 
  174.      * @access private
  175.      */
  176.     var $_dataSource;    
  177.     
  178.     /**
  179.      * Fields/directions to sort the data by
  180.      *
  181.      * @var array Structure: array(fieldName => direction, ....)
  182.      * @access private
  183.      */
  184.     var $sortSpec = array();
  185.  
  186.     /**
  187.      * Default fields/directions to sort the data by
  188.      *
  189.      * @var array Structure: array(fieldName => direction, ....)
  190.      * @access private
  191.      */
  192.     var $defaultSortSpec = array();
  193.  
  194.     /**
  195.      * Limit of records to show per page.
  196.      * @var string 
  197.      * @access private
  198.      */
  199.     var $rowLimit;
  200.  
  201.     /**
  202.      * The current page to show.
  203.      * @var string 
  204.      * @access private
  205.      */
  206.     var $page;
  207.  
  208.     /**
  209.      * Whether the page number was provided at instantiation or not
  210.      * @var bool 
  211.      * @access private
  212.      */
  213.     var $_forcePage
  214.     
  215.     /**
  216.      * GET/POST/Cookie parameters prefix
  217.      * @var string 
  218.      * @access private
  219.      */
  220.     var $_requestPrefix '';    
  221.  
  222.     /**
  223.      * Possible renderer types and their equivalent renderer constants
  224.      * @var array 
  225.      * @access private
  226.      */
  227.     var $_rendererTypes = array(
  228.         'html_table' => DATAGRID_RENDER_TABLE,
  229.         'smarty' => DATAGRID_RENDER_SMARTY,
  230.         'spreadsheet_excel_writer_workbook' => DATAGRID_RENDER_XLS,
  231.         'console_table' => DATAGRID_RENDER_CONSOLE,
  232.         'pager_common' => DATAGRID_RENDER_PAGER,
  233.     );
  234.  
  235.     /**
  236.      * Number of records that should be buffered when streaming is enabled
  237.      * @var integer 
  238.      * @access private
  239.      */
  240.     var $_bufferSize = null;
  241.     
  242.     /**
  243.      * Array of matched params
  244.      *
  245.      * @var array 
  246.      * @access private
  247.      */
  248.     var $_mapperMatch = null;
  249.     
  250.     /**
  251.      * Allowed URL parameters. Format: Name => regex
  252.      *
  253.      * @var array 
  254.      * @access private
  255.      */
  256.     var $_mapperRules = array(
  257.         'page' => '[0-9]+',
  258.         'orderBy' => '[^\/]+',
  259.         'direction' => '(ASC|DESC|asc|desc)'
  260.     );
  261.     
  262.     /**
  263.      * Default values for mapper. Format: Name => value
  264.      * 
  265.      * A default value triggers the param to be optional
  266.      * 
  267.      * @var array 
  268.      * @access private
  269.      */
  270.     var $_mapperDefaults = array(
  271.         'orderBy' => null,
  272.         'direction' => null,
  273.         'page' => 1
  274.     );
  275.  
  276.     /**
  277.      * URL mapper instance, if activated
  278.      * 
  279.      * @var object Net_URL_Mapper 
  280.      * @access protected
  281.      */
  282.     var $_urlMapper = null;
  283.     
  284.    /**
  285.      * Constructor
  286.      *
  287.      * Builds the DataGrid class. The Core functionality and Renderer are
  288.      * seperated for maintainability and to keep cohesion high.
  289.      *
  290.      * @example constructor.php     Instantiation
  291.      * @param  string   $limit      The number of records to display per page.
  292.      * @param  int      $page       The current page viewed.
  293.      *                               In most cases, this is useless.
  294.      *                               Note: if you specify this, the "page" GET
  295.      *                               variable will be ignored.
  296.      * @param  string   $rendererType  The type of renderer to use.
  297.      *                                  You may prefer to use the $type argument
  298.      *                                  of {@link render}{@link fill} or
  299.      *                                  {@link getOutput}
  300.      *                                  
  301.      * @return void 
  302.      * @access public
  303.      */
  304.     function Structures_DataGrid($limit = null$page = null,
  305.                                  $rendererType = null)
  306.     {
  307.         // Set the defined rowlimit
  308.         $this->rowLimit $limit;
  309.         
  310.         //Use set page number, otherwise automatically detect the page number
  311.         if (!is_null($page)) {
  312.             $this->page $page;
  313.             $this->_forcePage = true;
  314.         else {
  315.             $this->page = 1;
  316.             $this->_forcePage = false;
  317.         }
  318.  
  319.         // Automatic handling of GET/POST/COOKIE variables
  320.         $this->_parseHttpRequest();
  321.  
  322.         if (!is_null($rendererType)) {
  323.             $this->setRenderer($rendererType);
  324.         }
  325.     }
  326.  
  327.     /**
  328.      * Method used for debugging purposes only.  Displays a dump of the DataGrid
  329.      * object.
  330.      *
  331.      * @access  public
  332.      * @return  void 
  333.      */
  334.     function dump()
  335.     {
  336.         echo '<pre>';
  337.         print_r($this);
  338.         echo '</pre>';
  339.     }
  340.  
  341.     /**
  342.      * Checks if a file exists in the include path
  343.      *
  344.      * @access private
  345.      * @param  string   filename
  346.      * @return boolean true success and false on error
  347.      */
  348.     function fileExists($file)
  349.     {
  350.         $fp @fopen($file'r'true);
  351.         if (is_resource($fp)) {
  352.             @fclose($fp);
  353.             return true;
  354.          }
  355.          return false;
  356.     }
  357.  
  358.     /**
  359.      * Checks if a class exists without triggering __autoload
  360.      *
  361.      * @param  string  className
  362.      * @return bool true success and false on error
  363.      *
  364.      * @access private
  365.      */
  366.     function classExists($className)
  367.     {
  368.         if (version_compare(phpversion()"5.0"">=")) {
  369.             return class_exists($classNamefalse);
  370.         }
  371.         return class_exists($className);
  372.     }
  373.  
  374.     /**
  375.      * Load a Renderer or DataSource driver
  376.      * 
  377.      * @param string $className Name of the driver class
  378.      * @access private
  379.      * @return object The driver object or a PEAR_Error
  380.      * @static
  381.      */
  382.     function &loadDriver($className)
  383.     {
  384.         if (!Structures_DataGrid::classExists($className)) {
  385.             $fileName str_replace('_'DIRECTORY_SEPARATOR$className'.php';
  386.             if (!include_once($fileName)) {
  387.                 if (!Structures_DataGrid::fileExists($fileName)) {
  388.                     $msg = "unable to find package '$className' file '$fileName'";
  389.                 else {
  390.                     $msg = "unable to load driver class '$className' from file '$fileName'";
  391.                 }
  392.                 $error = PEAR::raiseError($msg);
  393.                 return $error;
  394.             }
  395.         }
  396.  
  397.         $driver =new $className();
  398.         return $driver;
  399.     }
  400.     
  401.     /**
  402.      * Datasource driver Factory
  403.      *
  404.      * A clever method which loads and instantiate data source drivers.
  405.      *
  406.      * Can be called in various ways:
  407.      *
  408.      * Detect the source type and load the appropriate driver with default
  409.      * options:
  410.      * <code>
  411.      * $driver =& Structures_DataGrid::dataSourceFactory($source);
  412.      * </code>
  413.      *
  414.      * Detect the source type and load the appropriate driver with custom
  415.      * options:
  416.      * <code>
  417.      * $driver =& Structures_DataGrid::dataSourceFactory($source, $options);
  418.      * </code>
  419.      *
  420.      * Load a driver for an explicit type (faster, bypasses detection routine):
  421.      * <code>
  422.      * $driver =& Structures_DataGrid::dataSourceFactory($source, $options, $type);
  423.      * </code>
  424.      *
  425.      * @access  public
  426.      * @param   mixed   $source     The data source respective to the driver
  427.      * @param   array   $options    An associative array of the form:
  428.      *                               array(optionName => optionValue, ...)
  429.      * @param   string  $type       The data source type constant (of the form
  430.      *                               DATAGRID_SOURCE_*)
  431.      * @uses    Structures_DataGrid::_detectSourceType()
  432.      * @return  Structures_DataGrid_DataSource|PEAR_Error
  433.      *                               driver object or PEAR_Error on failure
  434.      * @static
  435.      */
  436.     function &dataSourceFactory($source$options = array()$type = null)
  437.     {
  438.         if (is_null($type&&
  439.             !($type Structures_DataGrid::_detectSourceType($source,
  440.                                                              $options))) {
  441.             $error = PEAR::raiseError('Unable to determine the data source type. '.
  442.                                       'You may want to explicitly specify it.');
  443.             return $error;
  444.         }
  445.  
  446.         $type Structures_DataGrid::_correctDriverName($type'DataSource');
  447.         if (PEAR::isError($type)) {
  448.             return $type;
  449.         }
  450.  
  451.         $className = "Structures_DataGrid_DataSource_$type";
  452.  
  453.         if (PEAR::isError($driver =Structures_DataGrid::loadDriver($className))) {
  454.             return $driver;
  455.         }
  456.         
  457.         $result $driver->bind($source$options);
  458.        
  459.         if (PEAR::isError($result)) {
  460.             return $result;
  461.         else {
  462.             return $driver;
  463.         }
  464.     }
  465.  
  466.     /**
  467.      * Renderer driver factory
  468.      *
  469.      * Load and instantiate a renderer driver.
  470.      * 
  471.      * @access  private
  472.      * @param   mixed   $source     The rendering container respective to the driver
  473.      * @param   array   $options    An associative array of the form:
  474.      *                               array(optionName => optionValue, ...)
  475.      * @param   string  $type       The renderer type constant (of the form
  476.      *                               DATAGRID_RENDER_*)
  477.      * @uses    Structures_DataGrid_DataSource::_detectRendererType()
  478.      * @return  mixed               Returns the renderer driver object or
  479.      *                               PEAR_Error on failure
  480.      */
  481.     function &rendererFactory($type$options = array())
  482.     {
  483.         $type Structures_DataGrid::_correctDriverName($type'Renderer');
  484.         if (PEAR::isError($type)) {
  485.             return $type;
  486.         }
  487.  
  488.         $className = "Structures_DataGrid_Renderer_$type";
  489.  
  490.         if (PEAR::isError($driver =Structures_DataGrid::loadDriver($className))) {
  491.             return $driver;
  492.         }        
  493.  
  494.         $options array_merge($this->_rendererCommonOptions$options);
  495.         if ($this->_urlMapper{
  496.             $driver->setUrlMapper($this->_urlMapper);
  497.         }
  498.         if ($options{
  499.             $driver->setOptions((array)$options);
  500.         }
  501.  
  502.         return $driver;
  503.     }
  504.  
  505.     /**
  506.      * Render the datagrid
  507.      *
  508.      * You can call this method several times with different renderers.
  509.      * 
  510.      * @param  mixed  $renderer Renderer type or instance (optional)
  511.      * @param  array  $options  An associative array of the form:
  512.      *                           array(optionName => optionValue, ...)
  513.      * @access public
  514.      * @return mixed    True or PEAR_Error
  515.      */
  516.     function render($renderer = null$options = array())
  517.     {
  518.         if (!is_null($renderer)) {
  519.             $this->_saveRenderer();
  520.             
  521.             if (is_a($renderer'Structures_DataGrid_Renderer')) {
  522.                 $result $this->attachRenderer($renderer);
  523.             else {
  524.                 $result $this->setRenderer($renderer);
  525.             }
  526.             if (PEAR::isError($result)) {
  527.                 $this->_restoreRenderer();
  528.                 return $result;
  529.             }
  530.         else if (!isset($this->_renderer)) {
  531.             $result $this->setRenderer(DATAGRID_RENDER_DEFAULT);
  532.             if (PEAR::isError($result)) {
  533.                 return $result;
  534.             }
  535.         }
  536.  
  537.         $options array_merge($this->_rendererCommonOptions(array)$options);
  538.         if ($options{
  539.             $this->_renderer->setOptions($options);
  540.         }
  541.  
  542.         if (!$this->_renderer->isBuilt()) {
  543.             $result $this->build();
  544.             if (PEAR::isError($result)) {
  545.                 return $result;
  546.             }
  547.         }
  548.  
  549.         $result $this->_renderer->render();
  550.         if (PEAR::isError($result)) {
  551.             if ($result->getCode(== DATAGRID_ERROR_UNSUPPORTED{
  552.                 $type is_null($this->_rendererType
  553.                         ? get_class($this->_renderer)
  554.                         : $this->_rendererType;
  555.                 $this->_restoreRenderer();
  556.                 return PEAR::raiseError("The $type driver does not support the ".
  557.                                         "render() method. Try using fill().");
  558.             else {
  559.                 $this->_restoreRenderer();
  560.                 return $result;
  561.             }
  562.         }
  563.         $this->_restoreRenderer();
  564.  
  565.         return true;
  566.     }
  567.  
  568.     /**
  569.      * Return the datagrid output
  570.      *
  571.      * @param  int    $type     Renderer type (optional)
  572.      * @param  array  $options  An associative array of the form:
  573.      *                           array(optionName => optionValue, ...)
  574.      * @access public
  575.      * @return mixed The datagrid output (Usually a string: HTML, CSV, etc...)
  576.      *                or a PEAR_Error
  577.      */
  578.     function getOutput($type = null$options = array())
  579.     {
  580.         if (!is_null($this->_bufferSize)) {
  581.             return PEAR::raiseError('getOutput() cannot be used together with ' .
  582.                                     'streaming.');
  583.         }
  584.  
  585.         if (!is_null($type)) {
  586.             $this->_saveRenderer();
  587.             
  588.             $test $this->setRenderer($type);
  589.             if (PEAR::isError($test)) {
  590.                 $this->_restoreRenderer();
  591.                 return $test;
  592.             }
  593.         else if (!isset($this->_renderer)) {
  594.             $this->setRenderer(DATAGRID_RENDER_DEFAULT);
  595.         }
  596.         
  597.         $options array_merge($this->_rendererCommonOptions(array)$options);
  598.         if ($options{
  599.             $this->_renderer->setOptions($options);
  600.         }
  601.         
  602.         if (!$this->_renderer->isBuilt()) {
  603.             $result $this->build();
  604.             if (PEAR::isError($result)) {
  605.                 return $result;
  606.             }
  607.         }
  608.         
  609.         $output $this->_renderer->getOutput();
  610.         if (PEAR::isError($output&& $output->getCode(== DATAGRID_ERROR_UNSUPPORTED{
  611.             $type is_null($this->_rendererType
  612.                     ? get_class($this->_renderer)
  613.                     : $this->_rendererType;
  614.             $this->_restoreRenderer();
  615.             return PEAR::raiseError("The $type driver does not support the ".
  616.                                     "getOutput() method. Try using render().");
  617.         }
  618.         
  619.         $this->_restoreRenderer();
  620.         return $output;
  621.     }
  622.  
  623.     /**
  624.      * Get the current or default Rendering driver
  625.      *
  626.      * Retrieves the renderer object as a reference
  627.      *
  628.      * @return object Renderer object reference
  629.      * @access public
  630.      */
  631.     function &getRenderer()
  632.     {
  633.         isset($this->_rendereror $this->setRenderer(DATAGRID_RENDER_DEFAULT);
  634.         return $this->_renderer;
  635.     }
  636.  
  637.     /**
  638.      * Get the currently loaded DataSource driver
  639.      *
  640.      * Retrieves the DataSource object as a reference
  641.      *
  642.      * @return object DataSource object reference or null if no driver is loaded
  643.      * @access public
  644.      */
  645.     function &getDataSource()
  646.     {
  647.         if (isset($this->_dataSource)) {
  648.             return $this->_dataSource;
  649.         }
  650.         return null;
  651.     }
  652.  
  653.     /**
  654.      * Set Renderer
  655.      *
  656.      * Defines which renderer to be used by the DataGrid based on given
  657.      * $type and $options. To attach an existing renderer instance, use
  658.      * attachRenderer() instead.
  659.      *
  660.      * @param  string   $type           The defined renderer string
  661.      * @param  array    $options        Rendering options
  662.      * @return mixed    Renderer instance or PEAR_Error
  663.      * @access public
  664.      * @see Structures_DataGrid::attachRenderer()
  665.      */
  666.     function &setRenderer($type$options = array())
  667.     {
  668.         $renderer =$this->rendererFactory($type$options);
  669.         if (PEAR::isError($renderer)) {
  670.             return $renderer;
  671.         }
  672.         $this->_rendererType $type;
  673.         return $this->attachRenderer($renderer);
  674.     }
  675.  
  676.     /**
  677.      * Backup the current renderer
  678.      * 
  679.      * @return void 
  680.      * @access private
  681.      */
  682.     function _saveRenderer()
  683.     {
  684.         if (isset($this->_renderer)) {
  685.             // The following line is a workaround for PHP bug 32660
  686.             // See: http://bugs.php.net/bug.php?id=32660
  687.             // Another solution would be to remove __get which is used only for BC
  688.             $this->_rendererBackup = 1; 
  689.  
  690.             $this->_rendererBackup =$this->_renderer;
  691.             $this->_rendererTypeBackup $this->_rendererType;
  692.  
  693.             unset($this->_renderer);
  694.             $this->_rendererType = null;
  695.         else {
  696.             $this->_rendererEmptyBackup = true;
  697.         }
  698.     }
  699.     
  700.     /**
  701.      * Restore a previously saved renderer
  702.      * 
  703.      * If the $_renderer property was not set when _saveRenderer() got
  704.      * previously called, _restoreRenderer() will unset it.
  705.      * 
  706.      * @return void 
  707.      * @access private
  708.      */
  709.     function _restoreRenderer()
  710.     {
  711.         if ($this->_rendererEmptyBackup{
  712.             unset($this->_renderer);
  713.             $this->_rendererType = null;
  714.         elseif (isset($this->_rendererBackup)) {
  715.             $this->_renderer =$this->_rendererBackup;
  716.             $this->_rendererType $this->_rendererTypeBackup;
  717.         
  718.         
  719.         unset($this->_rendererBackup);
  720.         $this->_rendererTypeBackup = null;
  721.         $this->_rendererEmptyBackup = false;
  722.     }
  723.   
  724.     /**
  725.      * Tell the renderer how the data is sorted
  726.      *
  727.      * This method takes the "multiSort" capabilities of the datasource
  728.      * into account. The idea is to correctly inform the renderer : for
  729.      * example, a GET request may contain multiple fields and directions
  730.      * to sort by. But, if the datasource does not support "multiSort"
  731.      * then the renderer should not be told that the data is sorted according
  732.      * to multiple fields.
  733.      *
  734.      * It also properly set the "multiSortCapable" renderer flag (second argument
  735.      * to Renderer::setCurrentSorting()).
  736.      * 
  737.      * This method requires both a datasource and renderer to be loaded.
  738.      * 
  739.      * It should be called even if $sortSpec is empty.
  740.      *
  741.      * @return void 
  742.      * @access private
  743.      */
  744.     function _setRendererCurrentSorting()
  745.     {
  746.         $sortSpec $this->sortSpec $this->sortSpec $this->defaultSortSpec;
  747.         if (isset($this->_dataSource)
  748.             && $this->_dataSource->hasFeature('multiSort')
  749.            {
  750.             $this->_renderer->setCurrentSorting($sortSpectrue);
  751.         else {
  752.             reset($sortSpec);
  753.             list($field$directioneach($sortSpec);
  754.             $this->_renderer->setCurrentSorting(
  755.                     array($field => $direction)false);
  756.         }
  757.     }
  758.     
  759.     /**
  760.      * Attach an already instantiated Rendering driver
  761.      * 
  762.      * @param object $renderer Driver object, subclassing
  763.      *                          Structures_DataGrid_Renderer
  764.      * @return mixed           Renderer instance or a PEAR_Error object
  765.      * @access public
  766.      * @see Structures_DataGrid::setRenderer()
  767.      */
  768.     function &attachRenderer(&$renderer)
  769.     {
  770.         if (is_subclass_of($renderer'structures_datagrid_renderer')) {
  771.             // The following line is a workaround for PHP bug 32660
  772.             // See: http://bugs.php.net/bug.php?id=32660
  773.             $this->_renderer = 1;
  774.             
  775.             $this->_renderer =$renderer;
  776.             if (isset($this->_dataSource)) {
  777.                 $this->_renderer->setColumns($this->columnSet);
  778.                 $this->_renderer->setLimit($this->page$this->rowLimit
  779.                                            $this->getRecordCount());
  780.                 $this->_setRendererCurrentSorting();
  781.             }
  782.             if ($this->_requestPrefix{
  783.                 $this->_renderer->setRequestPrefix($this->_requestPrefix)
  784.             }
  785.  
  786.         else {
  787.             return PEAR::raiseError('Invalid renderer type, ' 
  788.                                     'must be a valid renderer driver class');
  789.         }
  790.  
  791.         return $renderer;
  792.     }
  793.  
  794.     /**
  795.      * Fill a rendering container with data
  796.      * 
  797.      * @example fill-sortform.php Fill a form with sort fields
  798.      * @example fill-pager.php    Filling a Pager object
  799.      * @param object &$container A rendering container of any of the supported
  800.      *                           types (example: an HTML_Table object,
  801.      *                           a Spreadsheet_Excel_Writer object, etc...)
  802.      * @param array  $options   Options for the corresponding rendering driver
  803.      * @param string $type      Explicit type in case the container type
  804.      *                           can't be detected
  805.      * @return mixed            Either true or a PEAR_Error object
  806.      * @access public
  807.      */
  808.     function fill(&$container$options = array()$type = null)
  809.     {
  810.         if (is_null($type)) {
  811.             $type $this->_detectRendererType($container);
  812.             if (is_null($type)) {
  813.                 return PEAR::raiseError('The rendering container type can not '.
  814.                                         'be automatically detected. Please ' 
  815.                                         'specify its type explicitly.');
  816.             }
  817.         }
  818.  
  819.         /* Is a renderer driver already loaded and does it exactly match 
  820.          * the driver class name that corresponds to $type ? */
  821.         //FIXME: is this redundant with the $rendererType property ?
  822.         if (!isset($this->_renderer
  823.             or !is_a($this->_renderer"Structures_DataGrid_Renderer_$type")) {
  824.             /* No, then load the right driver */
  825.             $this->_saveRenderer();
  826.             if (PEAR::isError($test $this->setRenderer($type$options))) {
  827.                 $this->_restoreRenderer();
  828.                 return $test;
  829.             }
  830.         else {
  831.             $options array_merge($this->_rendererCommonOptions(array)$options);
  832.             $this->_renderer->setOptions($options);
  833.         }
  834.  
  835.         $test $this->_renderer->setContainer($container);
  836.         if (PEAR::isError($test)) {
  837.             if ($test->getCode(== DATAGRID_ERROR_UNSUPPORTED{
  838.                 $this->_restoreRenderer();
  839.                 return PEAR::raiseError("The $type driver does not support the " . 
  840.                                         "fill() method. Try using render().");
  841.             else {
  842.                 $this->_restoreRenderer();
  843.                 return $test;
  844.             }
  845.         }
  846.  
  847.         if (!$this->_renderer->isBuilt()) {
  848.             $result $this->build();
  849.             if (PEAR::isError($result)) {
  850.                 return $result;
  851.             }
  852.         }
  853.  
  854.         $this->_restoreRenderer();
  855.         return true;
  856.     }
  857.  
  858.     /**
  859.      * Create Default Columns
  860.      *
  861.      * This method handles the instantiation of default column objects,
  862.      * when some records have been fetched from the datasource but columns
  863.      * have neither been generated, nor provided by the user.
  864.      *
  865.      * @access private
  866.      * @return void 
  867.      */
  868.     function _createDefaultColumns()
  869.     {
  870.         if (empty($this->columnSet)) {
  871.             $this->generateColumns();
  872.         }
  873.     }
  874.  
  875.     /**
  876.      * Retrieves the current page number when paging is implemented
  877.      *
  878.      * @return int       the current page number
  879.      * @access public
  880.      */
  881.     function getCurrentPage()
  882.     {
  883.         return $this->page;
  884.     }
  885.  
  886.     /**
  887.      * Define the current page number.
  888.      *
  889.      * This method is used when paging is implemented
  890.      *
  891.      * @access public
  892.      * @param  mixed     $page       The current page number (as string or int).
  893.      */
  894.     function setCurrentPage($page)
  895.     {
  896.         $this->page $page;
  897.     }
  898.  
  899.     /**
  900.      * Returns the total number of pages
  901.      *
  902.      * @return int       the total number of pages or 1 if there are no records
  903.      *                    or if there is no row limit
  904.      * @access public
  905.      */
  906.     function getPageCount()
  907.     {
  908.         if (is_null($this->rowLimit|| $this->getRecordCount(== 0{
  909.             return 1;
  910.         else {
  911.             return ceil($this->getRecordCount($this->rowLimit);
  912.         }
  913.     }
  914.  
  915.     /**
  916.      * Returns the number of columns
  917.      *
  918.      * @return int       the number of columns
  919.      * @access public
  920.      */
  921.     function getColumnCount()
  922.     {
  923.         return count($this->columnSet);
  924.     }
  925.  
  926.     /**
  927.      * Returns the total number of records
  928.      *
  929.      * @return int       the total number of records
  930.      * @access public
  931.      */
  932.     function getRecordCount()
  933.     {
  934.         if (isset($this->_dataSource)) {
  935.             return $this->_dataSource->count();
  936.         else {
  937.             // If there is no datasource then there is no data. The old way
  938.             // of putting an array into the recordSet property does not exist
  939.             // anymore. Binding an array loads the Array datasource driver.
  940.             return 0;
  941.         }
  942.     }    
  943.  
  944.     /**
  945.      * Returns the number of the first record of the current page
  946.      *
  947.      * @return int       the number of the first record currently shown, or: 0
  948.      *                    if there are no records, 1 if there is no row limit
  949.      * @access public
  950.      */
  951.     function getCurrentRecordNumberStart()
  952.     {
  953.         if (is_null($this->page)) {
  954.             return 1;
  955.         elseif ($this->getRecordCount(== 0{
  956.             return 0;
  957.         else {
  958.             return ($this->page - 1$this->rowLimit + 1;
  959.         }
  960.     }
  961.  
  962.     /**
  963.      * Returns the number of the last record of the current page
  964.      *
  965.      * @return int       the number of the last record currently shown
  966.      * @access public
  967.      */
  968.     function getCurrentRecordNumberEnd()
  969.     {
  970.         if (is_null($this->rowLimit)) {
  971.             return $this->getRecordCount();
  972.         else {
  973.             return
  974.                 min($this->getCurrentRecordNumberStart($this->rowLimit - 1,
  975.                     $this->getRecordCount());
  976.         }
  977.     }
  978.  
  979.     /**
  980.      * Set the global GET/POST variables prefix
  981.      * 
  982.      * If you need to change the request variables, you can define a prefix.
  983.      * This is extra useful when using multiple datagrids.
  984.      * 
  985.      * This methods need to be called before bind().
  986.      *
  987.      * @access  public
  988.      * @param   string $prefix      The prefix to use on request variables;
  989.      */
  990.     function setRequestPrefix($prefix)
  991.     {
  992.         $this->_requestPrefix $prefix;
  993.         $this->_parseHttpRequest();
  994.  
  995.         if (isset($this->_renderer)) {
  996.  
  997.             $this->_renderer->setRequestPrefix($prefix);
  998.  
  999.             /* We just called parseHttpRequest() using a new requestPrefix.
  1000.              * The page and sort request might have changed, so we need
  1001.              * to pass them again to the renderer */
  1002.             $this->_renderer->setLimit($this->page$this->rowLimit
  1003.                                        $this->getRecordCount());
  1004.             $this->_setRendererCurrentSorting();
  1005.         }
  1006.     }
  1007.  
  1008.     /**
  1009.      * Add a column, with optional position
  1010.      *
  1011.      * @example addColumn.php       Adding a simple column
  1012.      * @access  public
  1013.      * @param   object  $column     The Structures_DataGrid_Column object
  1014.      *                               (reference to)
  1015.      * @param   string  $position   One of: "last", "first", "after" or "before"
  1016.      *                               (default: "last")
  1017.      * @param   string  $relativeTo The name (label) or field name of the
  1018.      *                               relative column, if $position is "after"
  1019.      *                               or "before"
  1020.      * @return  mixed               PEAR_Error on failure, void otherwise
  1021.      */
  1022.     function addColumn(&$column$position 'last'$relativeTo = null)
  1023.     {
  1024.         if (!is_a($column'structures_datagrid_column')) {
  1025.             return PEAR::raiseError(__FUNCTION__ . "(): not a valid ".
  1026.                                     " Structures_DataGrid_Column object");
  1027.         else {
  1028.             switch ($position{
  1029.                 case 'first':
  1030.                     array_unshift($this->columnSet'');
  1031.                     $this->columnSet[0=$column;
  1032.                     break;
  1033.                 case 'last':    
  1034.                     $this->columnSet[=$column;
  1035.                     break;
  1036.                 case 'after':
  1037.                 case 'before':
  1038.                     $this->_createDefaultColumns();
  1039.                     // Has a relative column been specified ?
  1040.                     if (is_null($relativeTo)) {
  1041.                         return PEAR::raiseError(
  1042.                                 __FUNCTION__ . "(): a relative column must be".
  1043.                                 "specified when using position \"$position\"");
  1044.                     }
  1045.                     // Yes, does it exist ?
  1046.                     foreach ($this->columnSet as $i => $relColumn{
  1047.                         if ($relColumn->columnName == $relativeTo 
  1048.                                 || $relColumn->fieldName == $relativeTo{
  1049.                             $relIndex $i;
  1050.                         }
  1051.                     }
  1052.                     // If it does not exist, return an error
  1053.                     if (!isset($relIndex)) {
  1054.                         return PEAR::raiseError(
  1055.                                 __FUNCTION__ . "(): Can't find a relative ".
  1056.                                 "column which name or field name is: ".
  1057.                                 $relativeTo);
  1058.                     }
  1059.                     // It exists, add the column after or before it
  1060.                     if ($position == 'after'{
  1061.                         array_splice($this->columnSet$relIndex + 1,  0'');
  1062.                         $this->columnSet[$relIndex + 1=$column;
  1063.                     else {
  1064.                         array_splice($this->columnSet$relIndex,  0'');
  1065.                         $this->columnSet[$relIndex=$column;
  1066.                     }
  1067.                     break;
  1068.             }
  1069.         
  1070.     }
  1071.  
  1072.     /**
  1073.      * Return the current columns
  1074.      *
  1075.      * @return  array   Structures_DataGrid_Column objects (references to).
  1076.      *                   This is a numerically indexed array (starting from 0).
  1077.      * @access  public
  1078.      */
  1079.     function getColumns()
  1080.     {
  1081.         $this->_createDefaultColumns();
  1082.  
  1083.         // Cloning the column set to prevent users from playing with our
  1084.         // internal $columnSet property.
  1085.         $columnSetClone = array();
  1086.  
  1087.         $columnCount $this->getColumnCount();
  1088.         for ($i = 0; $i $columnCount$i++{
  1089.             $columnSetClone[$i=$this->columnSet[$i];
  1090.         }
  1091.  
  1092.         return $columnSetClone;
  1093.     }
  1094.  
  1095.     /**
  1096.      * Find a column by name (label)
  1097.      *
  1098.      * @access  public
  1099.      * @param   string   $name      The name (label) of the column to look for
  1100.      * @return  object              Either the column object (reference to) or
  1101.      *                               false if there is no such column
  1102.      */
  1103.     function &getColumnByName($name)
  1104.     {
  1105.         $this->_createDefaultColumns();
  1106.         foreach ($this->columnSet as $key => $column{
  1107.             if ($column->columnName === $name{
  1108.                 return $this->columnSet[$key];
  1109.             }
  1110.         }
  1111.         $ret = false;
  1112.         return $ret;
  1113.     }
  1114.  
  1115.     /**
  1116.      * Find a column by field name
  1117.      *
  1118.      * @access  public
  1119.      * @param   string   $fieldName The field name of the column to look for
  1120.      * @return  object              Either the column object (reference to) or
  1121.      *                               false if there is no such column
  1122.      */
  1123.     function &getColumnByField($fieldName)
  1124.     {
  1125.         $this->_createDefaultColumns();
  1126.         foreach ($this->columnSet as $key => $column{
  1127.             if ($column->fieldName === $fieldName{
  1128.                 return $this->columnSet[$key];
  1129.             }
  1130.         }
  1131.         $ret = false;
  1132.         return $ret;
  1133.     }
  1134.  
  1135.     /**
  1136.      * Remove a column
  1137.      *
  1138.      * @example removeColumn.php    Remove an unneeded column
  1139.      * @access  public
  1140.      * @param   object  $column     The Structures_DataGrid_Column object
  1141.      *                               (reference to)
  1142.      * @return  void 
  1143.      */
  1144.     function removeColumn(&$column)
  1145.     {
  1146.         $columnCount count($this->columnSet);
  1147.         for ($i = 0; $i $columnCount$i++{
  1148.             if ($this->columnSet[$i]->id == $column->id{
  1149.                 for ($i++; $i $columnCount$i++{
  1150.                     $this->columnSet[$i - 1=$this->columnSet[$i];
  1151.                 }
  1152.                 array_pop($this->columnSet);
  1153.             }
  1154.         }
  1155.     }
  1156.  
  1157.     /**
  1158.      * A simple way to add a record set to the datagrid
  1159.      *
  1160.      * @example bind-dataobject.php Bind a DB_DataObject
  1161.      * @example bind-sql.php        Bind an SQL query
  1162.      * @access  public
  1163.      * @param   mixed   $container  The record set in any of the supported data
  1164.      *                               source types
  1165.      * @param   array   $options    Optional. The options to be used for the
  1166.      *                               data source
  1167.      * @param   string  $type       Optional. The data source type
  1168.      * @return  bool                True if successful, otherwise PEAR_Error.
  1169.      */
  1170.     function bind($container$options = array()$type = null)
  1171.     {
  1172.         $source =Structures_DataGrid::dataSourceFactory($container$options,
  1173.                                                           $type);
  1174.         if (!PEAR::isError($source)) {
  1175.             return $this->bindDataSource($source);
  1176.         else {
  1177.             return $source;
  1178.         }
  1179.     }
  1180.  
  1181.     /**
  1182.      * Bind an already instantiated DataSource driver
  1183.      *
  1184.      * @access  public
  1185.      * @param   mixed   &$source    The data source driver object
  1186.      * @return  mixed               True if successful, otherwise PEAR_Error
  1187.      */
  1188.     function bindDataSource(&$source)
  1189.     {
  1190.         if (is_subclass_of($source'structures_datagrid_datasource')) {
  1191.             $this->_dataSource =$source;
  1192.             $result $this->fetchDataSource();
  1193.             if (PEAR::isError($result)) {
  1194.                 return $result;
  1195.             }
  1196.             if ($columnSet $this->_dataSource->getColumns()) {
  1197.                 $this->columnSet array_merge($this->columnSet$columnSet);
  1198.             }
  1199.         else {
  1200.             return PEAR::raiseError('Invalid data source type, ' 
  1201.                                     'must be a valid data source driver class');
  1202.         }
  1203.  
  1204.         return true;
  1205.     }
  1206.  
  1207.     /**
  1208.      * Request the datasource to sort its data
  1209.      *
  1210.      * @return void 
  1211.      * @access private
  1212.      */
  1213.     function _sortDataSource()
  1214.     {
  1215.         if (!empty($this->sortSpec)) {
  1216.             if ($this->_dataSource->hasFeature('multiSort')) {
  1217.                 $this->_dataSource->sort($this->sortSpec);
  1218.             else {
  1219.                 reset($this->sortSpec);
  1220.                 list($sortBy$directioneach($this->sortSpec);
  1221.                 $this->_dataSource->sort($sortBy$direction);
  1222.             }
  1223.         }
  1224.     }
  1225.     
  1226.     /**
  1227.      * Fetch data from the datasource
  1228.      *
  1229.      * @param  integer  $startRow  Start fetching from the specified row number
  1230.      *                              (optional)
  1231.      * @return mixed Either true or a PEAR_Error object
  1232.      * @access private
  1233.      */
  1234.     function fetchDataSource($startRow = null)
  1235.     {
  1236.         if (isset($this->_dataSource)) {
  1237.             // Sort the data
  1238.             if (empty($this->sortSpecand $this->defaultSortSpec{
  1239.                 $this->sortSpec $this->defaultSortSpec;
  1240.             }
  1241.  
  1242.             $this->_sortDataSource();
  1243.  
  1244.             // is streaming enabled or not?
  1245.             if (is_null($this->_bufferSize)) {
  1246.                 // sometimes we have to fix the page number:
  1247.                 // if we have a row limit, a page number lower than 1, or greater
  1248.                 // than 1 and the real page count is lower than the given page
  1249.                 // number indicates, the page number will be set to 1
  1250.                 if (!is_null($this->rowLimit&& ($this->page < 1 ||
  1251.                     ($this->page > 1 && $this->getPageCount($this->page))
  1252.                    {
  1253.                     $this->page = 1;
  1254.                 }
  1255.  
  1256.                 // Determine page
  1257.                 $page $this->page $this->page - 1 : 0;
  1258.  
  1259.                 // Fetch the data
  1260.                 $recordSet $this->_dataSource->fetch(
  1261.                                 ($page $this->rowLimit),
  1262.                                 $this->rowLimit);
  1263.             else {
  1264.                 $limit $this->_bufferSize;
  1265.                 if (!is_null($this->rowLimit&& $limit $this->rowLimit{
  1266.                     $limit $this->rowLimit;
  1267.                 }
  1268.  
  1269.                 // Fetch the data
  1270.                 $recordSet $this->_dataSource->fetch($startRow$limit);
  1271.             }
  1272.  
  1273.             if (PEAR::isError($recordSet)) {
  1274.                 return $recordSet;
  1275.             else {
  1276.                 $this->recordSet $recordSet;
  1277.                 return true;
  1278.             }
  1279.         else {
  1280.             return PEAR::raiseError("Cannot fetch data: no datasource driver loaded.");
  1281.         }
  1282.     }
  1283.  
  1284.     /**
  1285.      * Sorts the records by the defined field.
  1286.      *
  1287.      * Do not use this method if data is coming from a database as sorting
  1288.      * is much faster coming directly from the database itself.
  1289.      *
  1290.      * @access  public
  1291.      * @param   array   $sortSpec   Sorting specification
  1292.      *                               Structure: array(fieldName => direction, ...)
  1293.      * @param   string  $direction  Deprecated. Put the direction(s) into
  1294.      *                               $sortSpec
  1295.      * @return  void 
  1296.      */
  1297.     function sortRecordSet($sortSpec$direction 'ASC')
  1298.     {
  1299.         if (is_array($sortSpec)) {
  1300.             $this->sortSpec $sortSpec;
  1301.         else {
  1302.             $this->sortSpec = array($sortSpec => $direction);
  1303.         
  1304.  
  1305.         if (isset($this->_dataSource)) {
  1306.             $this->_sortDataSource();
  1307.         
  1308.     }
  1309.  
  1310.     /**
  1311.      * Set default sorting specification
  1312.      *
  1313.      * If there is no sorting query in the HTTP request, and if the
  1314.      * sortRecordSet() method is not called, then the specification
  1315.      * passed to setDefaultSort() will be used.
  1316.      * 
  1317.      * This is especially useful if you want the data to already be
  1318.      * sorted when a user first see the datagrid.
  1319.      * 
  1320.      * This method needs to be called before bind().
  1321.      *
  1322.      * @param array $sortSpec   Sorting specification
  1323.      *                           Structure: array(fieldName => direction, ...)
  1324.      * @return mixed Either true or a PEAR_Error object
  1325.      * @access public
  1326.      */
  1327.     function setDefaultSort($sortSpec)
  1328.     {
  1329.         if (!is_array($sortSpec)) {
  1330.             return PEAR::raiseError('Invalid parameter, array expected');
  1331.         }
  1332.         $this->defaultSortSpec $sortSpec;
  1333.         return true;
  1334.     }
  1335.    
  1336.     /**
  1337.      * Read an HTTP request argument
  1338.      *
  1339.      * This methods take the $_requestPrefix into account, and respect the
  1340.      * POST, GET, COOKIE read order.
  1341.      *
  1342.      * @param   string  $name   Argument name
  1343.      * @return  mixed           Argument value or null
  1344.      * @access  private
  1345.      */
  1346.     function _getRequestArgument($name)
  1347.     {
  1348.         $value = null;
  1349.         if (is_array($this->_mapperMatch)) {
  1350.             if (isset($this->_mapperMatch[$name])) {
  1351.                 return $this->_mapperMatch[$name];
  1352.             }
  1353.         }
  1354.  
  1355.         $prefix $this->_requestPrefix;
  1356.         if (isset($_REQUEST["$prefix$name"])) {
  1357.             if (isset($_POST["$prefix$name"])) {
  1358.                 $value $_POST["$prefix$name"];
  1359.             elseif (isset($_GET["$prefix$name"])) {
  1360.                 $value $_GET["$prefix$name"];
  1361.             elseif (isset($_COOKIE["$prefix$name"])) {
  1362.                 $value $_COOKIE["$prefix$name"];
  1363.             
  1364.         }
  1365.         return $value;
  1366.     }
  1367.   
  1368.     /**
  1369.      * Secure the sort direction string
  1370.      *
  1371.      * @param   string  $str    Direction string
  1372.      * @return  string          Either ASC or DESC
  1373.      * @access  private
  1374.      */
  1375.     function _secureDirection($str)
  1376.     {
  1377.         $str strtoupper($str);
  1378.         return ($str == 'ASC' or $str == 'DESC'$str 'ASC';
  1379.     }
  1380.  
  1381.     /**
  1382.      * Parse HTTP Request parameters
  1383.      * 
  1384.      * Determine page, sort and direction values
  1385.      * 
  1386.      * @access  private
  1387.      * @return  array      Associative array of parsed arguments, each of these
  1388.      *                      defaulting to null if not found.
  1389.      */
  1390.     function _parseHttpRequest()
  1391.     {
  1392.         //FIXME: with two grids on the same page, one grid with an empty prefix
  1393.         //and the other with a non-empty prefix, the first interfers with the 
  1394.         //second, because _parseHttpRequest() is called from the constructor
  1395.         //before setRequestPrefix(). 
  1396.         
  1397.         if (!$this->_forcePage{
  1398.             if (!($this->page $this->_getRequestArgument('page'))) {
  1399.                 $this->page = 1;
  1400.             }
  1401.             if (!is_numeric($this->page)) {
  1402.                 $this->page = 1;
  1403.             }
  1404.         
  1405.  
  1406.         if (($orderBy $this->_getRequestArgument('orderBy')) !== null{
  1407.             if (is_array($orderBy)) {
  1408.                 $direction $this->_getRequestArgument('direction');
  1409.                 $this->sortSpec = array();
  1410.                 foreach ($orderBy as $index => $field{
  1411.                     if (!empty($field)) {
  1412.                         $this->sortSpec[$field
  1413.                             $this->_secureDirection($direction[$index]);
  1414.                     }
  1415.                 
  1416.             else {
  1417.                 if (!($direction $this->_getRequestArgument('direction'))) {
  1418.                     $direction 'ASC';
  1419.                 }
  1420.                 $this->sortSpec 
  1421.                     array($orderBy => $this->_secureDirection($direction));
  1422.             }
  1423.         }
  1424.     }     
  1425.  
  1426.     /**
  1427.      * Detect datasource container type
  1428.      *
  1429.      * @param   mixed   $source     Some kind of source
  1430.      * @param   array   $options    Options passed to dataSourceFactory()
  1431.      * @return  string              The type constant of this source or null if
  1432.      *                               it couldn't be detected
  1433.      * @access  private
  1434.      * @todo    Add CSV detector.  Possible rewrite in IFs to allow for
  1435.      *           hierarchy for seperating file handle sources from others
  1436.      */
  1437.     function _detectSourceType($source$options = array())
  1438.     {
  1439.         switch(true{
  1440.             // DB_DataObject
  1441.             case is_object($source&& is_subclass_of($source'db_dataobject'):
  1442.                 return DATAGRID_SOURCE_DATAOBJECT;
  1443.  
  1444.             // DB_Result
  1445.             case strtolower(get_class($source)) == 'db_result':
  1446.                 return DATAGRID_SOURCE_DB;
  1447.                 
  1448.             // Array
  1449.             case is_array($source):
  1450.                 return DATAGRID_SOURCE_ARRAY;
  1451.  
  1452.             // RSS
  1453.             case is_string($source&& stristr('<rss'$source):
  1454.             case is_string($source&& stristr('<rdf:RDF'$source):
  1455.             case is_string($source&& strpos($source'.rss'!== false:
  1456.                 return DATAGRID_SOURCE_RSS;
  1457.  
  1458.             // XML
  1459.             case is_string($source&& preg_match('#^ *<\?xml#'$source=== 1:
  1460.                 return DATAGRID_SOURCE_XML;
  1461.             
  1462.             // SQL query based drivers
  1463.             case is_string($source&& 
  1464.                 preg_match('#SELECT\s.+\sFROM#is'$source=== 1:
  1465.                 if (array_key_exists('dbc'$options)) {
  1466.                     switch (true{
  1467.                         case is_subclass_of($options['dbc']'db_common'):
  1468.                             return 'DBQuery';
  1469.                         case is_subclass_of($options['dbc']'PDO'):
  1470.                             return 'PDO';
  1471.                     }
  1472.                 }
  1473.                 return 'MDB2';  // default driver for SQL queries
  1474.  
  1475.             // DB_Table
  1476.             case is_object($source&& is_subclass_of($source'db_table'):
  1477.                 return DATAGRID_SOURCE_DBTABLE;
  1478.  
  1479.             default:
  1480.                 return null;
  1481.         }
  1482.     }
  1483.  
  1484.     /**
  1485.      * Detect rendering container type
  1486.      * 
  1487.      * @param object $container The rendering container
  1488.      * @return string           The container type or null if unrecognized
  1489.      * @access private
  1490.      */
  1491.     function _detectRendererType(&$container)
  1492.     {
  1493.         foreach ($this->_rendererTypes as $class => $type{
  1494.             if (is_a($container$class)) {
  1495.                 return $type;
  1496.             }
  1497.         }
  1498.  
  1499.         return null;
  1500.     }
  1501.  
  1502.     /**
  1503.      * Correct the (file)name of a driver
  1504.      * 
  1505.      * @param string    $name    The name of the driver
  1506.      * @param string    $type    The type of the driver
  1507.      * @return mixed             Either true or a PEAR_Error object
  1508.      * @access private
  1509.      */
  1510.     function _correctDriverName($name$type)
  1511.     {
  1512.         $driverNameMap = array(
  1513.             'DataSource' => array(
  1514.                 'DBDataObject' => 'DataObject',
  1515.                 'XLS' => 'Excel'
  1516.             ),
  1517.             'Renderer' => array(
  1518.                 'ConsoleTable' => 'Console',
  1519.                 'Excel' => 'XLS'
  1520.             )
  1521.         );
  1522.  
  1523.         // replace underscores (e.g. HTML_Table driver has filename HTMLTable.php)
  1524.         $name str_replace('_'''$name);
  1525.  
  1526.         // does the file exist?
  1527.         if (Structures_DataGrid::fileExists("Structures/DataGrid/$type/$name.php")) {
  1528.             return $name;
  1529.         }
  1530.  
  1531.         // check, whether a name mapping exists (e.g. from 'Excel' to 'XLS')
  1532.         if (isset($driverNameMap[$type][$name])) {
  1533.             return $driverNameMap[$type][$name];
  1534.         }
  1535.  
  1536.         // we could not find a valid driver name => return an error
  1537.         $error = PEAR::raiseError("Unknown $type driver. Please specify an " .
  1538.                                   'existing driver.');
  1539.         return $error;
  1540.     }
  1541.  
  1542.     /**
  1543.      * Build the datagrid
  1544.      * 
  1545.      * @return mixed Either true or a PEAR_Error object
  1546.      * @access public
  1547.      */
  1548.     function build()
  1549.     {
  1550.         if (isset($this->_dataSource)) {
  1551.             isset($this->_rendereror $this->setRenderer(DATAGRID_RENDER_DEFAULT);
  1552.             // is streaming enabled or not?
  1553.             if (is_null($this->_bufferSize)) {
  1554.                 $this->_prepareColumnsAndRenderer();
  1555.                 $result $this->_renderer->build($this->recordSet0true);
  1556.                 if (PEAR::isError($result)) {
  1557.                     return $result;
  1558.                 }
  1559.             else {
  1560.                 $recordCount $this->_dataSource->count();
  1561.                 for ($row ($this->page - 1$this->rowLimit$initial = true;
  1562.                      (   is_null($this->rowLimit)
  1563.                       || $row $this->page $this->rowLimit
  1564.                      )
  1565.                      && $row $recordCount;
  1566.                      $row += $this->_bufferSize$initial = false
  1567.                     {
  1568.  
  1569.                     if ($initial
  1570.                         // prepare columns and renderer only on first iteration
  1571.                         $this->_prepareColumnsAndRenderer();
  1572.                     else {
  1573.                         // we don't fetch on the first iteration because a chunk
  1574.                         // of data has already been fetched by bindDataSource()
  1575.                         if (PEAR::isError($result $this->fetchDataSource($row))) {
  1576.                             unset($this->_dataSource);
  1577.                             return $result;
  1578.                         }
  1579.                     }
  1580.  
  1581.                     if (   (   is_null($this->rowLimit)
  1582.                             || $row $this->_bufferSize $this->page $this->rowLimit
  1583.                            )
  1584.                         && $row $this->_bufferSize $recordCount
  1585.                        {
  1586.                         $eof = false;
  1587.                     else {
  1588.                         $eof = true;
  1589.                     }
  1590.                     $startRow $row ($this->page - 1$this->rowLimit;
  1591.                     $result $this->_renderer->build($this->recordSet,
  1592.                                                       $startRow$eof);
  1593.                     if (PEAR::isError($result)) {
  1594.                         return $result;
  1595.                     }
  1596.                 }
  1597.             }
  1598.             return true;
  1599.         else {
  1600.             return PEAR::raiseError('Cannot build the datagrid: ' .
  1601.                                     'no datasource driver loaded');
  1602.         }
  1603.     }
  1604.     
  1605.     /**
  1606.      * Prepare columns and renderer for building
  1607.      * 
  1608.      * @return void 
  1609.      * @access private
  1610.      */
  1611.     function _prepareColumnsAndRenderer()
  1612.     {
  1613.         $this->_createDefaultColumns();
  1614.  
  1615.         if (isset($this->_renderer)) {
  1616.             $this->_renderer->setStreaming(!is_null($this->_bufferSize));
  1617.             $this->_renderer->setColumns($this->columnSet);
  1618.             $this->_renderer->setLimit($this->page$this->rowLimit
  1619.                                        $this->getRecordCount());
  1620.             if ($this->sortSpec{
  1621.                 $this->_setRendererCurrentSorting();
  1622.             }
  1623.         }
  1624.     }
  1625.  
  1626.     /**
  1627.      * Provide some BC fix (requires PHP5)
  1628.      * 
  1629.      * This is a PHP5 magic method used to simulate the old public
  1630.      * $renderer property
  1631.      * @access private
  1632.      */
  1633.     function __get($var)
  1634.     {
  1635.         if ($var == 'renderer'{
  1636.             isset($this->_rendereror $this->setRenderer(DATAGRID_RENDER_DEFAULT);
  1637.             return $this->_renderer;
  1638.         }
  1639.     }
  1640.  
  1641.     /**
  1642.      * Set a single renderer option
  1643.      *
  1644.      * @param   string  $name   Option name
  1645.      * @param   mixed   $value  Option value
  1646.      * @param   bool    $common Whether to use this option for all
  1647.      *                           renderers (true) or only for the current
  1648.      *                           one (false)
  1649.      * @access  public
  1650.      */
  1651.     function setRendererOption($name$value$common = false)
  1652.     {
  1653.         $this->setRendererOptions(array($name => $value)$common);
  1654.     }
  1655.  
  1656.     /**
  1657.      * Set multiple renderer options
  1658.      *
  1659.      * @param   array   $options    An associative array of the form:
  1660.      *                               array("option_name" => "option_value",...)
  1661.      * @param   bool    $common     Whether to use these options for all
  1662.      *                               renderers (true) or only for the current
  1663.      *                               one (false)
  1664.      * @access  public
  1665.      */
  1666.     function setRendererOptions($options$common = false)
  1667.     {
  1668.         if ($common{
  1669.             $this->_rendererCommonOptions 
  1670.                 = array_merge($this->_rendererCommonOptions(array)$options);
  1671.  
  1672.             // There is no need to load the default renderer if these are common 
  1673.             // options. rendererFactory() will set them up.
  1674.             isset($this->_rendererand $this->_renderer->setOptions((array)$options);
  1675.         else {
  1676.             isset($this->_rendereror $this->setRenderer(DATAGRID_RENDER_DEFAULT);
  1677.             $this->_renderer->setOptions((array)$options);
  1678.         }
  1679.     }
  1680.  
  1681.     /**
  1682.      * Set a single datasource option
  1683.      *
  1684.      * @param   string  $name       Option name
  1685.      * @param   mixed   $value      Option value
  1686.      * @access  public
  1687.      */
  1688.     function setDataSourceOption($name$value)
  1689.     {
  1690.         return $this->setDataSourceOptions(array($name => $value));
  1691.     }
  1692.  
  1693.     /**
  1694.      * Set multiple datasource options
  1695.      *
  1696.      * @param   array   $options    An associative array of the form:
  1697.      *                               array("option_name" => "option_value",...)
  1698.      * @access  public
  1699.      */
  1700.     function setDataSourceOptions($options)
  1701.     {
  1702.         if (isset($this->_dataSource)) {
  1703.             $this->_dataSource->setOptions((array)$options);
  1704.         else {
  1705.             return PEAR::raiseError('Unable to set options; no datasource loaded.');
  1706.         }
  1707.     }
  1708.  
  1709.     /**
  1710.      * Enable streaming support for reading from DataSources and writing with
  1711.      * Renderers and set the buffer size (number of records)
  1712.      *
  1713.      * @param   integer  $bufferSize  Number of records that should be buffered
  1714.      * @access  public
  1715.      */
  1716.     function enableStreaming($bufferSize = 500)
  1717.     {
  1718.         $this->_bufferSize $bufferSize;
  1719.     }
  1720.  
  1721.     /**
  1722.      * Generate columns from a fields list
  1723.      *
  1724.      * This is a shortcut for adding simple columns easily, instead of creating
  1725.      * them manually and calling addColumn() for each.
  1726.      *
  1727.      * The generated columns are appended to the current column set.
  1728.      *
  1729.      * @param   array   $fields Fields and labels.
  1730.      *                           Array of the form: array(field => label, ...)
  1731.      *                           The default is an empty array, which means:
  1732.      *                           all fields fetched from the datasource
  1733.      *
  1734.      * @return  void 
  1735.      * @access  public
  1736.      */
  1737.     function generateColumns($fields = array())
  1738.     {
  1739.         if (empty($fields)) {
  1740.             if (!empty($this->recordSet)) {
  1741.                 foreach ($this->recordSet[0as $key => $data{
  1742.                     $fields[$key$key;
  1743.                 }
  1744.             }
  1745.         }
  1746.  
  1747.         foreach ($fields as $field => $label{
  1748.             $column = new Structures_DataGrid_Column($label$field$field);
  1749.             $this->addColumn($column);
  1750.             unset($column);
  1751.         }
  1752.     }
  1753.  
  1754.     /**
  1755.      * Enable and configure URL mapping
  1756.      *
  1757.      * If this is set, it will be parsed instead of GET/POST.
  1758.      * This is only supported on PHP5, as it depends on
  1759.      * Net_URL_Mapper.
  1760.      * 
  1761.      * There are three possible placeholders, :pager, :orderBy and :direction.
  1762.      * :page or (:orderBy and :direction) can be used alone.
  1763.      * 
  1764.      * It is possible to use multipe DataGrid instances on one page with
  1765.      * different prefixes.
  1766.      * 
  1767.      * Instead of a format string you might also pass a Net_URL_Mapper instance
  1768.      * to this method, in which case $prefix and $scriptname will be ignored.
  1769.      * This instance must be properly set up, connected to url patterns, etc...
  1770.      * This is especially useful when you've already configured URL mapping
  1771.      * globally for your application and want Structures_DataGrid to integrate.
  1772.      *
  1773.      * @example urlFormat.php    configure a url format
  1774.      *
  1775.      * @param mixed  $format     The URL format string or a Net_URL_Mapper instance
  1776.      * @param string $prefix     Sets the url prefix
  1777.      * @param string $scriptname Set the scriptname if mod_rewrite not available
  1778.      * 
  1779.      * @return void 
  1780.      * @access public
  1781.      * @throws Net_URL_Mapper_InvalidException
  1782.      * 
  1783.      * @see http://pear.php.net/Net_URL_Mapper
  1784.      */
  1785.     function setUrlFormat($format$prefix = null$scriptname = null)
  1786.     {
  1787.         if (is_string($format)) {
  1788.             if (!Structures_DataGrid::fileExists('Net/URL/Mapper.php')) {
  1789.                 return PEAR::raiseError('Net_URL_Mapper Package is missing');
  1790.             }
  1791.             include_once 'Net/URL/Mapper.php';
  1792.         }
  1793.         
  1794.         // reset parsed Params and reparse the request
  1795.         $this->_mapperMatch = null;
  1796.         
  1797.         
  1798.         // only call _parseHttpRequest again if the URL matches
  1799.         if ($this->_parseRequestWithMapper($format$prefix$scriptname)) {
  1800.             $this->_parseHttpRequest();
  1801.  
  1802.             // copied from setRequestPrefix
  1803.             // perhabs, this part can be moved to _parseHttpRequest
  1804.             if (isset($this->_renderer)) {
  1805.                 /* The page and sort request might have changed, so we need
  1806.                  * to pass them again to the renderer */
  1807.                 $this->_renderer->setLimit($this->page$this->rowLimit
  1808.                                            $this->getRecordCount());
  1809.                 $this->_setRendererCurrentSorting();
  1810.             }
  1811.         }
  1812.     }
  1813.     
  1814.     /**
  1815.      * Tries to parse the request with
  1816.      * Net_URL_Mapper.
  1817.      *
  1818.      * @param mixed  $format     The URL format string or a Net_URL_Mapper instance
  1819.      * @param string $prefix     Set the url prefix
  1820.      * @param string $scriptname Set the scriptname if mod_rewrite not available
  1821.      * 
  1822.      * @return void 
  1823.      * @access private
  1824.      * @throws Net_URL_Mapper_InvalidException
  1825.      * 
  1826.      */
  1827.     function _parseRequestWithMapper($format$prefix = null$scriptname = null)
  1828.     {
  1829.         if (is_a($format'Net_URL_Mapper')) {
  1830.             $this->_urlMapper = $format;
  1831.         else {
  1832.             // Use a special instance, so that it is usable for multipe 
  1833.             // SDG instances and other NUM Instances
  1834.             $this->_urlMapper = Net_URL_Mapper::getInstance('__SDG_Instance_' $prefix);
  1835.             
  1836.             // If the prefix is not null, set it
  1837.             if (!is_null($prefix)) {
  1838.                 $this->_urlMapper->setPrefix($prefix);
  1839.             }
  1840.             
  1841.             //if the scriptname is not null, set it
  1842.             if (!is_null($scriptname)) {
  1843.                 $this->_urlMapper->setScriptName($scriptname);
  1844.             }
  1845.             
  1846.             // "connect" the format wit defaults and the defined rules
  1847.             $this->_urlMapper->connect($format$this->_mapperDefaults$this->_mapperRules);
  1848.         }
  1849.             
  1850.         // run NUM, if it returns an array, the url was successfully matched,
  1851.         // return true
  1852.         if ($this->_mapperMatch $this->_urlMapper->match($_SERVER['REQUEST_URI'])) {
  1853.             return true;
  1854.         }
  1855.         return false;
  1856.     }
  1857. }
  1858.  
  1859. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  1860. ?>

Documentation generated on Tue, 18 Dec 2007 11:30:11 -0500 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.