drupal/includes/pager.inc

552 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
// $Id$
/**
* @file
* Functions to aid in presenting database results as a set of pages.
*/
/**
* Query extender for pager queries.
*
* This is the "default" pager mechanism. It creates a paged query with a fixed
* number of entries per page.
*/
class PagerDefault extends SelectQueryExtender {
/**
* The highest element we've autogenerated so far.
*
* @var int
*/
static protected $maxElement = 0;
/**
* The number of elements per page to allow.
*
* @var int
*/
protected $limit = 10;
/**
* The unique ID of this pager on this page.
*
* @var int
*/
protected $element = NULL;
/**
* The count query that will be used for this pager.
*
* @var SelectQueryInterface
*/
protected $customCountQuery = FALSE;
public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) {
parent::__construct($query, $connection);
// Add pager tag. Do this here to ensure that it is always added before
// preExecute() is called.
$this->addTag('pager');
}
/**
* Override the execute method.
*
* Before we run the query, we need to add pager-based range() instructions
* to it.
*/
public function execute() {
global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
// Add convenience tag to mark that this is an extended query. We have to
// do this in the constructor to ensure that it is set before preExecute()
// gets called.
if (!$this->preExecute($this)) {
return NULL;
}
// A NULL limit is the "kill switch" for pager queries.
if (empty($this->limit)) {
return;
}
$this->ensureElement();
$page = isset($_GET['page']) ? $_GET['page'] : '';
// Convert comma-separated $page to an array, used by other functions.
$pager_page_array = explode(',', $page);
if (!isset($pager_page_array[$this->element])) {
$pager_page_array[$this->element] = 0;
}
// We calculate the total of pages as ceil(items / limit).
$pager_total_items[$this->element] = $this->getCountQuery()->execute()->fetchField();
$pager_total[$this->element] = ceil($pager_total_items[$this->element] / $this->limit);
$pager_page_array[$this->element] = max(0, min((int)$pager_page_array[$this->element], ((int)$pager_total[$this->element]) - 1));
$pager_limits[$this->element] = $this->limit;
$this->range($pager_page_array[$this->element] * $this->limit, $this->limit);
// Now that we've added our pager-based range instructions, run the query normally.
return $this->query->execute();
}
/**
* Ensure that there is an element associated with this query.
*
* After running this query, access $this->element to get the element for this
* query.
*/
protected function ensureElement() {
if (!empty($this->element)) {
return;
}
$this->element = self::$maxElement++;
}
/**
* Specify the count query object to use for this pager.
*
* You will rarely need to specify a count query directly. If not specified,
* one is generated off of the pager query itself.
*
* @param SelectQueryInterface $query
* The count query object. It must return a single row with a single column,
* which is the total number of records.
*/
public function setCountQuery(SelectQueryInterface $query) {
$this->customCountQuery = $query;
}
/**
* Retrieve the count query for this pager.
*
* The count query may be specified manually or, by default, taken from the
* query we are extending.
*
* @return
* A count SelectQueryInterface object.
*/
protected function getCountQuery() {
if ($this->customCountQuery) {
return $this->customCountQuery;
}
else {
return $this->query->countQuery();
}
}
/**
* Specify the maximum number of elements per page for this query.
*
* The default if not specified is 10 items per page.
*
* @param $limit
* An integer specifying the number of elements per page. If passed a false
* value (FALSE, 0, NULL), the pager is disabled.
*/
public function limit($limit = 10) {
$this->limit = $limit;
return $this;
}
/**
* Specify the element ID for this pager query.
*
* The element is used to differentiate different pager queries on the same
* page so that they may be operated independently. If you do not specify an
* element, every pager query on the page will get a unique element. If for
* whatever reason you want to explicitly define an element for a given query,
* you may do so here.
*
* @param $element
*/
public function element($element) {
$this->element = $element;
return $this;
}
}
/**
* Compose a URL query parameter array for pager links.
*
* @return
* A URL query parameter array that consists of all components of the current
* page request except for those pertaining to paging.
*/
function pager_get_query_parameters() {
$query = &drupal_static(__FUNCTION__);
if (!isset($query)) {
$query = drupal_get_query_parameters($_REQUEST, array_merge(array('q', 'page'), array_keys($_COOKIE)));
}
return $query;
}
/**
* Format a query pager.
*
* Menu callbacks that display paged query results should call theme('pager') to
* retrieve a pager control so that users can view other results.
* Format a list of nearby pages with additional query results.
*
* @param $variables
* An associative array containing:
* - tags: An array of labels for the controls in the pager.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - parameters: An associative array of query string parameters to append to
* the pager links.
* - quantity: The number of pages in the list.
*
* @return
* An HTML string that generates the query pager.
*
* @ingroup themeable
*/
function theme_pager($variables) {
$tags = $variables['tags'];
$element = $variables['element'];
$parameters = $variables['parameters'];
$quantity = $variables['quantity'];
global $pager_page_array, $pager_total;
// Calculate various markers within this pager piece:
// Middle is used to "center" pages around the current page.
$pager_middle = ceil($quantity / 2);
// current is the page we are currently paged to
$pager_current = $pager_page_array[$element] + 1;
// first is the first page listed by this pager piece (re quantity)
$pager_first = $pager_current - $pager_middle + 1;
// last is the last page listed by this pager piece (re quantity)
$pager_last = $pager_current + $quantity - $pager_middle;
// max is the maximum page number
$pager_max = $pager_total[$element];
// End of marker calculations.
// Prepare for generation loop.
$i = $pager_first;
if ($pager_last > $pager_max) {
// Adjust "center" if at end of query.
$i = $i + ($pager_max - $pager_last);
$pager_last = $pager_max;
}
if ($i <= 0) {
// Adjust "center" if at start of query.
$pager_last = $pager_last + (1 - $i);
$i = 1;
}
// End of generation loop preparation.
$li_first = theme('pager_first', array('text' => (isset($tags[0]) ? $tags[0] : t('« first')), 'element' => $element, 'parameters' => $parameters));
$li_previous = theme('pager_previous', array('text' => (isset($tags[1]) ? $tags[1] : t(' previous')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
$li_next = theme('pager_next', array('text' => (isset($tags[3]) ? $tags[3] : t('next ')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
$li_last = theme('pager_last', array('text' => (isset($tags[4]) ? $tags[4] : t('last »')), 'element' => $element, 'parameters' => $parameters));
if ($pager_total[$element] > 1) {
if ($li_first) {
$items[] = array(
'class' => array('pager-first'),
'data' => $li_first,
);
}
if ($li_previous) {
$items[] = array(
'class' => array('pager-previous'),
'data' => $li_previous,
);
}
// When there is more than one page, create the pager list.
if ($i != $pager_max) {
if ($i > 1) {
$items[] = array(
'class' => array('pager-ellipsis'),
'data' => '…',
);
}
// Now generate the actual pager piece.
for (; $i <= $pager_last && $i <= $pager_max; $i++) {
if ($i < $pager_current) {
$items[] = array(
'class' => array('pager-item'),
'data' => theme('pager_previous', array('text' => $i, 'element' => $element, 'interval' => ($pager_current - $i), 'parameters' => $parameters)),
);
}
if ($i == $pager_current) {
$items[] = array(
'class' => array('pager-current'),
'data' => $i,
);
}
if ($i > $pager_current) {
$items[] = array(
'class' => array('pager-item'),
'data' => theme('pager_next', array('text' => $i, 'element' => $element, 'interval' => ($i - $pager_current), 'parameters' => $parameters)),
);
}
}
if ($i < $pager_max) {
$items[] = array(
'class' => array('pager-ellipsis'),
'data' => '…',
);
}
}
// End generation.
if ($li_next) {
$items[] = array(
'class' => array('pager-next'),
'data' => $li_next,
);
}
if ($li_last) {
$items[] = array(
'class' => array('pager-last'),
'data' => $li_last,
);
}
return '<h2 class="element-invisible">' . t('Pages') . '</h2>' . theme('item_list', array('items' => $items, 'title' => NULL, 'type' => 'ul', 'attributes' => array('class' => array('pager'))));
}
}
/**
* @name Pager pieces
* @{
* Use these pieces to construct your own custom pagers in your theme. Note that
* you should NOT modify this file to customize your pager.
*/
/**
* Format a "first page" link.
*
* @param $variables
* An associative array containing:
* - text: The name (or image) of the link.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - parameters: An associative array of query string parameters to append to
* the pager links.
*
* @return
* An HTML string that generates this piece of the query pager.
*
* @ingroup themeable
*/
function theme_pager_first($variables) {
$text = $variables['text'];
$element = $variables['element'];
$parameters = $variables['parameters'];
global $pager_page_array;
$output = '';
// If we are anywhere but the first page
if ($pager_page_array[$element] > 0) {
$output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array(0, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
}
return $output;
}
/**
* Format a "previous page" link.
*
* @param $variables
* An associative array containing:
* - text: The name (or image) of the link.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - interval: The number of pages to move backward when the link is clicked.
* - parameters: An associative array of query string parameters to append to
* the pager links.
*
* @return
* An HTML string that generates this piece of the query pager.
*
* @ingroup themeable
*/
function theme_pager_previous($variables) {
$text = $variables['text'];
$element = $variables['element'];
$interval = $variables['interval'];
$parameters = $variables['parameters'];
global $pager_page_array;
$output = '';
// If we are anywhere but the first page
if ($pager_page_array[$element] > 0) {
$page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
// If the previous page is the first page, mark the link as such.
if ($page_new[$element] == 0) {
$output = theme('pager_first', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
}
// The previous page is not the first page.
else {
$output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
}
}
return $output;
}
/**
* Format a "next page" link.
*
* @param $variables
* An associative array containing:
* - text: The name (or image) of the link.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - interval: The number of pages to move forward when the link is clicked.
* - parameters: An associative array of query string parameters to append to
* the pager links.
*
* @return
* An HTML string that generates this piece of the query pager.
*
* @ingroup themeable
*/
function theme_pager_next($variables) {
$text = $variables['text'];
$element = $variables['element'];
$interval = $variables['interval'];
$parameters = $variables['parameters'];
global $pager_page_array, $pager_total;
$output = '';
// If we are anywhere but the last page
if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
$page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
// If the next page is the last page, mark the link as such.
if ($page_new[$element] == ($pager_total[$element] - 1)) {
$output = theme('pager_last', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
}
// The next page is not the last page.
else {
$output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
}
}
return $output;
}
/**
* Format a "last page" link.
*
* @param $variables
* An associative array containing:
* - text: The name (or image) of the link.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - parameters: An associative array of query string parameters to append to
* the pager links.
*
* @return
* An HTML string that generates this piece of the query pager.
*
* @ingroup themeable
*/
function theme_pager_last($variables) {
$text = $variables['text'];
$element = $variables['element'];
$parameters = $variables['parameters'];
global $pager_page_array, $pager_total;
$output = '';
// If we are anywhere but the last page
if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
$output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
}
return $output;
}
/**
* Format a link to a specific query result page.
*
* @param $variables
* An associative array containing:
* - page_new: The first result to display on the linked page.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - parameters: An associative array of query string parameters to append to
* the pager link.
* - attributes: An associative array of HTML attributes to apply to a pager
* anchor tag.
*
* @return
* An HTML string that generates the link.
*
* @ingroup themeable
*/
function theme_pager_link($variables) {
$text = $variables['text'];
$page_new = $variables['page_new'];
$element = $variables['element'];
$parameters = $variables['parameters'];
$attributes = $variables['attributes'];
$page = isset($_GET['page']) ? $_GET['page'] : '';
if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
$parameters['page'] = $new_page;
}
$query = array();
if (count($parameters)) {
$query = drupal_get_query_parameters($parameters, array());
}
if ($query_pager = pager_get_query_parameters()) {
$query = array_merge($query, $query_pager);
}
// Set each pager link title
if (!isset($attributes['title'])) {
static $titles = NULL;
if (!isset($titles)) {
$titles = array(
t('« first') => t('Go to first page'),
t(' previous') => t('Go to previous page'),
t('next ') => t('Go to next page'),
t('last »') => t('Go to last page'),
);
}
if (isset($titles[$text])) {
$attributes['title'] = $titles[$text];
}
elseif (is_numeric($text)) {
$attributes['title'] = t('Go to page @number', array('@number' => $text));
}
}
return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => $query));
}
/**
* @} End of "Pager pieces".
*/
/**
* Helper function
*
* Copies $old_array to $new_array and sets $new_array[$element] = $value
* Fills in $new_array[0 .. $element - 1] = 0
*/
function pager_load_array($value, $element, $old_array) {
$new_array = $old_array;
// Look for empty elements.
for ($i = 0; $i < $element; $i++) {
if (empty($new_array[$i])) {
// Load found empty element with 0.
$new_array[$i] = 0;
}
}
// Update the changed element.
$new_array[$element] = (int)$value;
return $new_array;
}