Seditio Source
Root |
./othercms/croogo-4.0.7/vendor/cakephp/cakephp/src/Datasource/Paginator.php
<?php
/**
 * 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
 * @since         3.5.0
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Datasource;

use
Cake\Core\InstanceConfigTrait;
use
Cake\Datasource\Exception\PageOutOfBoundsException;

/**
 * This class is used to handle automatic model data pagination.
 */
class Paginator implements PaginatorInterface
{
    use
InstanceConfigTrait;

   
/**
     * Default pagination settings.
     *
     * When calling paginate() these settings will be merged with the configuration
     * you provide.
     *
     * - `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.
     * - `whitelist` - 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
     */
   
protected $_defaultConfig = [
       
'page' => 1,
       
'limit' => 20,
       
'maxLimit' => 100,
       
'whitelist' => ['limit', 'sort', 'page', 'direction'],
    ];

   
/**
     * Paging params after pagination operation is done.
     *
     * @var array
     */
   
protected $_pagingParams = [];

   
/**
     * Handles automatic pagination of model records.
     *
     * ### Configuring pagination
     *
     * When calling `paginate()` you can use the $settings parameter to pass in
     * pagination settings. These settings are used to build the queries made
     * and control other pagination settings.
     *
     * If your settings contain a key with the current table's alias. The data
     * inside that key will be used. Otherwise the top level configuration will
     * be used.
     *
     * ```
     *  $settings = [
     *    'limit' => 20,
     *    'maxLimit' => 100
     *  ];
     *  $results = $paginator->paginate($table, $settings);
     * ```
     *
     * The above settings will be used to paginate any repository. You can configure
     * repository specific settings by keying the settings with the repository alias.
     *
     * ```
     *  $settings = [
     *    'Articles' => [
     *      'limit' => 20,
     *      'maxLimit' => 100
     *    ],
     *    'Comments' => [ ... ]
     *  ];
     *  $results = $paginator->paginate($table, $settings);
     * ```
     *
     * This would allow you to have different pagination settings for
     * `Articles` and `Comments` repositories.
     *
     * ### Controlling sort fields
     *
     * By default CakePHP will automatically allow sorting on any column on the
     * repository object being paginated. Often times you will want to allow
     * sorting on either associated columns or calculated fields. In these cases
     * you will need to define a whitelist of all the columns you wish to allow
     * sorting on. You can define the whitelist in the `$settings` parameter:
     *
     * ```
     * $settings = [
     *   'Articles' => [
     *     'finder' => 'custom',
     *     'sortWhitelist' => ['title', 'author_id', 'comment_count'],
     *   ]
     * ];
     * ```
     *
     * Passing an empty array as whitelist disallows sorting altogether.
     *
     * ### Paginating with custom finders
     *
     * You can paginate with any find type defined on your table using the
     * `finder` option.
     *
     * ```
     *  $settings = [
     *    'Articles' => [
     *      'finder' => 'popular'
     *    ]
     *  ];
     *  $results = $paginator->paginate($table, $settings);
     * ```
     *
     * Would paginate using the `find('popular')` method.
     *
     * You can also pass an already created instance of a query to this method:
     *
     * ```
     * $query = $this->Articles->find('popular')->matching('Tags', function ($q) {
     *   return $q->where(['name' => 'CakePHP'])
     * });
     * $results = $paginator->paginate($query);
     * ```
     *
     * ### Scoping Request parameters
     *
     * By using request parameter scopes you can paginate multiple queries in
     * the same controller action:
     *
     * ```
     * $articles = $paginator->paginate($articlesQuery, ['scope' => 'articles']);
     * $tags = $paginator->paginate($tagsQuery, ['scope' => 'tags']);
     * ```
     *
     * Each of the above queries will use different query string parameter sets
     * for pagination data. An example URL paginating both results would be:
     *
     * ```
     * /dashboard?articles[page]=1&tags[page]=2
     * ```
     *
     * @param \Cake\Datasource\RepositoryInterface|\Cake\Datasource\QueryInterface $object The table or query to paginate.
     * @param array $params Request params
     * @param array $settings The settings/configuration used for pagination.
     * @return \Cake\Datasource\ResultSetInterface Query results
     * @throws \Cake\Datasource\Exception\PageOutOfBoundsException
     */
   
public function paginate($object, array $params = [], array $settings = [])
    {
       
$query = null;
        if (
$object instanceof QueryInterface) {
           
$query = $object;
           
$object = $query->getRepository();
        }

       
$alias = $object->getAlias();
       
$defaults = $this->getDefaults($alias, $settings);
       
$options = $this->mergeOptions($params, $defaults);
       
$options = $this->validateSort($object, $options);
       
$options = $this->checkLimit($options);

       
$options += ['page' => 1, 'scope' => null];
       
$options['page'] = (int)$options['page'] < 1 ? 1 : (int)$options['page'];
        list(
$finder, $options) = $this->_extractFinder($options);

        if (empty(
$query)) {
           
$query = $object->find($finder, $options);
        } else {
           
$query->applyOptions($options);
        }

       
$cleanQuery = clone $query;
       
$results = $query->all();
       
$numResults = count($results);
       
$count = $cleanQuery->count();

       
$page = $options['page'];
       
$limit = $options['limit'];
       
$pageCount = max((int)ceil($count / $limit), 1);
       
$requestedPage = $page;
       
$page = min($page, $pageCount);

       
$order = (array)$options['order'];
       
$sortDefault = $directionDefault = false;
        if (!empty(
$defaults['order']) && count($defaults['order']) === 1) {
           
$sortDefault = key($defaults['order']);
           
$directionDefault = current($defaults['order']);
        }

       
$start = 0;
        if (
$count >= 1) {
           
$start = (($page - 1) * $limit) + 1;
        }
       
$end = $start + $limit - 1;
        if (
$count < $end) {
           
$end = $count;
        }

       
$paging = [
           
'finder' => $finder,
           
'page' => $page,
           
'current' => $numResults,
           
'count' => $count,
           
'perPage' => $limit,
           
'start' => $start,
           
'end' => $end,
           
'prevPage' => $page > 1,
           
'nextPage' => $count > ($page * $limit),
           
'pageCount' => $pageCount,
           
'sort' => $options['sort'],
           
'direction' => isset($options['sort']) ? current($order) : null,
           
'limit' => $defaults['limit'] != $limit ? $limit : null,
           
'sortDefault' => $sortDefault,
           
'directionDefault' => $directionDefault,
           
'scope' => $options['scope'],
           
'completeSort' => $order,
        ];

       
$this->_pagingParams = [$alias => $paging];

        if (
$requestedPage > $page) {
            throw new
PageOutOfBoundsException([
               
'requestedPage' => $requestedPage,
               
'pagingParams' => $this->_pagingParams,
            ]);
        }

        return
$results;
    }

   
/**
     * Extracts the finder name and options out of the provided pagination options.
     *
     * @param array $options the pagination options.
     * @return array An array containing in the first position the finder name
     *   and in the second the options to be passed to it.
     */
   
protected function _extractFinder($options)
    {
       
$type = !empty($options['finder']) ? $options['finder'] : 'all';
        unset(
$options['finder'], $options['maxLimit']);

        if (
is_array($type)) {
           
$options = (array)current($type) + $options;
           
$type = key($type);
        }

        return [
$type, $options];
    }

   
/**
     * Get paging params after pagination operation.
     *
     * @return array
     */
   
public function getPagingParams()
    {
        return
$this->_pagingParams;
    }

   
/**
     * Merges the various options that Paginator 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 config value `whitelist` to modify
     * which options/values can be set using request parameters.
     *
     * @param array $params Request params.
     * @param array $settings The settings to merge with the request data.
     * @return array Array of merged options.
     */
   
public function mergeOptions($params, $settings)
    {
        if (!empty(
$settings['scope'])) {
           
$scope = $settings['scope'];
           
$params = !empty($params[$scope]) ? (array)$params[$scope] : [];
        }
       
$params = array_intersect_key($params, array_flip($this->getConfig('whitelist')));

        return
array_merge($settings, $params);
    }

   
/**
     * Get the settings for a $model. If there are no settings for a specific
     * repository, the general settings will be used.
     *
     * @param string $alias Model name to get settings for.
     * @param array $settings The settings which is used for combining.
     * @return array An array of pagination settings for a model,
     *   or the general settings.
     */
   
public function getDefaults($alias, $settings)
    {
        if (isset(
$settings[$alias])) {
           
$settings = $settings[$alias];
        }

       
$defaults = $this->getConfig();
       
$maxLimit = isset($settings['maxLimit']) ? $settings['maxLimit'] : $defaults['maxLimit'];
       
$limit = isset($settings['limit']) ? $settings['limit'] : $defaults['limit'];

        if (
$limit > $maxLimit) {
           
$limit = $maxLimit;
        }

       
$settings['maxLimit'] = $maxLimit;
       
$settings['limit'] = $limit;

        return
$settings + $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 via URL parameters. This helps prevent users from ordering large
     * result sets on un-indexed values.
     *
     * If you need to sort on associated columns or synthetic properties you
     * will need to use a whitelist.
     *
     * 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.
     *
     * The default order options provided to paginate() will be merged with the user's
     * requested sorting field/direction.
     *
     * @param \Cake\Datasource\RepositoryInterface $object Repository object.
     * @param array $options The pagination options being used for this request.
     * @return array An array of options with sort + direction removed and
     *   replaced with order if possible.
     */
   
public function validateSort(RepositoryInterface $object, array $options)
    {
        if (isset(
$options['sort'])) {
           
$direction = null;
            if (isset(
$options['direction'])) {
               
$direction = strtolower($options['direction']);
            }
            if (!
in_array($direction, ['asc', 'desc'])) {
               
$direction = 'asc';
            }

           
$order = (isset($options['order']) && is_array($options['order'])) ? $options['order'] : [];
            if (
$order && $options['sort'] && strpos($options['sort'], '.') === false) {
               
$order = $this->_removeAliases($order, $object->getAlias());
            }

           
$options['order'] = [$options['sort'] => $direction] + $order;
        } else {
           
$options['sort'] = null;
        }
        unset(
$options['direction']);

        if (empty(
$options['order'])) {
           
$options['order'] = [];
        }
        if (!
is_array($options['order'])) {
            return
$options;
        }

       
$inWhitelist = false;
        if (isset(
$options['sortWhitelist'])) {
           
$field = key($options['order']);
           
$inWhitelist = in_array($field, $options['sortWhitelist'], true);
            if (!
$inWhitelist) {
               
$options['order'] = [];
               
$options['sort'] = null;

                return
$options;
            }
        }

        if (
           
$options['sort'] === null
           
&& count($options['order']) === 1
           
&& !is_numeric(key($options['order']))
        ) {
           
$options['sort'] = key($options['order']);
        }

       
$options['order'] = $this->_prefix($object, $options['order'], $inWhitelist);

        return
$options;
    }

   
/**
     * Remove alias if needed.
     *
     * @param array $fields Current fields
     * @param string $model Current model alias
     * @return array $fields Unaliased fields where applicable
     */
   
protected function _removeAliases($fields, $model)
    {
       
$result = [];
        foreach (
$fields as $field => $sort) {
            if (
strpos($field, '.') === false) {
               
$result[$field] = $sort;
                continue;
            }

            list (
$alias, $currentField) = explode('.', $field);

            if (
$alias === $model) {
               
$result[$currentField] = $sort;
                continue;
            }

           
$result[$field] = $sort;
        }

        return
$result;
    }

   
/**
     * Prefixes the field with the table alias if possible.
     *
     * @param \Cake\Datasource\RepositoryInterface $object Repository object.
     * @param array $order Order array.
     * @param bool $whitelisted Whether or not the field was whitelisted.
     * @return array Final order array.
     */
   
protected function _prefix(RepositoryInterface $object, $order, $whitelisted = false)
    {
       
$tableAlias = $object->getAlias();
       
$tableOrder = [];
        foreach (
$order as $key => $value) {
            if (
is_numeric($key)) {
               
$tableOrder[] = $value;
                continue;
            }
           
$field = $key;
           
$alias = $tableAlias;

            if (
strpos($key, '.') !== false) {
                list(
$alias, $field) = explode('.', $key);
            }
           
$correctAlias = ($tableAlias === $alias);

            if (
$correctAlias && $whitelisted) {
               
// Disambiguate fields in schema. As id is quite common.
               
if ($object->hasField($field)) {
                   
$field = $alias . '.' . $field;
                }
               
$tableOrder[$field] = $value;
            } elseif (
$correctAlias && $object->hasField($field)) {
               
$tableOrder[$tableAlias . '.' . $field] = $value;
            } elseif (!
$correctAlias && $whitelisted) {
               
$tableOrder[$alias . '.' . $field] = $value;
            }
        }

        return
$tableOrder;
    }

   
/**
     * Check the limit parameter and ensure it's 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'] = max(min($options['limit'], $options['maxLimit']), 1);

        return
$options;
    }
}