Blame | Last modification | View Log | RSS feed
<?php/*** Paginator Component** CakePHP(tm) : Rapid Development Framework (http://cakephp.org)* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)** Licensed under The MIT License* For full copyright and license information, please see the LICENSE.txt* Redistributions of files must retain the above copyright notice.** @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)* @link http://cakephp.org CakePHP(tm) Project* @package Cake.Controller.Component* @since CakePHP(tm) v 2.0* @license http://www.opensource.org/licenses/mit-license.php MIT License*/App::uses('Component', 'Controller');App::uses('Hash', 'Utility');/*** This component is used to handle automatic model data pagination. The primary way to use this* component is to call the paginate() method. There is a convenience wrapper on Controller as well.** ### Configuring pagination** You configure pagination using the PaginatorComponent::$settings. This allows you to configure* the default pagination behavior in general or for a specific model. General settings are used when there* are no specific model configuration, or the model you are paginating does not have specific settings.** ```* $this->Paginator->settings = array(* 'limit' => 20,* 'maxLimit' => 100* );* ```** The above settings will be used to paginate any model. You can configure model specific settings by* keying the settings with the model name.** ```* $this->Paginator->settings = array(* 'Post' => array(* 'limit' => 20,* 'maxLimit' => 100* ),* 'Comment' => array( ... )* );* ```** This would allow you to have different pagination settings for `Comment` and `Post` models.** #### Paginating with custom finders** You can paginate with any find type defined on your model using the `findType` option.** ```* $this->Paginator->settings = array(* 'Post' => array(* 'findType' => 'popular'* )* );* ```** Would paginate using the `find('popular')` method.** @package Cake.Controller.Component* @link http://book.cakephp.org/2.0/en/core-libraries/components/pagination.html*/class PaginatorComponent extends Component {/*** Pagination settings. These settings control pagination at a general level.* You can also define sub arrays for pagination settings for specific models.** - `maxLimit` The maximum limit users can choose to view. Defaults to 100* - `limit` The initial number of items per page. Defaults to 20.* - `page` The starting page, defaults to 1.* - `paramType` What type of parameters you want pagination to use?* - `named` Use named parameters / routed parameters.* - `querystring` Use query string parameters.** @var array*/public $settings = array('page' => 1,'limit' => 20,'maxLimit' => 100,'paramType' => 'named');/*** A list of parameters users are allowed to set using request parameters. Modifying* this list will allow users to have more influence over pagination,* be careful with what you permit.** @var array*/public $whitelist = array('limit', 'sort', 'page', 'direction');/*** Constructor** @param ComponentCollection $collection A ComponentCollection this component can use to lazy load its components* @param array $settings Array of configuration settings.*/public function __construct(ComponentCollection $collection, $settings = array()) {$settings = array_merge($this->settings, (array)$settings);$this->Controller = $collection->getController();parent::__construct($collection, $settings);}/*** Handles automatic pagination of model records.** @param Model|string $object Model to paginate (e.g: model instance, or 'Model', or 'Model.InnerModel')* @param string|array $scope Additional find conditions to use while paginating* @param array $whitelist List of allowed fields for ordering. This allows you to prevent ordering* on non-indexed, or undesirable columns. See PaginatorComponent::validateSort() for additional details* on how the whitelisting and sort field validation works.* @return array Model query results* @throws MissingModelException* @throws NotFoundException*/public function paginate($object = null, $scope = array(), $whitelist = array()) {if (is_array($object)) {$whitelist = $scope;$scope = $object;$object = null;}$object = $this->_getObject($object);if (!is_object($object)) {throw new MissingModelException($object);}$options = $this->mergeOptions($object->alias);$options = $this->validateSort($object, $options, $whitelist);$options = $this->checkLimit($options);$conditions = $fields = $order = $limit = $page = $recursive = null;if (!isset($options['conditions'])) {$options['conditions'] = array();}$type = 'all';if (isset($options[0])) {$type = $options[0];unset($options[0]);}extract($options);if (is_array($scope) && !empty($scope)) {$conditions = array_merge($conditions, $scope);} elseif (is_string($scope)) {$conditions = array($conditions, $scope);}if ($recursive === null) {$recursive = $object->recursive;}$extra = array_diff_key($options, compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive'));if (!empty($extra['findType'])) {$type = $extra['findType'];unset($extra['findType']);}if ($type !== 'all') {$extra['type'] = $type;}if ((int)$page < 1) {$page = 1;}$page = $options['page'] = (int)$page;if ($object->hasMethod('paginate')) {$results = $object->paginate($conditions, $fields, $order, $limit, $page, $recursive, $extra);} else {$parameters = compact('conditions', 'fields', 'order', 'limit', 'page');if ($recursive != $object->recursive) {$parameters['recursive'] = $recursive;}$results = $object->find($type, array_merge($parameters, $extra));}$defaults = $this->getDefaults($object->alias);unset($defaults[0]);if (!$results) {$count = 0;} elseif ($object->hasMethod('paginateCount')) {$count = $object->paginateCount($conditions, $recursive, $extra);} elseif ($page === 1 && count($results) < $limit) {$count = count($results);} else {$parameters = compact('conditions');if ($recursive != $object->recursive) {$parameters['recursive'] = $recursive;}$count = $object->find('count', array_merge($parameters, $extra));}$pageCount = (int)ceil($count / $limit);$requestedPage = $page;$page = max(min($page, $pageCount), 1);$paging = array('page' => $page,'current' => count($results),'count' => $count,'prevPage' => ($page > 1),'nextPage' => ($count > ($page * $limit)),'pageCount' => $pageCount,'order' => $order,'limit' => $limit,'options' => Hash::diff($options, $defaults),'paramType' => $options['paramType']);if (!isset($this->Controller->request['paging'])) {$this->Controller->request['paging'] = array();}$this->Controller->request['paging'] = array_merge((array)$this->Controller->request['paging'],array($object->alias => $paging));if ($requestedPage > $page) {throw new NotFoundException();}if (!in_array('Paginator', $this->Controller->helpers) &&!array_key_exists('Paginator', $this->Controller->helpers)) {$this->Controller->helpers[] = 'Paginator';}return $results;}/*** Get the object pagination will occur on.** @param string|Model $object The object you are looking for.* @return mixed The model object to paginate on.*/protected function _getObject($object) {if (is_string($object)) {$assoc = null;if (strpos($object, '.') !== false) {list($object, $assoc) = pluginSplit($object);}if ($assoc && isset($this->Controller->{$object}->{$assoc})) {return $this->Controller->{$object}->{$assoc};}if ($assoc && isset($this->Controller->{$this->Controller->modelClass}->{$assoc})) {return $this->Controller->{$this->Controller->modelClass}->{$assoc};}if (isset($this->Controller->{$object})) {return $this->Controller->{$object};}if (isset($this->Controller->{$this->Controller->modelClass}->{$object})) {return $this->Controller->{$this->Controller->modelClass}->{$object};}}if (empty($object) || $object === null) {if (isset($this->Controller->{$this->Controller->modelClass})) {return $this->Controller->{$this->Controller->modelClass};}$className = null;$name = $this->Controller->uses[0];if (strpos($this->Controller->uses[0], '.') !== false) {list($name, $className) = explode('.', $this->Controller->uses[0]);}if ($className) {return $this->Controller->{$className};}return $this->Controller->{$name};}return $object;}/*** Merges the various options that Pagination uses.* Pulls settings together from the following places:** - General pagination settings* - Model specific settings.* - Request parameters** The result of this method is the aggregate of all the option sets combined together. You can change* PaginatorComponent::$whitelist to modify which options/values can be set using request parameters.** @param string $alias Model alias being paginated, if the general settings has a key with this value* that key's settings will be used for pagination instead of the general ones.* @return array Array of merged options.*/public function mergeOptions($alias) {$defaults = $this->getDefaults($alias);switch ($defaults['paramType']) {case 'named':$request = $this->Controller->request->params['named'];break;case 'querystring':$request = $this->Controller->request->query;break;}$request = array_intersect_key($request, array_flip($this->whitelist));return array_merge($defaults, $request);}/*** Get the default settings for a $model. If there are no settings for a specific model, the general settings* will be used.** @param string $alias Model name to get default settings for.* @return array An array of pagination defaults for a model, or the general settings.*/public function getDefaults($alias) {$defaults = $this->settings;if (isset($this->settings[$alias])) {$defaults = $this->settings[$alias];}$defaults += array('page' => 1,'limit' => 20,'maxLimit' => 100,'paramType' => 'named');return $defaults;}/*** Validate that the desired sorting can be performed on the $object. Only fields or* virtualFields can be sorted on. The direction param will also be sanitized. Lastly* sort + direction keys will be converted into the model friendly order key.** You can use the whitelist parameter to control which columns/fields are available for sorting.* This helps prevent users from ordering large result sets on un-indexed values.** Any columns listed in the sort whitelist will be implicitly trusted. You can use this to sort* on synthetic columns, or columns added in custom find operations that may not exist in the schema.** @param Model $object The model being paginated.* @param array $options The pagination options being used for this request.* @param array $whitelist The list of columns that can be used for sorting. If empty all keys are allowed.* @return array An array of options with sort + direction removed and replaced with order if possible.*/public function validateSort(Model $object, array $options, array $whitelist = array()) {if (empty($options['order']) && is_array($object->order)) {$options['order'] = $object->order;}if (isset($options['sort'])) {$direction = null;if (isset($options['direction'])) {$direction = strtolower($options['direction']);}if (!in_array($direction, array('asc', 'desc'))) {$direction = 'asc';}$options['order'] = array($options['sort'] => $direction);}if (!empty($whitelist) && isset($options['order']) && is_array($options['order'])) {$field = key($options['order']);$inWhitelist = in_array($field, $whitelist, true);if (!$inWhitelist) {$options['order'] = null;}return $options;}if (!empty($options['order']) && is_array($options['order'])) {$order = array();foreach ($options['order'] as $key => $value) {if (is_int($key)) {$key = $value;$value = 'asc';}$field = $key;$alias = $object->alias;if (strpos($key, '.') !== false) {list($alias, $field) = explode('.', $key);}$correctAlias = ($object->alias === $alias);if ($correctAlias && $object->hasField($field)) {$order[$object->alias . '.' . $field] = $value;} elseif ($correctAlias && $object->hasField($key, true)) {$order[$field] = $value;} elseif (isset($object->{$alias}) && $object->{$alias}->hasField($field, true)) {$order[$alias . '.' . $field] = $value;}}$options['order'] = $order;}return $options;}/*** Check the limit parameter and ensure its within the maxLimit bounds.** @param array $options An array of options with a limit key to be checked.* @return array An array of options for pagination*/public function checkLimit(array $options) {$options['limit'] = (int)$options['limit'];if (empty($options['limit']) || $options['limit'] < 1) {$options['limit'] = 1;}$options['limit'] = min($options['limit'], $options['maxLimit']);return $options;}}