paginator.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: paginator.php 8271 2009-08-02 18:55:37Z jperras $ */
00003 /**
00004  * Pagination Helper class file.
00005  *
00006  * Generates pagination links
00007  *
00008  * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
00009  * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00010  *
00011  * Licensed under The MIT License
00012  * Redistributions of files must retain the above copyright notice.
00013  *
00014  * @filesource
00015  * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00016  * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00017  * @package       cake
00018  * @subpackage    cake.cake.libs.view.helpers
00019  * @since         CakePHP(tm) v 1.2.0
00020  * @version       $Revision: 8271 $
00021  * @modifiedby    $LastChangedBy: jperras $
00022  * @lastmodified  $Date: 2009-08-02 14:55:37 -0400 (Sun, 02 Aug 2009) $
00023  * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
00024  */
00025 /**
00026  * Pagination Helper class for easy generation of pagination links.
00027  *
00028  * PaginationHelper encloses all methods needed when working with pagination.
00029  *
00030  * @package       cake
00031  * @subpackage    cake.cake.libs.view.helpers
00032  */
00033 class PaginatorHelper extends AppHelper {
00034 /**
00035  * Helper dependencies
00036  *
00037  * @var array
00038  */
00039     var $helpers = array('Html', 'Ajax');
00040 /**
00041  * Holds the default model for paged recordsets
00042  *
00043  * @var string
00044  */
00045     var $__defaultModel = null;
00046 /**
00047  * Holds the default options for pagination links
00048  *
00049  * The values that may be specified are:
00050  *
00051  *  - <i>$options['format']</i> Format of the counter. Supported formats are 'range' and 'pages'
00052  *   and custom (default). In the default mode the supplied string is parsed and constants are replaced
00053  *   by their actual values.
00054  *   Constants: %page%, %pages%, %current%, %count%, %start%, %end% .
00055  *  - <i>$options['separator']</i> The separator of the actual page and number of pages (default: ' of ').
00056  *  - <i>$options['url']</i> Url of the action. See Router::url()
00057  *  - <i>$options['url']['sort']</i>  the key that the recordset is sorted.
00058  *  - <i>$options['url']['direction']</i> Direction of the sorting (default: 'asc').
00059  *  - <i>$options['url']['page']</i> Page # to display.
00060  *  - <i>$options['model']</i> The name of the model.
00061  *  - <i>$options['escape']</i> Defines if the title field for the link should be escaped (default: true).
00062  *  - <i>$options['update']</i> DOM id of the element updated with the results of the AJAX call.
00063  *                             If this key isn't specified Paginator will use plain HTML links.
00064  *  - <i>$options['indicator']</i> DOM id of the element that will be shown when doing AJAX requests.
00065  *
00066  * @var array
00067  */
00068     var $options = array();
00069 /**
00070  * Gets the current paging parameters from the resultset for the given model
00071  *
00072  * @param  string $model Optional model name.  Uses the default if none is specified.
00073  * @return array The array of paging parameters for the paginated resultset.
00074  */
00075     function params($model = null) {
00076         if (empty($model)) {
00077             $model = $this->defaultModel();
00078         }
00079         if (!isset($this->params['paging']) || empty($this->params['paging'][$model])) {
00080             return null;
00081         }
00082         return $this->params['paging'][$model];
00083     }
00084 /**
00085  * Sets default options for all pagination links
00086  *
00087  * @param  mixed $options Default options for pagination links. If a string is supplied - it
00088  * is used as the DOM id element to update. See #options for list of keys.
00089  */
00090     function options($options = array()) {
00091         if (is_string($options)) {
00092             $options = array('update' => $options);
00093         }
00094 
00095         if (!empty($options['paging'])) {
00096             if (!isset($this->params['paging'])) {
00097                 $this->params['paging'] = array();
00098             }
00099             $this->params['paging'] = array_merge($this->params['paging'], $options['paging']);
00100             unset($options['paging']);
00101         }
00102         $model = $this->defaultModel();
00103 
00104         if (!empty($options[$model])) {
00105             if (!isset($this->params['paging'][$model])) {
00106                 $this->params['paging'][$model] = array();
00107             }
00108             $this->params['paging'][$model] = array_merge($this->params['paging'][$model], $options[$model]);
00109             unset($options[$model]);
00110         }
00111         $this->options = array_filter(array_merge($this->options, $options));
00112     }
00113 /**
00114  * Gets the current page of the recordset for the given model
00115  *
00116  * @param  string $model Optional model name.  Uses the default if none is specified.
00117  * @return string The current page number of the recordset.
00118  */
00119     function current($model = null) {
00120         $params = $this->params($model);
00121 
00122         if (isset($params['page'])) {
00123             return $params['page'];
00124         }
00125         return 1;
00126     }
00127 /**
00128  * Gets the current key by which the recordset is sorted
00129  *
00130  * @param  string $model Optional model name.  Uses the default if none is specified.
00131  * @param  mixed $options Options for pagination links. See #options for list of keys.
00132  * @return string The name of the key by which the recordset is being sorted, or
00133  *  null if the results are not currently sorted.
00134  */
00135     function sortKey($model = null, $options = array()) {
00136         if (empty($options)) {
00137             $params = $this->params($model);
00138             $options = array_merge($params['defaults'], $params['options']);
00139         }
00140 
00141         if (isset($options['sort']) && !empty($options['sort'])) {
00142             if (preg_match('/(?:\w+\.)?(\w+)/', $options['sort'], $result) && isset($result[1])) {
00143                 if ($result[0] == $this->defaultModel()) {
00144                     return $result[1];
00145                 }
00146             }
00147             return $options['sort'];
00148         } elseif (isset($options['order']) && is_array($options['order'])) {
00149             return key($options['order']);
00150         } elseif (isset($options['order']) && is_string($options['order'])) {
00151             if (preg_match('/(?:\w+\.)?(\w+)/', $options['order'], $result) && isset($result[1])) {
00152                 return $result[1];
00153             }
00154             return $options['order'];
00155         }
00156         return null;
00157     }
00158 /**
00159  * Gets the current direction the recordset is sorted
00160  *
00161  * @param  string $model Optional model name.  Uses the default if none is specified.
00162  * @param  mixed $options Options for pagination links. See #options for list of keys.
00163  * @return string The direction by which the recordset is being sorted, or
00164  *  null if the results are not currently sorted.
00165  */
00166     function sortDir($model = null, $options = array()) {
00167         $dir = null;
00168 
00169         if (empty($options)) {
00170             $params = $this->params($model);
00171             $options = array_merge($params['defaults'], $params['options']);
00172         }
00173 
00174         if (isset($options['direction'])) {
00175             $dir = strtolower($options['direction']);
00176         } elseif (isset($options['order']) && is_array($options['order'])) {
00177             $dir = strtolower(current($options['order']));
00178         }
00179 
00180         if ($dir == 'desc') {
00181             return 'desc';
00182         }
00183         return 'asc';
00184     }
00185 /**
00186  * Generates a "previous" link for a set of paged records
00187  *
00188  * @param  string $title Title for the link. Defaults to '<< Previous'.
00189  * @param  mixed $options Options for pagination link. See #options for list of keys.
00190  * @param  string $disabledTitle Title when the link is disabled.
00191  * @param  mixed $disabledOptions Options for the disabled pagination link. See #options for list of keys.
00192  * @return string A "previous" link or $disabledTitle text if the link is disabled.
00193  */
00194     function prev($title = '<< Previous', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
00195         return $this->__pagingLink('Prev', $title, $options, $disabledTitle, $disabledOptions);
00196     }
00197 /**
00198  * Generates a "next" link for a set of paged records
00199  *
00200  * @param  string $title Title for the link. Defaults to 'Next >>'.
00201  * @param  mixed $options Options for pagination link. See #options for list of keys.
00202  * @param  string $disabledTitle Title when the link is disabled.
00203  * @param  mixed $disabledOptions Options for the disabled pagination link. See #options for list of keys.
00204  * @return string A "next" link or or $disabledTitle text if the link is disabled.
00205  */
00206     function next($title = 'Next >>', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
00207         return $this->__pagingLink('Next', $title, $options, $disabledTitle, $disabledOptions);
00208     }
00209 /**
00210  * Generates a sorting link
00211  *
00212  * @param  string $title Title for the link.
00213  * @param  string $key The name of the key that the recordset should be sorted.
00214  * @param  array $options Options for sorting link. See #options for list of keys.
00215  * @return string A link sorting default by 'asc'. If the resultset is sorted 'asc' by the specified
00216  *  key the returned link will sort by 'desc'.
00217  */
00218     function sort($title, $key = null, $options = array()) {
00219         $options = array_merge(array('url' => array(), 'model' => null), $options);
00220         $url = $options['url'];
00221         unset($options['url']);
00222 
00223         if (empty($key)) {
00224             $key = $title;
00225             $title = __(Inflector::humanize(preg_replace('/_id$/', '', $title)), true);
00226         }
00227         $dir = 'asc';
00228         $sortKey = $this->sortKey($options['model']);
00229         $isSorted = ($sortKey === $key || $sortKey === $this->defaultModel() . '.' . $key);
00230 
00231         if ($isSorted && $this->sortDir($options['model']) === 'asc') {
00232             $dir = 'desc';
00233         }
00234 
00235         if (is_array($title) && array_key_exists($dir, $title)) {
00236             $title = $title[$dir];
00237         }
00238 
00239         $url = array_merge(array('sort' => $key, 'direction' => $dir), $url, array('order' => null));
00240         return $this->link($title, $url, $options);
00241     }
00242 /**
00243  * Generates a plain or Ajax link with pagination parameters
00244  *
00245  * @param  string $title Title for the link.
00246  * @param  mixed $url Url for the action. See Router::url()
00247  * @param  array $options Options for the link. See #options for list of keys.
00248  * @return string A link with pagination parameters.
00249  */
00250     function link($title, $url = array(), $options = array()) {
00251         $options = array_merge(array('model' => null, 'escape' => true), $options);
00252         $model = $options['model'];
00253         unset($options['model']);
00254 
00255         if (!empty($this->options)) {
00256             $options = array_merge($this->options, $options);
00257         }
00258         if (isset($options['url'])) {
00259             $url = array_merge((array)$options['url'], (array)$url);
00260             unset($options['url']);
00261         }
00262         $url = $this->url($url, true, $model);
00263 
00264         $obj = isset($options['update']) ? 'Ajax' : 'Html';
00265         $url = array_merge(array('page' => $this->current($model)), $url);
00266         $url = array_merge(Set::filter($url, true), array_intersect_key($url, array('plugin'=>true)));
00267         return $this->{$obj}->link($title, $url, $options);
00268     }
00269 /**
00270  * Merges passed URL options with current pagination state to generate a pagination URL.
00271  *
00272  * @param  array $options Pagination/URL options array
00273  * @param  boolean $asArray
00274  * @param  string $model Which model to paginate on
00275  * @return mixed By default, returns a full pagination URL string for use in non-standard contexts (i.e. JavaScript)
00276  */
00277     function url($options = array(), $asArray = false, $model = null) {
00278         $paging = $this->params($model);
00279         $url = array_merge(array_filter(Set::diff(array_merge($paging['defaults'], $paging['options']), $paging['defaults'])), $options);
00280 
00281         if (isset($url['order'])) {
00282             $sort = $direction = null;
00283             if (is_array($url['order'])) {
00284                 list($sort, $direction) = array($this->sortKey($model, $url), current($url['order']));
00285             }
00286             unset($url['order']);
00287             $url = array_merge($url, compact('sort', 'direction'));
00288         }
00289 
00290         if ($asArray) {
00291             return $url;
00292         }
00293         return parent::url($url);
00294     }
00295 /**
00296  * Protected method for generating prev/next links
00297  *
00298  */
00299     function __pagingLink($which, $title = null, $options = array(), $disabledTitle = null, $disabledOptions = array()) {
00300         $check = 'has' . $which;
00301         $_defaults = array('url' => array(), 'step' => 1, 'escape' => true, 'model' => null, 'tag' => 'div');
00302         $options = array_merge($_defaults, (array)$options);
00303         $paging = $this->params($options['model']);
00304 
00305         if (!$this->{$check}($options['model']) && (!empty($disabledTitle) || !empty($disabledOptions))) {
00306             if (!empty($disabledTitle) && $disabledTitle !== true) {
00307                 $title = $disabledTitle;
00308             }
00309             $options = array_merge($_defaults, (array)$disabledOptions);
00310         } elseif (!$this->{$check}($options['model'])) {
00311             return null;
00312         }
00313 
00314         foreach (array_keys($_defaults) as $key) {
00315             ${$key} = $options[$key];
00316             unset($options[$key]);
00317         }
00318         $url = array_merge(array('page' => $paging['page'] + ($which == 'Prev' ? $step * -1 : $step)), $url);
00319 
00320         if ($this->{$check}($model)) {
00321             return $this->link($title, $url, array_merge($options, array('escape' => $escape)));
00322         } else {
00323             return $this->Html->tag($tag, $title, $options, $escape);
00324         }
00325     }
00326 /**
00327  * Returns true if the given result set is not at the first page
00328  *
00329  * @param string $model Optional model name.  Uses the default if none is specified.
00330  * @return boolean True if the result set is not at the first page.
00331  */
00332     function hasPrev($model = null) {
00333         return $this->__hasPage($model, 'prev');
00334     }
00335 /**
00336  * Returns true if the given result set is not at the last page
00337  *
00338  * @param string $model Optional model name.  Uses the default if none is specified.
00339  * @return boolean True if the result set is not at the last page.
00340  */
00341     function hasNext($model = null) {
00342         return $this->__hasPage($model, 'next');
00343     }
00344 /**
00345  * Returns true if the given result set has the page number given by $page
00346  *
00347  * @param  string $model Optional model name.  Uses the default if none is specified.
00348  * @param  int $page The page number - if not set defaults to 1.
00349  * @return boolean True if the given result set has the specified page number.
00350  */
00351     function hasPage($model = null, $page = 1) {
00352         if (is_numeric($model)) {
00353             $page = $model;
00354             $model = null;
00355         }
00356         $paging = $this->params($model);
00357         return $page <= $paging['pageCount'];
00358     }
00359 /**
00360  * Protected method
00361  *
00362  */
00363     function __hasPage($model, $page) {
00364         $params = $this->params($model);
00365         if (!empty($params)) {
00366             if ($params["{$page}Page"] == true) {
00367                 return true;
00368             }
00369         }
00370         return false;
00371     }
00372 /**
00373  * Gets the default model of the paged sets
00374  *
00375  * @return string Model name or null if the pagination isn't initialized.
00376  */
00377     function defaultModel() {
00378         if ($this->__defaultModel != null) {
00379             return $this->__defaultModel;
00380         }
00381         if (empty($this->params['paging'])) {
00382             return null;
00383         }
00384         list($this->__defaultModel) = array_keys($this->params['paging']);
00385         return $this->__defaultModel;
00386     }
00387 /**
00388  * Returns a counter string for the paged result set
00389  *
00390  * @param  mixed $options Options for the counter string. See #options for list of keys.
00391  * @return string Counter string.
00392  */
00393     function counter($options = array()) {
00394         if (is_string($options)) {
00395             $options = array('format' => $options);
00396         }
00397 
00398         $options = array_merge(
00399             array(
00400                 'model' => $this->defaultModel(),
00401                 'format' => 'pages',
00402                 'separator' => ' of '
00403             ),
00404         $options);
00405 
00406         $paging = $this->params($options['model']);
00407         if ($paging['pageCount'] == 0) {
00408             $paging['pageCount'] = 1;
00409         }
00410         $start = 0;
00411         if ($paging['count'] >= 1) {
00412             $start = (($paging['page'] - 1) * $paging['options']['limit']) + 1;
00413         }
00414         $end = $start + $paging['options']['limit'] - 1;
00415         if ($paging['count'] < $end) {
00416             $end = $paging['count'];
00417         }
00418 
00419         switch ($options['format']) {
00420             case 'range':
00421                 if (!is_array($options['separator'])) {
00422                     $options['separator'] = array(' - ', $options['separator']);
00423                 }
00424                 $out = $start . $options['separator'][0] . $end . $options['separator'][1] . $paging['count'];
00425             break;
00426             case 'pages':
00427                 $out = $paging['page'] . $options['separator'] . $paging['pageCount'];
00428             break;
00429             default:
00430                 $replace = array(
00431                     '%page%' => $paging['page'],
00432                     '%pages%' => $paging['pageCount'],
00433                     '%current%' => $paging['current'],
00434                     '%count%' => $paging['count'],
00435                     '%start%' => $start,
00436                     '%end%' => $end
00437                 );
00438                 $out = str_replace(array_keys($replace), array_values($replace), $options['format']);
00439             break;
00440         }
00441         return $this->output($out);
00442     }
00443 /**
00444  * Returns a set of numbers for the paged result set
00445  * uses a modulus to decide how many numbers to show on each side of the current page (default: 8)
00446  *
00447  * @param  mixed $options Options for the numbers, (before, after, model, modulus, separator)
00448  * @return string numbers string.
00449  */
00450     function numbers($options = array()) {
00451         if ($options === true) {
00452             $options = array(
00453                 'before' => ' | ', 'after' => ' | ',
00454                 'first' => 'first', 'last' => 'last',
00455             );
00456         }
00457 
00458         $options = array_merge(
00459             array(
00460                 'tag' => 'span',
00461                 'before'=> null, 'after'=> null,
00462                 'model' => $this->defaultModel(),
00463                 'modulus' => '8', 'separator' => ' | ',
00464                 'first' => null, 'last' => null,
00465             ),
00466         (array)$options);
00467 
00468         $params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
00469         unset($options['model']);
00470 
00471         if ($params['pageCount'] <= 1) {
00472             return false;
00473         }
00474 
00475         extract($options);
00476         unset($options['tag'], $options['before'], $options['after'], $options['model'],
00477             $options['modulus'], $options['separator'], $options['first'], $options['last']);
00478 
00479         $out = '';
00480 
00481         if ($modulus && $params['pageCount'] > $modulus) {
00482             $half = intval($modulus / 2);
00483             $end = $params['page'] + $half;
00484 
00485             if ($end > $params['pageCount']) {
00486                 $end = $params['pageCount'];
00487             }
00488             $start = $params['page'] - ($modulus - ($end - $params['page']));
00489             if ($start <= 1) {
00490                 $start = 1;
00491                 $end = $params['page'] + ($modulus  - $params['page']) + 1;
00492             }
00493 
00494             if ($first && $start > 1) {
00495                 $offset = ($start <= (int)$first) ? $start - 1 : $first;
00496                 if ($offset < $start - 1) {
00497                     $out .= $this->first($offset, array('tag' => $tag, 'separator' => $separator));
00498                 } else {
00499                     $out .= $this->first($offset, array('tag' => $tag, 'after' => $separator, 'separator' => $separator));
00500                 }
00501             }
00502 
00503             $out .= $before;
00504 
00505             for ($i = $start; $i < $params['page']; $i++) {
00506                 $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options)) . $separator;
00507             }
00508 
00509             $out .= $this->Html->tag($tag, $params['page'], array('class' => 'current'));
00510             if ($i != $params['pageCount']) {
00511                 $out .= $separator;
00512             }
00513 
00514             $start = $params['page'] + 1;
00515             for ($i = $start; $i < $end; $i++) {
00516                 $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options)). $separator;
00517             }
00518 
00519             if ($end != $params['page']) {
00520                 $out .= $this->Html->tag($tag, $this->link($i, array('page' => $end), $options));
00521             }
00522 
00523             $out .= $after;
00524 
00525             if ($last && $end < $params['pageCount']) {
00526                 $offset = ($params['pageCount'] < $end + (int)$last) ? $params['pageCount'] - $end : $last;
00527                 if ($offset <= $last && $params['pageCount'] - $end > $offset) {
00528                     $out .= $this->last($offset, array('tag' => $tag, 'separator' => $separator));
00529                 } else {
00530                     $out .= $this->last($offset, array('tag' => $tag, 'before' => $separator, 'separator' => $separator));
00531                 }
00532             }
00533 
00534         } else {
00535             $out .= $before;
00536 
00537             for ($i = 1; $i <= $params['pageCount']; $i++) {
00538                 if ($i == $params['page']) {
00539                     $out .= $this->Html->tag($tag, $i, array('class' => 'current'));
00540                 } else {
00541                     $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
00542                 }
00543                 if ($i != $params['pageCount']) {
00544                     $out .= $separator;
00545                 }
00546             }
00547 
00548             $out .= $after;
00549         }
00550 
00551         return $this->output($out);
00552     }
00553 /**
00554  * Returns a first or set of numbers for the first pages
00555  *
00556  * @param  mixed $first if string use as label for the link, if numeric print page numbers
00557  * @param  mixed $options
00558  * @return string numbers string.
00559  */
00560     function first($first = '<< first', $options = array()) {
00561         $options = array_merge(
00562             array(
00563                 'tag' => 'span',
00564                 'after'=> null,
00565                 'model' => $this->defaultModel(),
00566                 'separator' => ' | ',
00567             ),
00568         (array)$options);
00569 
00570         $params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
00571         unset($options['model']);
00572 
00573         if ($params['pageCount'] <= 1) {
00574             return false;
00575         }
00576         extract($options);
00577         unset($options['tag'], $options['after'], $options['model'], $options['separator']);
00578 
00579         $out = '';
00580 
00581         if (is_int($first) && $params['page'] > $first) {
00582             if ($after === null) {
00583                 $after = '...';
00584             }
00585             for ($i = 1; $i <= $first; $i++) {
00586                 $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
00587                 if ($i != $first) {
00588                     $out .= $separator;
00589                 }
00590             }
00591             $out .= $after;
00592         } elseif ($params['page'] > 1) {
00593             $out = $this->Html->tag($tag, $this->link($first, array('page' => 1), $options)) . $after;
00594         }
00595         return $out;
00596     }
00597 /**
00598  * Returns a last or set of numbers for the last pages
00599  *
00600  * @param  mixed $last if string use as label for the link, if numeric print page numbers
00601  * @param  mixed $options
00602  * @return string numbers string.
00603  */
00604     function last($last = 'last >>', $options = array()) {
00605         $options = array_merge(
00606             array(
00607                 'tag' => 'span',
00608                 'before'=> null,
00609                 'model' => $this->defaultModel(),
00610                 'separator' => ' | ',
00611             ),
00612         (array)$options);
00613 
00614         $params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
00615         unset($options['model']);
00616 
00617         if ($params['pageCount'] <= 1) {
00618             return false;
00619         }
00620 
00621         extract($options);
00622         unset($options['tag'], $options['before'], $options['model'], $options['separator']);
00623 
00624         $out = '';
00625         $lower = $params['pageCount'] - $last + 1;
00626 
00627         if (is_int($last) && $params['page'] < $lower) {
00628             if ($before === null) {
00629                 $before = '...';
00630             }
00631             for ($i = $lower; $i <= $params['pageCount']; $i++) {
00632                 $out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
00633                 if ($i != $params['pageCount']) {
00634                     $out .= $separator;
00635                 }
00636             }
00637             $out = $before . $out;
00638         } elseif ($params['page'] < $params['pageCount']) {
00639             $out = $before . $this->Html->tag($tag, $this->link($last, array('page' => $params['pageCount']), $options));
00640         }
00641         return $out;
00642     }
00643 }
00644 ?>

Generated on Sun Nov 22 00:30:54 2009 for CakePHP 1.2.x.x (v1.2.4.8284) by doxygen 1.4.7