Current File : /home/jvzmxxx/wiki1/extensions/Flow/includes/Data/Pager/Pager.php
<?php

namespace Flow\Data\Pager;

use Flow\Data\Index;
use Flow\Data\ObjectManager;
use Flow\Exception\InvalidInputException;

/**
 * Fetches paginated results from the OM provided in constructor
 */
class Pager {
	private static $VALID_DIRECTIONS = array( 'fwd', 'rev' );
	const DEFAULT_DIRECTION = 'fwd';
	const DEFAULT_LIMIT = 1;
	const MAX_LIMIT = 500;
	const MAX_QUERIES = 4;

	/**
	 * @var ObjectManager
	 */
	protected $storage;

	/**
	 * @var Index
	 */
	protected $index;

	/**
	 * @var array Results sorted by the values in this array
	 */
	protected $sort;

	/**
	 * @var array Map of column name to column value for equality query
	 */
	protected $query;

	/**
	 * @var array Options effecting the result such as `sort`, `order`, and `pager-limit`
	 */
	protected $options;

	/**
	 * @var string
	 */
	protected $offsetKey;

	/**
	 * @var boolean Whether this pager uses ID fields
	 */
	protected $useId;

	public function __construct( ObjectManager $storage, array $query, array $options ) {
		// not sure i like this
		$this->storage = $storage;
		$this->query = $query;
		$this->options = $options + array(
			'pager-include-offset' => null,
			'pager-offset' => null,
			'pager-limit' => self::DEFAULT_LIMIT,
			'pager-dir' => self::DEFAULT_DIRECTION,
		);

		$this->options['pager-limit'] = intval( $this->options['pager-limit'] );
		if ( ! ( $this->options['pager-limit'] > 0 && $this->options['pager-limit'] < self::MAX_LIMIT ) ) {
			$this->options['pager-limit'] = self::DEFAULT_LIMIT;
		}

		if ( !in_array( $this->options['pager-dir'], self::$VALID_DIRECTIONS ) ) {
			$this->options['pager-dir'] = self::DEFAULT_DIRECTION;
		}

		$indexOptions = array(
			'limit' => $this->options['pager-limit']
		);
		if ( isset( $this->options['sort'], $this->options['order'] ) ) {
			$indexOptions += array(
				'sort' => array( $this->options['sort'] ),
				'order' => $this->options['order'],
			);
		}
		$this->sort = $storage->getIndexFor(
			array_keys( $query ),
			$indexOptions
		)->getSort();

		$useId = false;
		foreach ( $this->sort as $val ) {
			if ( substr( $val, -3 ) === '_id' ) {
				$useId = true;
			}
			break;
		}
		$this->useId = $useId;

		$this->offsetKey = $useId ? 'offset-id' : 'offset';
	}

	/**
	 * @param callable|null $filter Accepts an array of objects found in a single query
	 *  as its only argument and returns an array of accepted objects.
	 * @return PagerPage
	 */
	public function getPage( $filter = null ) {
		$numNeeded = $this->options['pager-limit'] + 1;
		$storageOffsetKey = $this->useId ? 'offset-id' : 'offset-value';

		$options = $this->options + array(
			// We need one item of leeway to determine if there are more items
			'limit' => $numNeeded,
			'offset-dir' => $this->options['pager-dir'],
			$storageOffsetKey => $this->options['pager-offset'],
			'include-offset' => $this->options['pager-include-offset'],
		);
		$offset = $this->options['pager-offset'];
		$results = array();
		$queries = 0;

		do {
			if ( $queries === 2 ) {
				// if we hit a third query ask for more items
				$options['limit'] = min( self::MAX_LIMIT, $this->options['pager-limit'] * 5 );
			}

			// Retrieve results
			$options = array(
				$storageOffsetKey => $offset,
			) + $options;
			$found = $this->storage->find( $this->query, $options );

			if ( !$found ) {
				// nothing found
				break;
			}
			$filtered = $filter ? call_user_func( $filter, $found ) : $found;
			if ( $this->options['pager-dir'] === 'rev' ) {
				// Paging A-Z with pager-offset F, pager-dir rev, pager-limit 2 gives
				// DE on first query, BC on second, and A on third.  The output
				// needs to be ABCDE
				$results = array_merge( $filtered, $results );
			} else {
				$results = array_merge( $results, $filtered );
			}

			if ( count( $found ) !== $options['limit'] ) {
				// last page
				break;
			}

			// setup offset for next query
			if ( $this->options['pager-dir'] === 'rev' ) {
				$last = reset( $found );
			} else {
				$last = end( $found );
			}
			$offset = $this->storage->serializeOffset( $last, $this->sort );

		} while ( count( $results ) < $numNeeded && ++$queries < self::MAX_QUERIES );

		if ( $queries >= self::MAX_QUERIES ) {
			$count = count( $results );
			$limit = $this->options['pager-limit'];
			wfDebugLog( 'Flow', __METHOD__ . "Reached maximum of $queries queries with $count results of $limit requested with query of " . json_encode( $this->query ) . ' and options ' . json_encode( $options ) );
		}

		if ( $results ) {
			return $this->processPage( $results );
		} else {
			return new PagerPage( array(), array(), $this );
		}
	}

	/**
	 * @param array $results
	 * @return PagerPage
	 * @throws InvalidInputException
	 */
	protected function processPage( $results ) {
		$pagingLinks = array();

		// Retrieve paging links
		if ( $this->options['pager-dir'] === 'fwd' ) {
			if ( count( $results ) > $this->options['pager-limit'] ) {
				// We got extra, another page exists
				$results = array_slice( $results, 0, $this->options['pager-limit'] );
				$pagingLinks['fwd'] = $this->makePagingLink(
					'fwd',
					end( $results ),
					$this->options['pager-limit']
				);
			}

			if ( $this->options['pager-offset'] !== null ) {
				$pagingLinks['rev'] = $this->makePagingLink(
					'rev',
					reset( $results ),
					$this->options['pager-limit']
				);
			}
		} elseif ( $this->options['pager-dir'] === 'rev' ) {
			if ( count( $results ) > $this->options['pager-limit'] ) {
				// We got extra, another page exists
				$results = array_slice( $results, -$this->options['pager-limit'] );
				$pagingLinks['rev'] = $this->makePagingLink(
					'rev',
					reset( $results ),
					$this->options['pager-limit']
				);
			}

			if ( $this->options['pager-offset'] !== null ) {
				$pagingLinks['fwd'] = $this->makePagingLink(
					'fwd',
					end( $results ),
					$this->options['pager-limit']
				);
			}
		} else {
			throw new InvalidInputException( "Unrecognised direction " . $this->options['pager-dir'], 'invalid-input' );
		}

		return new PagerPage( $results, $pagingLinks, $this );
	}

	/**
	 * @param string $direction
	 * @param object $object
	 * @param integer $pageLimit
	 * @return array
	 */
	protected function makePagingLink( $direction, $object, $pageLimit ) {
		$return = array(
			'offset-dir' => $direction,
			'limit' => $pageLimit,
			$this->offsetKey => $this->storage->serializeOffset( $object, $this->sort ),
		);
		if ( isset( $this->options['sortby'] ) ) {
			$return['sortby'] = $this->options['sortby'];
		}
		return $return;
	}
}