Source for file DataGrid.php
Documentation is available at DataGrid.php
* Structures_DataGrid Class
* Copyright (c) 1997-2007, Andrew Nagy <asnagy@webitecture.org>,
* Olivier Guilyardi <olivier@samalyse.com>,
* Mark Wiesemann <wiesemann@php.net>
* Sascha Grossenbacher <saschagros@bluewin.ch>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The names of the authors may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* CSV file id: $Id: DataGrid.php,v 1.144 2007/11/05 16:17:15 olivierg Exp $
* @version $Revision: 1.144 $
* @package Structures_DataGrid
* @license http://opensource.org/licenses/bsd-license.php New BSD License
require_once 'Structures/DataGrid/Column.php';
define('DATAGRID_RENDER_TABLE', 'HTMLTable');
define('DATAGRID_RENDER_SMARTY', 'Smarty');
define('DATAGRID_RENDER_XML', 'XML');
define('DATAGRID_RENDER_XLS', 'XLS');
define('DATAGRID_RENDER_XUL', 'XUL');
define('DATAGRID_RENDER_CSV', 'CSV');
define('DATAGRID_RENDER_CONSOLE', 'Console');
define('DATAGRID_RENDER_PAGER', 'Pager');
define('DATAGRID_RENDER_SORTFORM', 'HTMLSortForm');
define('DATAGRID_RENDER_DEFAULT', DATAGRID_RENDER_TABLE );
define('DATAGRID_SOURCE_ARRAY', 'Array');
define('DATAGRID_SOURCE_DATAOBJECT','DataObject');
define('DATAGRID_SOURCE_DB', 'DB');
define('DATAGRID_SOURCE_XML', 'XML');
define('DATAGRID_SOURCE_RSS', 'RSS');
define('DATAGRID_SOURCE_CSV', 'CSV');
define('DATAGRID_SOURCE_DBQUERY', 'DBQuery');
define('DATAGRID_SOURCE_DBTABLE', 'DBTable');
define('DATAGRID_SOURCE_MDB2', 'MDB2');
// PEAR_Error code for unsupported features
define('DATAGRID_ERROR_UNSUPPORTED', 1 );
* Structures_DataGrid Class
* A PHP class to implement the functionality provided by the .NET Framework's
* DataGrid control. This class can produce a data driven list in many formats
* based on a defined record set. Commonly, this is used for outputting an HTML
* table based on a record set from a database or an XML document. It allows
* for the output to be published in many ways, including an HTML table,
* an HTML Template, an Excel spreadsheet, an XML document. The data can
* be sorted and paged, each cell can have custom output, and the table can be
* custom designed with alternating color rows.
* require 'Structures/DataGrid.php';
* $datagrid =& new Structures_DataGrid();
* $options = array('dsn' => 'mysql://user:password@host/db_name');
* $datagrid->bind("SELECT * FROM my_table", $options);
* @author Andrew S. Nagy <asnagy@webitecture.org>
* @author Olivier Guilyardi <olivier@samalyse.com>
* @author Mark Wiesemann <wiesemann@php.net>
* @author Sascha Grossenbacher <saschagros@bluewin.ch>
* @package Structures_DataGrid
* @var object Structures_DataGrid_Renderer_* family
* @var int DATAGRID_RENDER_* constant family
var $_rendererType = null;
* Options to use for all renderers
var $_rendererCommonOptions = array ();
* @var object Structures_DataGrid_Renderer_* family
* Renderer driver type backup
* @var int DATAGRID_RENDER_* constant family
var $_rendererTypeBackup = null;
* Whether the backup is an empty renderer
* This property is set to true when _saveRenderer() is called and there
var $_rendererEmptyBackup = false;
* Array of columns. Columns are defined as a DataGridColumn object.
var $columnSet = array ();
var $recordSet = array ();
* The Data Source Driver object
* @var object Structures_DataGrid_DataSource
* Fields/directions to sort the data by
* @var array Structure: array(fieldName => direction, ....)
* Default fields/directions to sort the data by
* @var array Structure: array(fieldName => direction, ....)
var $defaultSortSpec = array ();
* Limit of records to show per page.
* The current page to show.
* Whether the page number was provided at instantiation or not
* GET/POST/Cookie parameters prefix
var $_requestPrefix = '';
* Possible renderer types and their equivalent renderer constants
var $_rendererTypes = array (
'html_table' => DATAGRID_RENDER_TABLE ,
'smarty' => DATAGRID_RENDER_SMARTY ,
'spreadsheet_excel_writer_workbook' => DATAGRID_RENDER_XLS ,
'console_table' => DATAGRID_RENDER_CONSOLE ,
'pager_common' => DATAGRID_RENDER_PAGER ,
* Number of records that should be buffered when streaming is enabled
* Array of matched params
var $_mapperMatch = null;
* Allowed URL parameters. Format: Name => regex
var $_mapperRules = array (
'direction' => '(ASC|DESC|asc|desc)'
* Default values for mapper. Format: Name => value
* A default value triggers the param to be optional
var $_mapperDefaults = array (
* URL mapper instance, if activated
* @var object Net_URL_Mapper
* Builds the DataGrid class. The Core functionality and Renderer are
* seperated for maintainability and to keep cohesion high.
* @example constructor.php Instantiation
* @param string $limit The number of records to display per page.
* @param int $page The current page viewed.
* In most cases, this is useless.
* Note: if you specify this, the "page" GET
* variable will be ignored.
* @param string $rendererType The type of renderer to use.
* You may prefer to use the $type argument
* of {@link render}, {@link fill} or
// Set the defined rowlimit
$this->rowLimit = $limit;
//Use set page number, otherwise automatically detect the page number
$this->_forcePage = true;
$this->_forcePage = false;
// Automatic handling of GET/POST/COOKIE variables
$this->_parseHttpRequest ();
* Method used for debugging purposes only. Displays a dump of the DataGrid
* Checks if a file exists in the include path
* @return boolean true success and false on error
function fileExists ($file)
$fp = @fopen($file, 'r', true );
* Checks if a class exists without triggering __autoload
* @param string className
* @return bool true success and false on error
function classExists ($className)
* Load a Renderer or DataSource driver
* @param string $className Name of the driver class
* @return object The driver object or a PEAR_Error
function &loadDriver ($className)
$fileName = str_replace('_', DIRECTORY_SEPARATOR , $className) . '.php';
if (!include_once($fileName)) {
$msg = " unable to find package '$className' file '$fileName'";
$msg = " unable to load driver class '$className' from file '$fileName'";
$error = PEAR ::raiseError ($msg);
$driver = & new $className();
* Datasource driver Factory
* A clever method which loads and instantiate data source drivers.
* Can be called in various ways:
* Detect the source type and load the appropriate driver with default
* $driver =& Structures_DataGrid::dataSourceFactory($source);
* Detect the source type and load the appropriate driver with custom
* $driver =& Structures_DataGrid::dataSourceFactory($source, $options);
* Load a driver for an explicit type (faster, bypasses detection routine):
* $driver =& Structures_DataGrid::dataSourceFactory($source, $options, $type);
* @param mixed $source The data source respective to the driver
* @param array $options An associative array of the form:
* array(optionName => optionValue, ...)
* @param string $type The data source type constant (of the form
* @uses Structures_DataGrid::_detectSourceType()
* @return Structures_DataGrid_DataSource|PEAR_Error
* driver object or PEAR_Error on failure
$error = PEAR ::raiseError ('Unable to determine the data source type. '.
'You may want to explicitly specify it.');
if (PEAR ::isError ($type)) {
$className = " Structures_DataGrid_DataSource_$type";
$result = $driver->bind ($source, $options);
if (PEAR ::isError ($result)) {
* Renderer driver factory
* Load and instantiate a renderer driver.
* @param mixed $source The rendering container respective to the driver
* @param array $options An associative array of the form:
* array(optionName => optionValue, ...)
* @param string $type The renderer type constant (of the form
* @uses Structures_DataGrid_DataSource::_detectRendererType()
* @return mixed Returns the renderer driver object or
function &rendererFactory ($type, $options = array ())
if (PEAR ::isError ($type)) {
$className = " Structures_DataGrid_Renderer_$type";
$options = array_merge($this->_rendererCommonOptions, $options);
$driver->setOptions ((array) $options);
* You can call this method several times with different renderers.
* @param mixed $renderer Renderer type or instance (optional)
* @param array $options An associative array of the form:
* array(optionName => optionValue, ...)
* @return mixed True or PEAR_Error
function render($renderer = null , $options = array ())
if (is_a($renderer, 'Structures_DataGrid_Renderer')) {
if (PEAR ::isError ($result)) {
$this->_restoreRenderer ();
} else if (!isset ($this->_renderer)) {
if (PEAR ::isError ($result)) {
$options = array_merge($this->_rendererCommonOptions, (array) $options);
$this->_renderer->setOptions ($options);
if (!$this->_renderer->isBuilt ()) {
$result = $this->build();
if (PEAR ::isError ($result)) {
$result = $this->_renderer->render ();
if (PEAR ::isError ($result)) {
$type = is_null($this->_rendererType)
$this->_restoreRenderer ();
return PEAR ::raiseError (" The $type driver does not support the ".
"render() method. Try using fill().");
$this->_restoreRenderer ();
$this->_restoreRenderer ();
* Return the datagrid output
* @param int $type Renderer type (optional)
* @param array $options An associative array of the form:
* array(optionName => optionValue, ...)
* @return mixed The datagrid output (Usually a string: HTML, CSV, etc...)
function getOutput($type = null , $options = array ())
if (!is_null($this->_bufferSize)) {
return PEAR ::raiseError ('getOutput() cannot be used together with ' .
if (PEAR ::isError ($test)) {
$this->_restoreRenderer ();
} else if (!isset ($this->_renderer)) {
$options = array_merge($this->_rendererCommonOptions, (array) $options);
$this->_renderer->setOptions ($options);
if (!$this->_renderer->isBuilt ()) {
$result = $this->build();
if (PEAR ::isError ($result)) {
$output = $this->_renderer->getOutput ();
$type = is_null($this->_rendererType)
$this->_restoreRenderer ();
return PEAR ::raiseError (" The $type driver does not support the ".
"getOutput() method. Try using render().");
$this->_restoreRenderer ();
* Get the current or default Rendering driver
* Retrieves the renderer object as a reference
* @return object Renderer object reference
* Get the currently loaded DataSource driver
* Retrieves the DataSource object as a reference
* @return object DataSource object reference or null if no driver is loaded
if (isset ($this->_dataSource)) {
return $this->_dataSource;
* Defines which renderer to be used by the DataGrid based on given
* $type and $options. To attach an existing renderer instance, use
* attachRenderer() instead.
* @param string $type The defined renderer string
* @param array $options Rendering options
* @return mixed Renderer instance or PEAR_Error
* @see Structures_DataGrid::attachRenderer()
$renderer = & $this->rendererFactory ($type, $options);
if (PEAR ::isError ($renderer)) {
$this->_rendererType = $type;
* Backup the current renderer
if (isset ($this->_renderer)) {
// The following line is a workaround for PHP bug 32660
// See: http://bugs.php.net/bug.php?id=32660
// Another solution would be to remove __get which is used only for BC
$this->_rendererBackup = 1;
$this->_rendererBackup = & $this->_renderer;
$this->_rendererTypeBackup = $this->_rendererType;
$this->_rendererType = null;
$this->_rendererEmptyBackup = true;
* Restore a previously saved renderer
* If the $_renderer property was not set when _saveRenderer() got
* previously called, _restoreRenderer() will unset it.
function _restoreRenderer ()
if ($this->_rendererEmptyBackup) {
$this->_rendererType = null;
} elseif (isset ($this->_rendererBackup)) {
$this->_renderer = & $this->_rendererBackup;
$this->_rendererType = $this->_rendererTypeBackup;
unset ($this->_rendererBackup);
$this->_rendererTypeBackup = null;
$this->_rendererEmptyBackup = false;
* Tell the renderer how the data is sorted
* This method takes the "multiSort" capabilities of the datasource
* into account. The idea is to correctly inform the renderer : for
* example, a GET request may contain multiple fields and directions
* to sort by. But, if the datasource does not support "multiSort"
* then the renderer should not be told that the data is sorted according
* It also properly set the "multiSortCapable" renderer flag (second argument
* to Renderer::setCurrentSorting()).
* This method requires both a datasource and renderer to be loaded.
* It should be called even if $sortSpec is empty.
function _setRendererCurrentSorting ()
$sortSpec = $this->sortSpec ? $this->sortSpec : $this->defaultSortSpec;
if (isset ($this->_dataSource)
$this->_renderer->setCurrentSorting ($sortSpec, true );
list ($field, $direction) = each($sortSpec);
$this->_renderer->setCurrentSorting (
array ($field => $direction), false );
* Attach an already instantiated Rendering driver
* @param object $renderer Driver object, subclassing
* Structures_DataGrid_Renderer
* @return mixed Renderer instance or a PEAR_Error object
* @see Structures_DataGrid::setRenderer()
// The following line is a workaround for PHP bug 32660
// See: http://bugs.php.net/bug.php?id=32660
$this->_renderer = & $renderer;
if (isset ($this->_dataSource)) {
$this->_renderer->setColumns ($this->columnSet);
$this->_renderer->setLimit ($this->page, $this->rowLimit,
$this->_setRendererCurrentSorting ();
if ($this->_requestPrefix) {
$this->_renderer->setRequestPrefix ($this->_requestPrefix);
return PEAR ::raiseError ('Invalid renderer type, ' .
'must be a valid renderer driver class');
* Fill a rendering container with data
* @example fill-sortform.php Fill a form with sort fields
* @example fill-pager.php Filling a Pager object
* @param object &$container A rendering container of any of the supported
* types (example: an HTML_Table object,
* a Spreadsheet_Excel_Writer object, etc...)
* @param array $options Options for the corresponding rendering driver
* @param string $type Explicit type in case the container type
* @return mixed Either true or a PEAR_Error object
function fill(&$container, $options = array (), $type = null )
$type = $this->_detectRendererType ($container);
return PEAR ::raiseError ('The rendering container type can not '.
'be automatically detected. Please ' .
'specify its type explicitly.');
/* Is a renderer driver already loaded and does it exactly match
* the driver class name that corresponds to $type ? */
//FIXME: is this redundant with the $rendererType property ?
if (!isset ($this->_renderer)
or !is_a($this->_renderer, " Structures_DataGrid_Renderer_$type" )) {
/* No, then load the right driver */
if (PEAR ::isError ($test = $this->setRenderer($type, $options))) {
$this->_restoreRenderer ();
$options = array_merge($this->_rendererCommonOptions, (array) $options);
$this->_renderer->setOptions ($options);
$test = $this->_renderer->setContainer ($container);
if (PEAR ::isError ($test)) {
$this->_restoreRenderer ();
return PEAR ::raiseError (" The $type driver does not support the " .
"fill() method. Try using render().");
$this->_restoreRenderer ();
if (!$this->_renderer->isBuilt ()) {
$result = $this->build();
if (PEAR ::isError ($result)) {
$this->_restoreRenderer ();
* This method handles the instantiation of default column objects,
* when some records have been fetched from the datasource but columns
* have neither been generated, nor provided by the user.
function _createDefaultColumns ()
if (empty ($this->columnSet)) {
* Retrieves the current page number when paging is implemented
* @return int the current page number
* Define the current page number.
* This method is used when paging is implemented
* @param mixed $page The current page number (as string or int).
* Returns the total number of pages
* @return int the total number of pages or 1 if there are no records
* or if there is no row limit
* Returns the number of columns
* @return int the number of columns
return count($this->columnSet);
* Returns the total number of records
* @return int the total number of records
if (isset ($this->_dataSource)) {
return $this->_dataSource->count();
// If there is no datasource then there is no data. The old way
// of putting an array into the recordSet property does not exist
// anymore. Binding an array loads the Array datasource driver.
* Returns the number of the first record of the current page
* @return int the number of the first record currently shown, or: 0
* if there are no records, 1 if there is no row limit
return ($this->page - 1 ) * $this->rowLimit + 1;
* Returns the number of the last record of the current page
* @return int the number of the last record currently shown
* Set the global GET/POST variables prefix
* If you need to change the request variables, you can define a prefix.
* This is extra useful when using multiple datagrids.
* This methods need to be called before bind().
* @param string $prefix The prefix to use on request variables;
$this->_requestPrefix = $prefix;
$this->_parseHttpRequest ();
if (isset ($this->_renderer)) {
$this->_renderer->setRequestPrefix ($prefix);
/* We just called parseHttpRequest() using a new requestPrefix.
* The page and sort request might have changed, so we need
* to pass them again to the renderer */
$this->_renderer->setLimit ($this->page, $this->rowLimit,
$this->_setRendererCurrentSorting ();
* Add a column, with optional position
* @example addColumn.php Adding a simple column
* @param object $column The Structures_DataGrid_Column object
* @param string $position One of: "last", "first", "after" or "before"
* @param string $relativeTo The name (label) or field name of the
* relative column, if $position is "after"
* @return mixed PEAR_Error on failure, void otherwise
function addColumn(&$column, $position = 'last', $relativeTo = null )
if (!is_a($column, 'structures_datagrid_column')) {
return PEAR ::raiseError (__FUNCTION__ . "(): not a valid ".
" Structures_DataGrid_Column object");
$this->columnSet[0 ] = & $column;
$this->columnSet[] = & $column;
$this->_createDefaultColumns ();
// Has a relative column been specified ?
__FUNCTION__ . "(): a relative column must be".
" specified when using position \"$position\"" );
foreach ($this->columnSet as $i => $relColumn) {
if ($relColumn->columnName == $relativeTo
|| $relColumn->fieldName == $relativeTo) {
// If it does not exist, return an error
__FUNCTION__ . "(): Can't find a relative ".
"column which name or field name is: ".
// It exists, add the column after or before it
if ($position == 'after') {
$this->columnSet[$relIndex + 1 ] = & $column;
$this->columnSet[$relIndex] = & $column;
* Return the current columns
* @return array Structures_DataGrid_Column objects (references to).
* This is a numerically indexed array (starting from 0).
$this->_createDefaultColumns ();
// Cloning the column set to prevent users from playing with our
// internal $columnSet property.
$columnSetClone = array ();
for ($i = 0; $i < $columnCount; $i++ ) {
$columnSetClone[$i] = & $this->columnSet[$i];
* Find a column by name (label)
* @param string $name The name (label) of the column to look for
* @return object Either the column object (reference to) or
* false if there is no such column
$this->_createDefaultColumns ();
foreach ($this->columnSet as $key => $column) {
if ($column->columnName === $name) {
return $this->columnSet[$key];
* Find a column by field name
* @param string $fieldName The field name of the column to look for
* @return object Either the column object (reference to) or
* false if there is no such column
$this->_createDefaultColumns ();
foreach ($this->columnSet as $key => $column) {
if ($column->fieldName === $fieldName) {
return $this->columnSet[$key];
* @example removeColumn.php Remove an unneeded column
* @param object $column The Structures_DataGrid_Column object
$columnCount = count($this->columnSet);
for ($i = 0; $i < $columnCount; $i++ ) {
if ($this->columnSet[$i]->id == $column->id ) {
for ($i++; $i < $columnCount; $i++ ) {
$this->columnSet[$i - 1 ] = & $this->columnSet[$i];
* A simple way to add a record set to the datagrid
* @example bind-dataobject.php Bind a DB_DataObject
* @example bind-sql.php Bind an SQL query
* @param mixed $container The record set in any of the supported data
* @param array $options Optional. The options to be used for the
* @param string $type Optional. The data source type
* @return bool True if successful, otherwise PEAR_Error.
function bind($container, $options = array (), $type = null )
if (!PEAR ::isError ($source)) {
* Bind an already instantiated DataSource driver
* @param mixed &$source The data source driver object
* @return mixed True if successful, otherwise PEAR_Error
$this->_dataSource = & $source;
$result = $this->fetchDataSource ();
if (PEAR ::isError ($result)) {
if ($columnSet = $this->_dataSource->getColumns()) {
$this->columnSet = array_merge($this->columnSet, $columnSet);
return PEAR ::raiseError ('Invalid data source type, ' .
'must be a valid data source driver class');
* Request the datasource to sort its data
function _sortDataSource ()
if (!empty ($this->sortSpec)) {
if ($this->_dataSource->hasFeature('multiSort')) {
$this->_dataSource->sort($this->sortSpec);
list ($sortBy, $direction) = each($this->sortSpec);
$this->_dataSource->sort($sortBy, $direction);
* Fetch data from the datasource
* @param integer $startRow Start fetching from the specified row number
* @return mixed Either true or a PEAR_Error object
function fetchDataSource ($startRow = null )
if (isset ($this->_dataSource)) {
if (empty ($this->sortSpec) and $this->defaultSortSpec) {
$this->sortSpec = $this->defaultSortSpec;
$this->_sortDataSource ();
// is streaming enabled or not?
// sometimes we have to fix the page number:
// if we have a row limit, a page number lower than 1, or greater
// than 1 and the real page count is lower than the given page
// number indicates, the page number will be set to 1
if (!is_null($this->rowLimit) && ($this->page < 1 ||
$page = $this->page ? $this->page - 1 : 0;
$recordSet = $this->_dataSource->fetch(
($page * $this->rowLimit),
$limit = $this->_bufferSize;
if (!is_null($this->rowLimit) && $limit > $this->rowLimit) {
$limit = $this->rowLimit;
$recordSet = $this->_dataSource->fetch($startRow, $limit);
if (PEAR ::isError ($recordSet)) {
$this->recordSet = $recordSet;
return PEAR ::raiseError ("Cannot fetch data: no datasource driver loaded.");
* Sorts the records by the defined field.
* Do not use this method if data is coming from a database as sorting
* is much faster coming directly from the database itself.
* @param array $sortSpec Sorting specification
* Structure: array(fieldName => direction, ...)
* @param string $direction Deprecated. Put the direction(s) into
$this->sortSpec = $sortSpec;
$this->sortSpec = array ($sortSpec => $direction);
if (isset ($this->_dataSource)) {
$this->_sortDataSource ();
* Set default sorting specification
* If there is no sorting query in the HTTP request, and if the
* sortRecordSet() method is not called, then the specification
* passed to setDefaultSort() will be used.
* This is especially useful if you want the data to already be
* sorted when a user first see the datagrid.
* This method needs to be called before bind().
* @param array $sortSpec Sorting specification
* Structure: array(fieldName => direction, ...)
* @return mixed Either true or a PEAR_Error object
return PEAR ::raiseError ('Invalid parameter, array expected');
$this->defaultSortSpec = $sortSpec;
* Read an HTTP request argument
* This methods take the $_requestPrefix into account, and respect the
* POST, GET, COOKIE read order.
* @param string $name Argument name
* @return mixed Argument value or null
function _getRequestArgument ($name)
if (isset ($this->_mapperMatch[$name])) {
return $this->_mapperMatch[$name];
$prefix = $this->_requestPrefix;
if (isset ($_REQUEST[" $prefix$name" ])) {
if (isset ($_POST[" $prefix$name" ])) {
$value = $_POST[" $prefix$name" ];
} elseif (isset ($_GET[" $prefix$name" ])) {
$value = $_GET[" $prefix$name" ];
} elseif (isset ($_COOKIE[" $prefix$name" ])) {
$value = $_COOKIE[" $prefix$name" ];
* Secure the sort direction string
* @param string $str Direction string
* @return string Either ASC or DESC
function _secureDirection ($str)
return ($str == 'ASC' or $str == 'DESC') ? $str : 'ASC';
* Parse HTTP Request parameters
* Determine page, sort and direction values
* @return array Associative array of parsed arguments, each of these
* defaulting to null if not found.
function _parseHttpRequest ()
//FIXME: with two grids on the same page, one grid with an empty prefix
//and the other with a non-empty prefix, the first interfers with the
//second, because _parseHttpRequest() is called from the constructor
//before setRequestPrefix().
if (!$this->_forcePage) {
if (!($this->page = $this->_getRequestArgument ('page'))) {
if (($orderBy = $this->_getRequestArgument ('orderBy')) !== null ) {
$direction = $this->_getRequestArgument ('direction');
$this->sortSpec = array ();
foreach ($orderBy as $index => $field) {
$this->sortSpec[$field] =
$this->_secureDirection ($direction[$index]);
if (!($direction = $this->_getRequestArgument ('direction'))) {
array ($orderBy => $this->_secureDirection ($direction));
* Detect datasource container type
* @param mixed $source Some kind of source
* @param array $options Options passed to dataSourceFactory()
* @return string The type constant of this source or null if
* it couldn't be detected
* @todo Add CSV detector. Possible rewrite in IFs to allow for
* hierarchy for seperating file handle sources from others
function _detectSourceType ($source, $options = array ())
// SQL query based drivers
preg_match('#SELECT\s.+\sFROM#is', $source) === 1:
return 'MDB2'; // default driver for SQL queries
* Detect rendering container type
* @param object $container The rendering container
* @return string The container type or null if unrecognized
function _detectRendererType (&$container)
foreach ($this->_rendererTypes as $class => $type) {
if (is_a($container, $class)) {
* Correct the (file)name of a driver
* @param string $name The name of the driver
* @param string $type The type of the driver
* @return mixed Either true or a PEAR_Error object
function _correctDriverName ($name, $type)
'DBDataObject' => 'DataObject',
'ConsoleTable' => 'Console',
// replace underscores (e.g. HTML_Table driver has filename HTMLTable.php)
// check, whether a name mapping exists (e.g. from 'Excel' to 'XLS')
if (isset ($driverNameMap[$type][$name])) {
return $driverNameMap[$type][$name];
// we could not find a valid driver name => return an error
$error = PEAR ::raiseError (" Unknown $type driver. Please specify an " .
* @return mixed Either true or a PEAR_Error object
if (isset ($this->_dataSource)) {
// is streaming enabled or not?
$this->_prepareColumnsAndRenderer ();
$result = $this->_renderer->build ($this->recordSet, 0 , true );
if (PEAR ::isError ($result)) {
$recordCount = $this->_dataSource->count();
for ($row = ($this->page - 1 ) * $this->rowLimit, $initial = true;
|| $row < $this->page * $this->rowLimit
$row += $this->_bufferSize, $initial = false
// prepare columns and renderer only on first iteration
$this->_prepareColumnsAndRenderer ();
// we don't fetch on the first iteration because a chunk
// of data has already been fetched by bindDataSource()
if (PEAR ::isError ($result = $this->fetchDataSource ($row))) {
unset ($this->_dataSource);
|| $row + $this->_bufferSize < $this->page * $this->rowLimit
&& $row + $this->_bufferSize < $recordCount
$startRow = $row - ($this->page - 1 ) * $this->rowLimit;
$result = $this->_renderer->build ($this->recordSet,
if (PEAR ::isError ($result)) {
return PEAR ::raiseError ('Cannot build the datagrid: ' .
'no datasource driver loaded');
* Prepare columns and renderer for building
function _prepareColumnsAndRenderer ()
$this->_createDefaultColumns ();
if (isset ($this->_renderer)) {
$this->_renderer->setStreaming (!is_null($this->_bufferSize));
$this->_renderer->setColumns ($this->columnSet);
$this->_renderer->setLimit ($this->page, $this->rowLimit,
$this->_setRendererCurrentSorting ();
* Provide some BC fix (requires PHP5)
* This is a PHP5 magic method used to simulate the old public
if ($var == 'renderer') {
* Set a single renderer option
* @param string $name Option name
* @param mixed $value Option value
* @param bool $common Whether to use this option for all
* renderers (true) or only for the current
* Set multiple renderer options
* @param array $options An associative array of the form:
* array("option_name" => "option_value",...)
* @param bool $common Whether to use these options for all
* renderers (true) or only for the current
$this->_rendererCommonOptions
= array_merge($this->_rendererCommonOptions, (array) $options);
// There is no need to load the default renderer if these are common
// options. rendererFactory() will set them up.
isset ($this->_renderer) and $this->_renderer->setOptions ((array) $options);
$this->_renderer->setOptions ((array) $options);
* Set a single datasource option
* @param string $name Option name
* @param mixed $value Option value
* Set multiple datasource options
* @param array $options An associative array of the form:
* array("option_name" => "option_value",...)
if (isset ($this->_dataSource)) {
return PEAR ::raiseError ('Unable to set options; no datasource loaded.');
* Enable streaming support for reading from DataSources and writing with
* Renderers and set the buffer size (number of records)
* @param integer $bufferSize Number of records that should be buffered
$this->_bufferSize = $bufferSize;
* Generate columns from a fields list
* This is a shortcut for adding simple columns easily, instead of creating
* them manually and calling addColumn() for each.
* The generated columns are appended to the current column set.
* @param array $fields Fields and labels.
* Array of the form: array(field => label, ...)
* The default is an empty array, which means:
* all fields fetched from the datasource
if (!empty ($this->recordSet)) {
foreach ($this->recordSet[0 ] as $key => $data) {
foreach ($fields as $field => $label) {
* Enable and configure URL mapping
* If this is set, it will be parsed instead of GET/POST.
* This is only supported on PHP5, as it depends on
* There are three possible placeholders, :pager, :orderBy and :direction.
* :page or (:orderBy and :direction) can be used alone.
* It is possible to use multipe DataGrid instances on one page with
* Instead of a format string you might also pass a Net_URL_Mapper instance
* to this method, in which case $prefix and $scriptname will be ignored.
* This instance must be properly set up, connected to url patterns, etc...
* This is especially useful when you've already configured URL mapping
* globally for your application and want Structures_DataGrid to integrate.
* @example urlFormat.php configure a url format
* @param mixed $format The URL format string or a Net_URL_Mapper instance
* @param string $prefix Sets the url prefix
* @param string $scriptname Set the scriptname if mod_rewrite not available
* @throws Net_URL_Mapper_InvalidException
* @see http://pear.php.net/Net_URL_Mapper
function setUrlFormat($format, $prefix = null , $scriptname = null )
return PEAR ::raiseError ('Net_URL_Mapper Package is missing');
include_once 'Net/URL/Mapper.php';
// reset parsed Params and reparse the request
$this->_mapperMatch = null;
// only call _parseHttpRequest again if the URL matches
if ($this->_parseRequestWithMapper ($format, $prefix, $scriptname)) {
$this->_parseHttpRequest ();
// copied from setRequestPrefix
// perhabs, this part can be moved to _parseHttpRequest
if (isset ($this->_renderer)) {
/* The page and sort request might have changed, so we need
* to pass them again to the renderer */
$this->_renderer->setLimit ($this->page, $this->rowLimit,
$this->_setRendererCurrentSorting ();
* Tries to parse the request with
* @param mixed $format The URL format string or a Net_URL_Mapper instance
* @param string $prefix Set the url prefix
* @param string $scriptname Set the scriptname if mod_rewrite not available
* @throws Net_URL_Mapper_InvalidException
function _parseRequestWithMapper ($format, $prefix = null , $scriptname = null )
if (is_a($format, 'Net_URL_Mapper')) {
// Use a special instance, so that it is usable for multipe
// SDG instances and other NUM Instances
$this->_urlMapper = Net_URL_Mapper ::getInstance ('__SDG_Instance_' . $prefix);
// If the prefix is not null, set it
//if the scriptname is not null, set it
// "connect" the format wit defaults and the defined rules
$this->_urlMapper->connect ($format, $this->_mapperDefaults, $this->_mapperRules);
// run NUM, if it returns an array, the url was successfully matched,
if ($this->_mapperMatch = $this->_urlMapper->match ($_SERVER['REQUEST_URI'])) {
/* vim: set expandtab tabstop=4 shiftwidth=4: */
Documentation generated on Tue, 18 Dec 2007 11:30:11 -0500 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.
|