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

Source for file DataSource.php

Documentation is available at DataSource.php

  1. <?php
  2. /**
  3.  * Base abstract class for data source drivers
  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.  * All rights reserved.
  13.  *
  14.  * Redistribution and use in source and binary forms, with or without
  15.  * modification, are permitted provided that the following conditions
  16.  * are met:
  17.  *
  18.  *    * Redistributions of source code must retain the above copyright
  19.  *      notice, this list of conditions and the following disclaimer.
  20.  *    * Redistributions in binary form must reproduce the above copyright
  21.  *      notice, this list of conditions and the following disclaimer in the
  22.  *      documentation and/or other materials provided with the distribution.
  23.  *    * The names of the authors may not be used to endorse or promote products
  24.  *      derived from this software without specific prior written permission.
  25.  *
  26.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  27.  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  28.  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  29.  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  30.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  31.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  32.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  33.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  34.  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  35.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  36.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  37.  *
  38.  * CVS file id: $Id: DataSource.php,v 1.43 2007/11/01 11:13:16 olivierg Exp $
  39.  * 
  40.  * @version  $Revision: 1.43 $
  41.  * @package  Structures_DataGrid
  42.  * @category Structures
  43.  * @license  http://opensource.org/licenses/bsd-license.php New BSD License
  44.  */
  45.  
  46. /**
  47.  * Base abstract class for DataSource drivers
  48.  * 
  49.  * SUPPORTED OPTIONS:
  50.  *
  51.  * - fields:            (array) Which data fields to fetch from the datasource.
  52.  *                              An empty array means: all fields.
  53.  *                              Form: array(field1, field2, ...)
  54.  * - primaryKey:        (array) Name(s), or numerical index(es) of the
  55.  *                              field(s) which contain a unique record
  56.  *                              identifier (only use several fields in case
  57.  *                              of a multiple-fields primary key)
  58.  * - generate_columns:  (bool)  Generate Structures_DataGrid_Column objects
  59.  *                              with labels. See the 'labels' option.
  60.  *                              DEPRECATED:
  61.  *                              use Structures_DataGrid::generateColumns() instead
  62.  * - labels:            (array) Data field to column label mapping. Only used
  63.  *                              when 'generate_columns' is true.
  64.  *                              Form: array(field => label, ...)
  65.  *                              DEPRECATED:
  66.  *                              use Structures_DataGrid::generateColumns() instead
  67.  *
  68.  * @author   Olivier Guilyardi <olivier@samalyse.com>
  69.  * @author   Andrew Nagy <asnagy@webitecture.org>
  70.  * @author   Mark Wiesemann <wiesemann@php.net>
  71.  * @package  Structures_DataGrid
  72.  * @category Structures
  73.  * @version  $Revision: 1.43 $
  74.  */
  75. {
  76.     /**
  77.      * Common and driver-specific options
  78.      *
  79.      * @var array 
  80.      * @access protected
  81.      * @see Structures_DataGrid_DataSource::_setOption()
  82.      * @see Structures_DataGrid_DataSource::addDefaultOptions()
  83.      */
  84.     var $_options = array();
  85.  
  86.     /**
  87.      * Special driver features
  88.      *
  89.      * @var array 
  90.      * @access protected
  91.      */
  92.     var $_features = array();
  93.  
  94.     /**
  95.      * Constructor
  96.      *
  97.      */
  98.     function Structures_DataGrid_DataSource()
  99.     {
  100.         $this->_options = array('generate_columns' => false,
  101.                                 'labels'           => array(),
  102.                                 'fields'           => array(),
  103.                                 'primaryKey'       => null,
  104.                                );
  105.  
  106.         $this->_features = array(
  107.                 'multiSort' => false// Multiple field sorting
  108.                 'writeMode' => false// insert, update and delete records
  109.         );
  110.     }
  111.  
  112.     /**
  113.      * Adds some default options.
  114.      *
  115.      * This method is meant to be called by drivers. It allows adding some
  116.      * default options.
  117.      *
  118.      * @access protected
  119.      * @param array $options An associative array of the form:
  120.      *                        array(optionName => optionValue, ...)
  121.      * @return void 
  122.      * @see Structures_DataGrid_DataSource::_setOption
  123.      */
  124.     function _addDefaultOptions($options)
  125.     {
  126.         $this->_options = array_merge($this->_options$options);
  127.     }
  128.  
  129.     /**
  130.      * Add special driver features
  131.      *
  132.      * This method is meant to be called by drivers. It allows specifying
  133.      * the special features that are supported by the current driver.
  134.      *
  135.      * @access protected
  136.      * @param array $features An associative array of the form:
  137.      *                         array(feature => true|false, ...)
  138.      * @return void 
  139.      */
  140.     function _setFeatures($features)
  141.     {
  142.         $this->_features = array_merge($this->_features$features);
  143.     }
  144.     
  145.     /**
  146.      * Set options
  147.      *
  148.      * @param   mixed   $options    An associative array of the form:
  149.      *                               array("option_name" => "option_value",...)
  150.      * @access  protected
  151.      */
  152.     function setOptions($options)
  153.     {
  154.         $this->_options = array_merge($this->_options$options);
  155.     }
  156.  
  157.     /**
  158.      * Set a single option
  159.      *
  160.      * @param   string  $name       Option name
  161.      * @param   mixed   $value      Option value
  162.      * @access  public
  163.      */
  164.     function setOption($name$value)
  165.     {
  166.         $this->_options[$name$value;
  167.     }
  168.  
  169.     /**
  170.      * Generate columns if options are properly set
  171.      *
  172.      * Note: must be called after fetch()
  173.      * 
  174.      * @access public
  175.      * @return array Array of Column objects. Empty array if irrelevant.
  176.      * @deprecated This method relates to the deprecated "generate_columns" option.
  177.      */
  178.     function getColumns()
  179.     {
  180.         $columns = array();
  181.         if ($this->_options['generate_columns'
  182.             and $fieldList $this->_options['fields']{
  183.             include_once 'Structures/DataGrid/Column.php';
  184.             
  185.             foreach ($fieldList as $field{
  186.                 $label strtr($field$this->_options['labels']);
  187.                 $col = new Structures_DataGrid_Column($label$field$field);
  188.                 $columns[$col;
  189.             }
  190.         }
  191.         
  192.         return $columns;
  193.     }
  194.     
  195.     
  196.     // Begin driver method prototypes DocBook template
  197.      
  198.     /**#@+
  199.      * 
  200.      * This method is public, but please note that it is not intended to be 
  201.      * called by user-space code. It is meant to be called by the main 
  202.      * Structures_DataGrid class.
  203.      *
  204.      * It is an abstract method, part of the DataGrid Datasource driver 
  205.      * interface, and must/may be overloaded by drivers.
  206.      */
  207.    
  208.     /**
  209.      * Fetching method prototype
  210.      *
  211.      * When overloaded this method must return an array of records.
  212.      * Each record can be either an associative array of field name/value
  213.      * pairs, or an object carrying fields as properties.
  214.      *
  215.      * This method must return a PEAR_Error object on failure.
  216.      *
  217.      * @abstract
  218.      * @param   integer $offset     Limit offset (starting from 0)
  219.      * @param   integer $len        Limit length
  220.      * @return  object              PEAR_Error with message
  221.      *                               "No data source driver loaded"
  222.      * @access  public
  223.      */
  224.     function &fetch($offset = 0$len = null)
  225.     {
  226.         return PEAR::raiseError("No data source driver loaded");
  227.     }
  228.  
  229.     /**
  230.      * Counting method prototype
  231.      *
  232.      * Note: must be called before fetch()
  233.      *
  234.      * When overloaded, this method must return the total number or records
  235.      * or a PEAR_Error object on failure
  236.      * 
  237.      * @abstract
  238.      * @return  object              PEAR_Error with message
  239.      *                               "No data source driver loaded"
  240.      * @access  public
  241.      */
  242.     function count()
  243.     {
  244.         return PEAR::raiseError("No data source driver loaded");
  245.     }
  246.     
  247.     /**
  248.      * Sorting method prototype
  249.      *
  250.      * When overloaded this method must return true on success or a PEAR_Error
  251.      * object on failure.
  252.      * 
  253.      * Note: must be called before fetch()
  254.      * 
  255.      * @abstract
  256.      * @param   string  $sortSpec   If the driver supports the "multiSort"
  257.      *                               feature this can be either a single field
  258.      *                               (string), or a sort specification array of
  259.      *                               the form: array(field => direction, ...)
  260.      *                               If "multiSort" is not supported, then this
  261.      *                               can only be a string.
  262.      * @param   string  $sortDir    Sort direction: 'ASC' or 'DESC'
  263.      * @return  object              PEAR_Error with message
  264.      *                               "No data source driver loaded"
  265.      * @access  public
  266.      */
  267.     function sort($sortSpec$sortDir = null)
  268.     {
  269.         return PEAR::raiseError("No data source driver loaded");
  270.     }    
  271.   
  272.     /**
  273.      * Datasource binding method prototype
  274.      *
  275.      * When overloaded this method must return true on success or a PEAR_Error
  276.      * object on failure.
  277.      *
  278.      * @abstract
  279.      * @param   mixed $container The datasource container
  280.      * @param   array $options   Binding options
  281.      * @return  object           PEAR_Error with message
  282.      *                            "No data source driver loaded"
  283.      * @access  public
  284.      */
  285.     function bind($container$options = array())
  286.     {
  287.         return PEAR::raiseError("No data source driver loaded");
  288.     }
  289.  
  290.     /**
  291.      * Record insertion method prototype
  292.      *
  293.      * Drivers that support the "writeMode" feature must implement this method.
  294.      *
  295.      * When overloaded this method must return true on success or a PEAR_Error
  296.      * object on failure.
  297.      *
  298.      * @abstract
  299.      * @param   array   $data   Associative array of the form:
  300.      *                           array(field => value, ..)
  301.      * @return  object          PEAR_Error with message
  302.      *                           "No data source driver loaded or write mode not
  303.      *                           supported by the current driver"
  304.      * @access  public
  305.      */
  306.     function insert($data)
  307.     {
  308.         return PEAR::raiseError("No data source driver loaded or write mode not"
  309.                                 "supported by the current driver");
  310.     }
  311.  
  312.     /**
  313.      * Return the primary key specification
  314.      *
  315.      * This method always returns an array containing:
  316.      * - either one field name or index in case of a single-field key
  317.      * - or several field names or indexes in case of a multiple-fields key
  318.      *
  319.      * Drivers that support the "writeMode" feature should overload this method
  320.      * if the key can be detected. However, the detection must not override the
  321.      * "primaryKey" option.
  322.      *
  323.      * @return  array       Field(s) name(s) or numerical index(es)
  324.      * @access  protected
  325.      */
  326.     function getPrimaryKey()
  327.     {
  328.         return $this->_options['primaryKey'];
  329.     }
  330.  
  331.     /**
  332.      * Record updating method prototype
  333.      *
  334.      * Drivers that support the "writeMode" feature must implement this method.
  335.      *
  336.      * When overloaded this method must return true on success or a PEAR_Error
  337.      * object on failure.
  338.      *
  339.      * @abstract
  340.      * @param   array   $key    Unique record identifier
  341.      * @param   array   $data   Associative array of the form:
  342.      *                           array(field => value, ..)
  343.      * @return  object          PEAR_Error with message
  344.      *                           "No data source driver loaded or write mode
  345.      *                           not supported by the current driver"
  346.      * @access  public
  347.      */
  348.     function update($key$data)
  349.     {
  350.         return PEAR::raiseError("No data source driver loaded or write mode not"
  351.                                 "supported by the current driver");
  352.     }
  353.  
  354.     /**
  355.      * Record deletion method prototype
  356.      *
  357.      * Drivers that support the "writeMode" feature must implement this method.
  358.      *
  359.      * When overloaded this method must return true on success or a PEAR_Error
  360.      * object on failure.
  361.      *
  362.      * @abstract
  363.      * @param   array   $key    Unique record identifier
  364.      * @return  object          PEAR_Error with message
  365.      *                           "No data source driver loaded or write mode
  366.      *                           not supported by the current driver"
  367.      * @access  public
  368.      */
  369.     function delete($key)
  370.     {
  371.         return PEAR::raiseError("No data source driver loaded or write mode not"
  372.                                 "supported by the current driver");
  373.     }
  374.  
  375.     /**
  376.      * Resources cleanup method prototype
  377.      *
  378.      * This is where drivers should close sql connections, files, etc...
  379.      * if needed.
  380.      *
  381.      * @abstract
  382.      * @return  void 
  383.      * @access  public
  384.      */
  385.     function free()
  386.     {
  387.     }
  388.  
  389.     /**#@-*/
  390.  
  391.     // End DocBook template
  392.   
  393.     /**
  394.      * List special driver features
  395.      *
  396.      * @return array Of the form: array(feature => true|false, etc...)
  397.      * @access public
  398.      */
  399.     function getFeatures()
  400.     {
  401.         return $this->_features;
  402.     }
  403.    
  404.     /**
  405.      * Tell if the driver as a specific feature
  406.      *
  407.      * @param  string $name Feature name
  408.      * @return bool 
  409.      * @access public
  410.      */
  411.     function hasFeature($name)
  412.     {
  413.         return $this->_features[$name];
  414.     }
  415.     
  416.     /**
  417.      * Dump the data as returned by fetch().
  418.      *
  419.      * This method is meant for debugging purposes. It returns what fetch()
  420.      * would return to its DataGrid host as a nicely formatted console-style
  421.      * table.
  422.      *
  423.      * @param   integer $offset     Limit offset (starting from 0)
  424.      * @param   integer $len        Limit length
  425.      * @param   string  $sortField  Field to sort by
  426.      * @param   string  $sortDir    Sort direction: 'ASC' or 'DESC'
  427.      * @return  string              The table string, ready to be printed
  428.      * @uses    Structures_DataGrid_DataSource::fetch()
  429.      * @access  public
  430.      */
  431.     function dump($offset=0$len=null$sortField=null$sortDir='ASC')
  432.     {
  433.         $records =$this->fetch($offset$len$sortField$sortDir);
  434.         $columns $this->getColumns();
  435.  
  436.         if (!$columns and !$records{
  437.             return "<Empty set>\n";
  438.         }
  439.         
  440.         include_once 'Console/Table.php';
  441.         $table = new Console_Table();
  442.         
  443.         $headers = array();
  444.         if ($columns{
  445.             foreach ($columns as $col{
  446.                 $headers[is_null($col->fieldName)
  447.                             ? $col->columnName
  448.                             : "{$col->columnName} ({$col->fieldName})";
  449.             }
  450.         else {
  451.             $headers array_keys($records[0]);
  452.         }
  453.  
  454.         $table->setHeaders($headers);
  455.         
  456.         foreach ($records as $rec{
  457.             $table->addRow($rec);
  458.         }
  459.        
  460.         return $table->getTable();
  461.     }
  462.  
  463. }
  464.  
  465. /**
  466.  * Base abstract class for SQL query based DataSource drivers
  467.  * 
  468.  * SUPPORTED OPTIONS:
  469.  *
  470.  * - db_options:  (array)  Options for the created database object. This option
  471.  *                         is only used when the 'dsn' option is given.
  472.  * - count_query: (string) Query that calculates the number of rows. See below
  473.  *                         for more information about when such a count query
  474.  *                         is needed.
  475.  *
  476.  * @author   Olivier Guilyardi <olivier@samalyse.com>
  477.  * @author   Mark Wiesemann <wiesemann@php.net>
  478.  * @package  Structures_DataGrid
  479.  * @category Structures
  480.  * @version  $Revision: 1.43 $
  481.  */
  482. {
  483.     /**
  484.      * SQL query
  485.      * @var string 
  486.      * @access protected
  487.      */
  488.     var $_query;
  489.  
  490.     /**
  491.      * Fields/directions to sort the data by
  492.      * @var array 
  493.      * @access protected
  494.      */
  495.     var $_sortSpec;
  496.  
  497.     /**
  498.      * Instantiated database object
  499.      * @var object 
  500.      * @access protected
  501.      */
  502.     var $_handle;
  503.  
  504.     /**
  505.      * Total number of rows
  506.      * 
  507.      * This property caches the result of count() to avoid running the same
  508.      * database query multiple times.
  509.      *
  510.      * @var int 
  511.      * @access private
  512.      */
  513.      var $_rowNum = null;
  514.  
  515.     /**
  516.      * Constructor
  517.      *
  518.      */
  519.     {
  520.         parent::Structures_DataGrid_DataSource();
  521.         $this->_addDefaultOptions(array('dbc' => null,
  522.                                         'dsn' => null,
  523.                                         'db_options'  => array(),
  524.                                         'count_query' => ''));
  525.         $this->_setFeatures(array('multiSort' => true));
  526.     }
  527.  
  528.     /**
  529.      * Bind
  530.      *
  531.      * @param   string    $query     The query string
  532.      * @param   mixed     $options   array('dbc' => [connection object])
  533.      *                                or
  534.      *                                array('dsn' => [dsn string])
  535.      * @access  public
  536.      * @return  mixed                True on success, PEAR_Error on failure
  537.      */
  538.     function bind($query$options = array())
  539.     {
  540.         if ($options{
  541.             $this->setOptions($options)
  542.         }
  543.  
  544.         if (isset($this->_options['dbc']&&
  545.             $this->_isConnection($this->_options['dbc'])) {
  546.             $this->_handle = &$this->_options['dbc'];
  547.         elseif (isset($this->_options['dsn'])) {
  548.             $dbOptions = array();
  549.             if (array_key_exists('db_options'$options)) {
  550.                 $dbOptions $options['db_options'];
  551.             }
  552.             $this->_handle =$this->_connect();
  553.             if (PEAR::isError($this->_handle)) {
  554.                 return PEAR::raiseError('Could not create connection: ' .
  555.                                         $this->_handle->getMessage(', ' .
  556.                                         $this->_handle->getUserInfo());
  557.             }
  558.         else {
  559.             return PEAR::raiseError('No Database object or dsn string specified');
  560.         }
  561.  
  562.         if (is_string($query)) {
  563.             $this->_query = $query;
  564.             return true;
  565.         else {
  566.             return PEAR::raiseError('Query parameter must be a string');
  567.         }
  568.     }
  569.  
  570.     /**
  571.      * Fetch
  572.      *
  573.      * @param   integer $offset     Offset (starting from 0)
  574.      * @param   integer $limit      Limit
  575.      * @access  public
  576.      * @return  mixed               The 2D Array of the records on success,
  577.      *                               PEAR_Error on failure
  578.      */
  579.     function &fetch($offset = 0$limit = null)
  580.     {
  581.         if (!empty($this->_sortSpec)) {
  582.             foreach ($this->_sortSpec as $field => $direction{
  583.                 $sortArray[$this->_quoteIdentifier($field' ' $direction;
  584.             }
  585.             $sortString join(', '$sortArray);
  586.         else {
  587.             $sortString '';
  588.         }
  589.  
  590.         $query $this->_query;
  591.  
  592.         // drop LIMIT statement
  593.         $query preg_replace('#\sLIMIT\s.*$#isD'' '$query);
  594.  
  595.         // if we have a sort string, we need to add it to the query string
  596.         if ($sortString != ''{
  597.             // if there is an existing ORDER BY statement, we can just add the
  598.             // sort string
  599.             $result preg_match('#ORDER\s+BY#is'$query);
  600.             if ($result === 1{
  601.                 $query .= ', ' $sortString;
  602.             else {  // otherwise we need to specify 'ORDER BY'
  603.                 $query .= ' ORDER BY ' $sortString;
  604.             }
  605.         }
  606.  
  607.         //FIXME: What about SQL injection ?
  608.         $recordSet $this->_getRecords($query$limit$offset);
  609.  
  610.         if (PEAR::isError($recordSet)) {
  611.             return $result;
  612.         }
  613.  
  614.         // Determine fields to render
  615.         if (!$this->_options['fields'&& count($recordSet)) {
  616.             $this->setOptions(array('fields' => array_keys($recordSet[0])));
  617.         }                
  618.  
  619.         return $recordSet;
  620.     }
  621.  
  622.     /**
  623.      * Count
  624.      *
  625.      * @access  public
  626.      * @return  mixed       The number or records (int),
  627.      *                       PEAR_Error on failure
  628.      */
  629.     function count()
  630.     {
  631.         // do we already have the cached number of records? (if yes, return it)
  632.         if (!is_null($this->_rowNum)) {
  633.             return $this->_rowNum;
  634.         }
  635.         // try to fetch the number of records
  636.         if ($this->_options['count_query'!= ''{
  637.             // complex queries might require special queries to get the
  638.             // right row count
  639.             $count $this->_getOne($this->_options['count_query']);
  640.             // $count has an integer value with number of rows or is a
  641.             // PEAR_Error instance on failure
  642.         }
  643.         elseif (preg_match('#GROUP\s+BY#is'$this->_query=== 1 ||
  644.                 preg_match('#SELECT.+SELECT#is'$this->_query=== 1 ||
  645.                 preg_match('#\sUNION\s#is'$this->_query=== 1 ||
  646.                 preg_match('#SELECT.+DISTINCT.+FROM#is'$this->_query=== 1
  647.             {
  648.             // GROUP BY, DISTINCT, UNION and subqueries are special cases
  649.             // ==> use the normal query and then numRows()
  650.             $count $this->_getRecordsNum($this->_query);
  651.             if (PEAR::isError($count)) {
  652.                 return $count;
  653.             }
  654.         else {
  655.             // don't query the whole table, just get the number of rows
  656.             $query preg_replace('#SELECT\s.+\sFROM#is',
  657.                                   'SELECT COUNT(*) FROM',
  658.                                   $this->_query);
  659.             $count $this->_getOne($query);
  660.             // $count has an integer value with number of rows or is a
  661.             // PEAR_Error instance on failure
  662.         }
  663.         // if we've got a number of records, save it to avoid running the same
  664.         // query multiple times
  665.         if (!PEAR::isError($count)) {
  666.             $this->_rowNum $count;
  667.         }
  668.         return $count;
  669.     }
  670.  
  671.     /**
  672.      * Disconnect from the database, if needed
  673.      *
  674.      * @abstract
  675.      * @return void 
  676.      * @access public
  677.      */
  678.     function free()
  679.     {
  680.         if ($this->_handle && is_null($this->_options['dbc'])) {
  681.             $this->_disconnect();
  682.             unset($this->_handle);
  683.         }
  684.     }
  685.  
  686.     /**
  687.      * This can only be called prior to the fetch method.
  688.      *
  689.      * @access  public
  690.      * @param   mixed   $sortSpec   A single field (string) to sort by, or a
  691.      *                               sort specification array of the form:
  692.      *                               array(field => direction, ...)
  693.      * @param   string  $sortDir    Sort direction: 'ASC' or 'DESC'
  694.      *                               This is ignored if $sortDesc is an array
  695.      */
  696.     function sort($sortSpec$sortDir 'ASC')
  697.     {
  698.         if (is_array($sortSpec)) {
  699.             $this->_sortSpec = $sortSpec;
  700.         else {
  701.             $this->_sortSpec[$sortSpec$sortDir;
  702.         }
  703.     }
  704.  
  705. }
  706.  
  707. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  708. ?>

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