1<?php
2/**
3 * Smarty Rendering Driver
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 * CVS file id: $Id: Smarty.php 259488 2008-05-10 15:08:26Z olivierg $
40 *
41 * @version  $Revision: 259488 $
42 * @package  Structures_DataGrid_Renderer_Smarty
43 * @category Structures
44 * @license  http://opensource.org/licenses/bsd-license.php New BSD License
45 */
46
47require_once 'Structures/DataGrid.php';
48require_once 'Structures/DataGrid/Renderer.php';
49
50/**
51 * Smarty Rendering Driver
52 *
53 * SUPPORTED OPTIONS:
54 *
55 * - selfPath:            (string) The complete path for sorting and paging links.
56 *                                 (default: $_SERVER['PHP_SELF'])
57 * - sortingResetsPaging: (bool)   Whether sorting HTTP queries reset paging.
58 * - convertEntities:     (bool)   Whether or not to convert html entities.
59 *                                 This calls htmlspecialchars().
60 * - varPrefix:           (string) Prefix for smarty variables and functions
61 *                                 assigned by this driver. Can be used in
62 *                                 conjunction with
63 *                                 Structure_DataGrid::setRequestPrefix() for
64 *                                 displaying several grids on a single page.
65 * - associative:         (bool)   By default the column set and the records
66 *                                 are numerically indexed arrays. By setting
67 *                                 this option to true the keys will be field
68 *                                 names instead.
69 *
70 * SUPPORTED OPERATION MODES:
71 *
72 * - Container Support: yes
73 * - Output Buffering:  yes
74 * - Direct Rendering:  no
75 * - Streaming:         no
76 * - Object Preserving: yes
77 *
78 * GENERAL NOTES:
79 *
80 * To use this driver you need the Smarty template engine from
81 * http://smarty.php.net
82 *
83 * This driver does not support the render() method, it is only able to:
84 *
85 * Either fill() a Smarty object by assigning variables and registering
86 * the {getPaging} smarty function. It's up to you to call Smarty::display()
87 * after the Smarty object has been filled.
88 *
89 * Or return all variables as a PHP array from getOutput(), for maximum
90 * flexibility, so that you can assign them the way you like to your Smarty
91 * instance.
92 *
93 * This driver assigns the following Smarty variables:
94 * <code>
95 * - $columnSet:       array of columns specifications
96 *                     structure:
97 *                          array (
98 *                              0 => array (
99 *                                  'name'       => field name,
100 *                                  'label'      => column label,
101 *                                  'link'       => sorting link,
102 *                                  'attributes' => attributes string,
103 *                                  'direction'  => 'ASC', 'DESC' or '',
104 *                                  'onclick'    => onMove call
105 *                              ),
106 *                              ...
107 *                          )
108 * - $recordSet:       array of records values
109 * - $currentPage:     current page (starting from 1)
110 * - $nextPage:        next page
111 * - $previousPage:    previous page
112 * - $recordLimit:     number of rows per page
113 * - $pagesNum:        number of pages
114 * - $columnsNum:      number of columns
115 * - $recordsNum:      number of records in the current page
116 * - $totalRecordsNum: total number of records
117 * - $firstRecord:     first record number (starting from 1)
118 * - $lastRecord:      last record number (starting from 1)
119 * - $currentSort:     array with column names and the directions used for sorting
120 * - $datagrid:        a reference that you can pass to {getPaging}
121 * </code>
122 *
123 * This driver registers a Smarty custom function named getPaging
124 * that can be called from Smarty templates with {getPaging} in order
125 * to print paging links. This function accepts the same parameters as the
126 * pagerOptions option of Structures_DataGrid_Renderer_Pager.
127 *
128 * {getPaging} accepts an optional "datagrid" parameter
129 * which you can pass the $datagrid variable, to display paging for an
130 * arbitrary datagrid (useful with multiple dynamic datagrids on a single page).
131 *
132 * Object Records : this drivers preserves object records if provided. This means
133 * that if your datasource provides objects instead of associative arrays as
134 * records, you can access their properties and methods in your smarty template,
135 * with something like: {$recordSet[col]->getSomeInformation()}.
136 *
137 * @version  $Revision: 259488 $
138 * @example  smarty-simple.php Using the Smarty renderer
139 * @example  smarty-simple.tpl Smarty template with sorting and paging (smarty-simple.tpl)
140 * @author   Andrew S. Nagy <asnagy@webitecture.org>
141 * @author   Olivier Guilyardi <olivier@samalyse.com>
142 * @author   Sascha Grossenbacher <saschagros@bluewin.ch>
143 * @access   public
144 * @package  Structures_DataGrid_Renderer_Smarty
145 * @see      Structures_DataGrid_Renderer_Pager
146 * @category Structures
147 */
148class Structures_DataGrid_Renderer_Smarty extends Structures_DataGrid_Renderer
149{
150    /**
151     * Variables that get assigned into the Smarty container
152     * @var array Associative array with smarty var names as keys
153     */
154    var $_data;
155
156    /**
157     * Smarty container
158     * @var object Smarty object
159     */
160    var $_smarty = null;
161
162    /**
163     * Constructor
164     *
165     * @access  public
166     */
167    function Structures_DataGrid_Renderer_Smarty()
168    {
169        parent::Structures_DataGrid_Renderer();
170        $this->_addDefaultOptions(
171            array(
172                'selfPath'            => htmlspecialchars($_SERVER['PHP_SELF']),
173                'convertEntities'     => true,
174                'sortingResetsPaging' => true,
175                'varPrefix'           => '',
176                'associative'         => false,
177            )
178        );
179
180        $this->_setFeatures(
181            array(
182                'outputBuffering' => true,
183                'objectPreserving' => true,
184            )
185        );
186    }
187
188    /**
189     * Attach an already instantiated Smarty object
190     *
191     * @param  object $smarty Smarty container
192     * @return mixed True or PEAR_Error
193     */
194    function setContainer(&$smarty)
195    {
196        $this->_smarty =& $smarty;
197        return true;
198    }
199
200    /**
201     * Attach a Smarty instance
202     *
203     * @deprecated Use setContainer() instead
204     * @param object Smarty instance
205     * @access public
206     */
207    function setSmarty(&$smarty)
208    {
209        return $this->setContainer($smarty);
210    }
211
212    /**
213     * Return the currently used Smarty object
214     *
215     * @return object Smarty or PEAR_Error object
216     */
217    function &getContainer()
218    {
219        return $this->_smarty;
220    }
221
222    /**
223     * Initialize the Smarty container
224     *
225     * @access protected
226     */
227    function init()
228    {
229        $p = $this->_options['varPrefix'];
230        $this->_data = array(
231            "{$p}currentPage"       => $this->_page,
232            "{$p}nextPage"          => ($this->_page < $this->_pagesNum) ? $this->_page + 1 : null,
233            "{$p}previousPage"      => ($this->_page > 1) ? $this->_page - 1 : null,
234            "{$p}recordLimit"       => $this->_pageLimit,
235            "{$p}columnsNum"        => $this->_columnsNum,
236            "{$p}recordsNum"        => $this->_recordsNum,
237            "{$p}totalRecordsNum"   => $this->_totalRecordsNum,
238            "{$p}pagesNum"          => $this->_pagesNum,
239            "{$p}firstRecord"       => $this->_firstRecord,
240            "{$p}lastRecord"        => $this->_lastRecord,
241            "{$p}currentSort"       => $this->_currentSort,
242        );
243    }
244
245    /**
246     * Build the header
247     *
248     * @param   array $columns Columns' fields names and labels
249     * @access  protected
250     * @return  void
251     */
252    function buildHeader(&$columns)
253    {
254        $prepared = array();
255        foreach ($columns as $index => $spec) {
256            $key = $this->_options['associative'] ? $spec['field'] : $index;
257            if (in_array($spec['field'], $this->_sortableFields)) {
258                reset($this->_currentSort);
259                if ((list($currentField, $currentDirection) = each($this->_currentSort))
260                    && isset($currentField)
261                    && $currentField == $spec['field']
262                   ) {
263                    if ($currentDirection == 'ASC') {
264                        $direction = 'DESC';
265                    } else {
266                        $direction = 'ASC';
267                    }
268                    $prepared[$key]['direction'] = $currentDirection;
269                } else {
270                    $prepared[$key]['direction'] = '';
271                    $direction = $this->_defaultDirections[$spec['field']];
272                }
273                $page = $this->_options['sortingResetsPaging'] ? 1 : $this->_page;
274                $extra = array('page' => $page);
275                // Check if NUM is enabled
276                if ($this->_urlMapper) {
277                    $prepared[$key]['link'] = $this->_buildMapperURL($spec['field'],
278                                                                       $direction,
279                                                                       $page);
280                } else {
281                    $query = $this->_buildSortingHttpQuery($spec['field'],
282                                                       $direction, true, $extra);
283                    $prepared[$key]['link'] = "{$this->_options['selfPath']}?$query";
284                }
285                $prepared[$key]['onclick'] = $this->_buildOnMoveCall($page,
286                        array($spec['field'] => $direction));
287            } else {
288                $query = '';
289                $prepared[$key]['link'] = "";
290            }
291            $prepared[$key]['name']   = $spec['field'];
292            $prepared[$key]['label']  = $spec['label'];
293
294            $prepared[$key]['attributes'] = "";
295            if (isset($this->_options['columnAttributes'][$spec['field']])) {
296                foreach ($this->_options['columnAttributes'][$spec['field']]
297                            as $name => $value) {
298                    $value = htmlspecialchars($value, ENT_COMPAT,
299                                              $this->_options['encoding']);
300                    $prepared[$key]['attributes'] .= "$name=\"$value\" ";
301                }
302            }
303        }
304
305        $this->_data[$this->_options['varPrefix'] . 'columnSet'] = $prepared;
306    }
307
308    /**
309     * Handles building the body of the table
310     *
311     * @access  protected
312     * @return  void
313     */
314    function buildBody()
315    {
316        if ($this->_options['associative']) {
317            $associative = array();
318            foreach ($this->_records as $row => $rec) {
319                if (is_array($rec)) {
320                    $associative[$row] = array();
321                    foreach ($this->_columns as $col => $spec) {
322                        $associative[$row][$spec['field']] = $rec[$col];
323                    }
324                } else {
325                    // object records are left untouched
326                    $associative[$row] =& $this->_records[$row];
327                }
328            }
329            $this->_data[$this->_options['varPrefix'] . 'recordSet']
330                = $associative;
331        } else {
332            $this->_data[$this->_options['varPrefix'] . 'recordSet']
333                = $this->_records;
334        }
335    }
336
337    /**
338     * Assign the computed variables to the Smarty container, if any
339     *
340     * @access protected
341     * @return void
342     */
343    function finalize()
344    {
345        $p = $this->_options['varPrefix'];
346
347        if ($this->_smarty) {
348            foreach ($this->_data as $key => $val) {
349                $this->_smarty->assign($key, $val);
350            }
351
352            $this->_smarty->assign("{$p}datagrid", $this->_getReference());
353
354            $this->_smarty->register_function("{$p}getPaging",
355                array(&$this, 'smartyGetPaging'));
356        } else {
357            $this->_data["{$p}datagrid"] = $this->_getReference();
358        }
359    }
360
361    /**
362     * Return the computed variables
363     *
364     * @access protected
365     * @return array Array with smarty variable names as keys
366     */
367    function flatten()
368    {
369        return $this->_data;
370    }
371
372    /**
373     * Discard the unsupported render() method
374     *
375     * This Smarty driver does not support the render() method.
376     * It is required to use the setContainer() (or
377     * Structures_DataGrid::fill()) method in order to do anything
378     * with this driver.
379     *
380     */
381    function render()
382    {
383        return $this->_noSupport(__FUNCTION__);
384    }
385
386    /**
387     * Smarty custom function "getPaging"
388     *
389     * This is only meant to be called from a smarty template, using the
390     * expression: {getPaging <options>}
391     *
392     * <options> are any Pager::factory() options
393     *
394     * @param array  $params Options passed from the Smarty template
395     * @param object $smarty Smarty object
396     * @return string Paging HTML links
397     * @access public
398     */
399    function smartyGetPaging($params, &$smarty)
400    {
401        // Load and get output from the Pager rendering driver
402        $driver =& Structures_DataGrid::loadDriver('Structures_DataGrid_Renderer_Pager');
403
404        // Propagate the selfPath option. Do not override user params
405        if (!isset($params['path']) && !isset($params['filename'])) {
406            $params['path'] = dirname($this->_options['selfPath']);
407            $params['fileName'] = basename($this->_options['selfPath']);
408            $params['fixFileName'] = false;
409        }
410
411        // Use a different renderer if provided
412        if (isset($params['datagrid'])) {
413            $renderer =& $this->_getReference($params['datagrid']);
414            unset($params['datagrid']);
415        } else {
416            $renderer =& $this;
417        }
418
419        $driver->setupAs($renderer, $params);
420        $driver->build(array(), 0);
421        return $driver->getOutput();
422    }
423
424    /**
425     * Return a renderer reference by id or create a new id
426     *
427     * @param  int      Renderer id
428     * @return mixed    New id or renderer object
429     */
430    function &_getReference($id = null)
431    {
432        static $references = array();
433
434        if (!is_null($id)) {
435            return $references[$id - 1];
436        } else {
437            $references[] =& $this;
438            $id = count($references);
439            return $id;
440        }
441    }
442
443    /**
444     * Default formatter for all cells
445     *
446     * @param string  Cell value
447     * @return string Formatted cell value
448     * @access protected
449     */
450    function defaultCellFormatter($value)
451    {
452        return $this->_options['convertEntities']
453               ? htmlspecialchars($value, ENT_COMPAT, $this->_options['encoding'])
454               : $value;
455    }
456}
457
458/* vim: set expandtab tabstop=4 shiftwidth=4: */
459?>
460