paginator.php

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