Commit b7ace862 authored by dawehner's avatar dawehner

#586668: Pagers turned into plugins to allow all kinds of new fun stuff with paging.

#652712 by dagmar: Some pager settings were not getting properly stored with pluggable pagers.
parent 4007ecf5
......@@ -79,6 +79,8 @@ Views 3.x-7.x-dev
o #627402 by dereine: Aggregator description needed more controllable input filtering.
o #699200 by xgretsch: Non-static functions cause strict warning messages.
o #705668 by dereine: Fix glossary view, add tests for glossary view.
o #586668: Pagers turned into plugins to allow all kinds of new fun stuff with paging.
o #652712 by dagmar: Some pager settings were not getting properly stored with pluggable pagers.
Views 6.x-3.x-dev
o #396380 by merlinofchaos, dereine and dagmar: Initial support for GROUP BY queries!!!!!!!!!!!!
......
......@@ -29,10 +29,10 @@ class views_handler_field_counter extends views_handler_field {
// Note: 1 is subtracted from the counter start value below because the
// counter value is incremented by 1 at the end of this function.
$count = is_numeric($this->options['counter_start']) ? $this->options['counter_start'] - 1 : 0;
$pager = $this->view->pager;
$pager = $this->view->query->pager;
// Get the base count of the pager.
if ($pager['use_pager']) {
$count += ($pager['items_per_page'] * $pager['current_page']) + $pager['offset'];
if ($pager->use_pager()) {
$count += ($pager->get_items_per_page() * $pager->get_current_page() + $pager->get_offset());
}
// Add the counter for the current site.
$count += $this->view->row_index + 1;
......
......@@ -259,6 +259,42 @@ function views_views_plugins() {
'help topic' => 'exposed-form-input-required',
),
),
'pager' => array(
'parent' => array(
'no ui' => TRUE,
'handler' => 'views_plugin_pager',
'parent' => '',
),
'none' => array(
'title' => t('Display all items'),
'help' => t("Display all items that this view might find"),
'handler' => 'views_plugin_pager_none',
'help topic' => 'pager-none',
'uses options' => TRUE,
),
'some' => array(
'title' => t('Display a specified number of items'),
'help' => t('Display a limited number items that this view might find.'),
'handler' => 'views_plugin_pager_some',
'help topic' => 'pager-some',
'uses options' => TRUE,
),
'full' => array(
'title' => t('Paged output, full pager'),
'help' => t('Paged output, full Drupal style'),
'handler' => 'views_plugin_pager_full',
'help topic' => 'pager-full',
'uses options' => TRUE,
),
'mini' => array(
'title' => t('Paged output, mini pager'),
'help' => t('Use the mini pager output.'),
'handler' => 'views_plugin_pager_mini',
'help topic' => 'pager-mini',
'uses options' => TRUE,
'parent' => 'full',
),
),
);
}
......
......@@ -32,14 +32,8 @@ class view extends views_db_object {
// Where the results of a query will go.
var $result = array();
// pager variables
var $pager = array(
'use_pager' => FALSE,
'items_per_page' => 10,
'element' => 0,
'offset' => 0,
'current_page' => 0,
);
// May be used to override the current page number.
var $current_page = NULL;
// Places to put attached renderings:
var $attachment_before = '';
......@@ -93,43 +87,38 @@ class view extends views_db_object {
$this->args = $args;
}
/**
* Set the page size for ranged or pager queries
*/
function set_items_per_page($items_per_page) {
$this->pager['items_per_page'] = $items_per_page;
if (empty($items_per_page)) {
$this->pager['use_pager'] = FALSE;
}
}
/**
* Change/Set the current page for the pager.
*/
function set_current_page($page) {
$this->pager['current_page'] = $page;
$this->current_page = $page;
}
/**
* Whether or not the pager should be used.
* Get the current page from the pager.
*/
function set_use_pager($use_pager) {
$this->pager['use_pager'] = $use_pager;
function get_current_page() {
if (!empty($this->query->pager)) {
return $this->query->pager->get_current_page();
}
}
/**
* The pager element id to use if use_apger is on
* Get the items per page from the pager.
*/
function set_pager_element($pager_element) {
$this->pager['element'] = $pager_element;
function get_items_per_page() {
if (!empty($this->query->pager)) {
return $this->query->pager->get_items_per_page();
}
}
/**
* How many records to skip. This does not function if use_pager is
* set.
* Get the pager offset from the pager.
*/
function set_offset($offset) {
$this->pager['offset'] = $offset;
function get_offset() {
if (!empty($this->query->pager)) {
return $this->query->pager->get_offset();
}
}
/**
......
......@@ -68,7 +68,7 @@ class views_plugin_cache extends views_plugin {
$data = array(
'result' => $this->view->result,
'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
'pager' => $this->view->pager,
'current_page' => $this->view->get_current_page(),
);
cache_set($this->get_results_key(), $data, $this->table);
break;
......@@ -94,12 +94,12 @@ class views_plugin_cache extends views_plugin {
return FALSE;
case 'results':
// Values to set: $view->result, $view->total_rows, $view->execute_time,
// $view->pager['current_page'].
// $view->current_page.
if ($cache = cache_get($this->get_results_key(), $this->table)) {
if (!$cutoff || $cache->created > $cutoff) {
$this->view->result = $cache->data['result'];
$this->view->total_rows = $cache->data['total_rows'];
$this->view->pager = $cache->data['pager'];
$this->view->set_current_page = $cache->data['current_page'];
$this->view->execute_time = 0;
return TRUE;
}
......
This diff is collapsed.
......@@ -14,12 +14,11 @@ class views_plugin_exposed_form extends views_plugin {
* @param $display
* The display handler.
*/
function init(&$view, &$display) {
function init(&$view, &$display, $options = array()) {
$this->view = &$view;
$this->display = &$display;
$exposed_form = $display->handler->get_option('exposed_form');
$this->unpack_options($this->options, $exposed_form['options']);
$this->unpack_options($this->options, $options);
}
/**
......@@ -108,9 +107,9 @@ class views_plugin_exposed_form extends views_plugin {
);
}
}
function exposed_form_validate(&$form, &$form_state) { }
/**
* This function is executed when exposed form is submited.
*
......@@ -119,9 +118,9 @@ class views_plugin_exposed_form extends views_plugin {
* @param $form_state
* A keyed array containing the current state of the form.
* @param $exclude
* Nested array of keys to exclude of insert into
* Nested array of keys to exclude of insert into
* $view->exposed_raw_input
*/
*/
function exposed_form_submit(&$form, &$form_state, &$exclude) {
if (isset($form_state['values']['op']) && $form_state['values']['op'] == t('Reset')) {
$this->reset_form($form, $form_state);
......
<?php
// $Id$
/**
* The base plugin to handle pager.
*
* @ingroup views_pager_plugins
*/
class views_plugin_pager extends views_plugin {
var $current_page = NULL;
var $total_items = 0;
/**
* Initialize the plugin.
*
* @param $view
* The view object.
* @param $display
* The display handler.
*/
function init(&$view, &$display, $options = array()) {
$this->view = &$view;
$this->display = &$display;
$this->unpack_options($this->options, $options);
}
/**
* Get how many items per page this pager will display.
*
* All but the leanest pagers should probably return a value here, so
* most pagers will not need to override this method.
*/
function get_items_per_page() {
return isset($this->options['items_per_page']) ? $this->options['items_per_page'] : 0;
}
/**
* Get the page offset, or how many items to skip.
*
* Even pagers that don't actually page can skip items at the beginning,
* so few pagers will need to override this method.
*/
function get_offset() {
return isset($this->options['offset']) ? $this->options['offset'] : 0;
}
/**
* Get the current page.
*
* If NULL, we do not know what the current page is.
*/
function get_current_page() {
return $this->current_page;
}
/**
* Set the current page.
*
* @param $number
* If provided, the page number will be set to this. If NOT provided,
* the page number will be set from the global page array.
*/
function set_current_page($number = NULL) {
$this->current_page = $number;
}
/**
* Get the total number of items.
*
* If NULL, we do not yet know what the total number of items are.
*/
function get_total_items() {
return $this->total_items;
}
/**
* Provide the default form form for validating options
*/
function options_validate(&$form, &$form_state) { }
/**
* Provide the default form form for submitting options
*/
function options_submit(&$form, &$form_state) { }
/**
* Return a string to display as the clickable title for the
* pager plugin.
*/
function summary_title() {
return t('Unknown');
}
/**
* Determine if this pager actually uses a pager.
*
* Only a couple of very specific pagers will set this to false.
*/
function use_pager() {
return TRUE;
}
/**
* Determine if a pager needs a count query.
*
* If a pager needs a count query, a simple query
*/
function use_count_query() {
return TRUE;
}
/**
* Execute the count query, which will be done just prior to the query
* itself being executed.
*/
function execute_count_query(&$count_query) {
$this->total_items = $count_query->execute();
if (!empty($this->options['offset'])) {
$this->total_items -= $this->options['offset'];
}
$this->update_page_info();
return $this->total_items;
}
/**
* If there are pagers that need global values set, this method can
* be used to set them. It will be called when the count query is run.
*/
function update_page_info() {
}
/**
* Modify the query for paging
*
* This is called during the build phase and can directly modify the query.
*/
function query() { }
/**
* Perform any needed actions just prior to the query executing.
*/
function pre_execute(&$query) { }
/**
* Perform any needed actions just after the query executing.
*/
function post_execute(&$result) { }
/**
* Render the pager.
*
* Called during the view render process, this will render the
* pager.
*
* @param $input
* Any extra GET parameters that should be retained, such as exposed
* input.
*/
function render($input) { }
/**
* Determine if there are more records available.
*
* This is primarily used to control the display of a more link.
*/
function has_more_records() {
return $this->get_items_per_page()
&& $this->total_items > (intval($this->current_page) + 1) * $this->get_items_per_page();
}
}
<?php
// $Id$
/**
* The plugin to handle full pager.
*
* @ingroup views_pager_plugins
*/
class views_plugin_pager_full extends views_plugin_pager {
function summary_title() {
if (!empty($this->options['offset'])) {
return format_plural($this->options['items_per_page'], 'Paged, @count item, skip @skip', 'Paged, @count items, skip @skip', array('@count' => $this->options['items_per_page'], '@skip' => $this->options['offset']));
}
return format_plural($this->options['items_per_page'], 'Paged, @count item', 'Paged, @count items', array('@count' => $this->options['items_per_page']));
}
function option_definition() {
$options = parent::option_definition();
$options['items_per_page'] = array('default' => 10);
$options['offset'] = array('default' => 0);
$options['id'] = array('default' => 0);
return $options;
}
/**
* Provide the default form for setting options.
*/
function options_form(&$form, &$form_state) {
$form['items_per_page'] = array(
'#title' => t('Items per page'),
'#type' => 'textfield',
'#description' => t('The number of items to display per page. Enter 0 for no limit.'),
'#default_value' => $this->options['items_per_page'],
);
$form['offset'] = array(
'#type' => 'textfield',
'#title' => t('Offset'),
'#description' => t('The number of items to skip. For example, if this field is 3, the first 3 items will be skipped and not displayed.'),
'#default_value' => $this->options['offset'],
);
$form['id'] = array(
'#type' => 'textfield',
'#title' => t('Pager ID'),
'#description' => t("Unless you're experiencing problems with pagers related to this view, you should leave this at 0. If using multiple pagers on one page you may need to set this number to a higher value so as not to conflict within the ?page= array. Large values will add a lot of commas to your URLs, so avoid if possible."),
'#default_value' => $this->options['id'],
);
}
function query() {
$this->view->query->set_limit($this->options['items_per_page']);
$this->view->query->set_offset($this->current_page * $this->options['items_per_page'] + $this->options['offset']);
}
function render($input) {
$pager_theme = views_theme_functions('pager', $this->view, $this->display);
$output = theme($pager_theme, array(
'tags' => $input, 'quantity' => $this->options['items_per_page'], 'element' => $this->options['id']));
return $output;
}
/**
* Set the current page.
*
* @param $number
* If provided, the page number will be set to this. If NOT provided,
* the page number will be set from the global page array.
*/
function set_current_page($number = NULL) {
if (isset($number)) {
$this->current_page = $number;
return;
}
// If the current page number was not prespecified, default to pulling it from 'page'
// based upon
global $pager_page_array;
// Extract the ['page'] info.
$pager_page_array = isset($_GET['page']) ? explode(',', $_GET['page']) : array();
$this->current_page = 0;
if (!empty($pager_page_array[$this->options['id']])) {
$this->current_page = intval($pager_page_array[$this->options['id']]);
}
}
/**
* Update global paging info.
*
* This is called after the count query has been run to set the total
* items available and to update the current page if the requested
* page is out of range.
*/
function update_page_info() {
// Dump information about what we already know into the globals.
global $pager_page_array, $pager_total, $pager_total_items;
// Set the item count for the pager.
$pager_total_items[$this->options['id']] = $this->total_items;
// Calculate and set the count of available pages.
$pager_total[$this->options['id']] = ceil($pager_total_items[$this->options['id']] / $this->get_items_per_page());
// See if the requested page was within range:
if ($this->current_page < 0) {
$this->current_page = 0;
}
else if ($this->current_page >= $pager_total[$this->options['id']]) {
// Pages are numbered from 0 so if there are 10 pages, the last page is 9.
$this->current_page = $pager_total[$this->options['id']] - 1;
}
// Put this number in to guarantee that we do not generate notices when the pager
// goes to look for it later.
$pager_page_array[$this->options['id']] = $this->current_page;
}
}
<?php
// $Id$
/**
* The plugin to handle full pager.
*
* @ingroup views_pager_plugins
*/
class views_plugin_pager_mini extends views_plugin_pager_full {
function summary_title() {
if (!empty($this->options['offset'])) {
return format_plural($this->options['items_per_page'], 'Mini pager, @count item, skip @skip', 'Mini pager, @count items, skip @skip', array('@count' => $this->options['items_per_page'], '@skip' => $this->options['offset']));
}
return format_plural($this->options['items_per_page'], 'Mini pager, @count item', 'Mini pager, @count items', array('@count' => $this->options['items_per_page']));
}
function render($input) {
$pager_theme = views_theme_functions('views_mini_pager', $this->view, $this->display);
return theme($pager_theme, array(
'tags' => $input, 'quantity' => $this->options['items_per_page'], 'element' => $this->options['id']));
}
}
<?php
// $Id$
/**
* Plugin for views without pagers.
*
* @ingroup views_pager_plugins
*/
class views_plugin_pager_none extends views_plugin_pager {
function summary_title() {
if (!empty($this->options['offset'])) {
return t('All items, skip @skip', array('@skip' => $this->options['offset']));
}
return t('All items');
}
function option_definition() {
$options = parent::option_definition();
$options['offset'] = array('default' => 0);
return $options;
}
/**
* Provide the default form for setting options.
*/
function options_form(&$form, &$form_state) {
$form['offset'] = array(
'#type' => 'textfield',
'#title' => t('Offset'),
'#description' => t('The number of items to skip. For example, if this field is 3, the first 3 items will be skipped and not displayed.'),
'#default_value' => $this->options['offset'],
);
}
function use_pager() {
return FALSE;
}
function use_count_query() {
return FALSE;
}
function get_items_per_page() {
return 0;
}
function execute_count_query(&$count_query) {
// If we are displaying all items, never count. But we can update the count in post_execute.
}
function post_execute($result) {
$this->total_items = count($result);
}
function query() {
// The only query modifications we might do are offsets.
if (!empty($this->options['offset'])) {
$this->view->query->set_offset($this->options['offset']);
}
}
}
<?php
// $Id$
/**
* Plugin for views without pagers.
*
* @ingroup views_pager_plugins
*/
class views_plugin_pager_some extends views_plugin_pager {
function summary_title() {
if (!empty($this->options['offset'])) {
return format_plural($this->options['items_per_page'], '@count item, skip @skip', '@count items, skip @skip', array('@count' => $this->options['items_per_page'], '@skip' => $this->options['offset']));
}
return format_plural($this->options['items_per_page'], '@count item', '@count items', array('@count' => $this->options['items_per_page']));
}
function option_definition() {
$options = parent::option_definition();
$options['items_per_page'] = array('default' => 10);
$options['offset'] = array('default' => 0);
return $options;
}
/**
* Provide the default form for setting options.
*/
function options_form(&$form, &$form_state) {
$form['items_per_page'] = array(
'#title' => t('Items to display'),
'#type' => 'textfield',
'#description' => t('The number of items to display per page. Enter 0 for no limit.'),
'#default_value' => $this->options['items_per_page'],
);
$form['offset'] = array(
'#type' => 'textfield',
'#title' => t('Offset'),
'#description' => t('The number of items to skip. For example, if this field is 3, the first 3 items will be skipped and not displayed.'),
'#default_value' => $this->options['offset'],
);
}
function use_pager() {
return FALSE;
}
function use_count_query() {
return FALSE;
}
function query() {
$this->view->query->set_limit($this->options['items_per_page']);
$this->view->query->set_offset($this->options['offset']);
}
}
......@@ -9,6 +9,10 @@
* Object used to create a SELECT query.
*/
class views_plugin_query extends views_plugin {
/**
* A pager plugin that should be provided by the display.
*/
var $pager = NULL;
/**
* Constructor; Create the basic query object and fill with default values.
......@@ -57,4 +61,37 @@ class views_plugin_query extends views_plugin {
* If NULL, aggregation is not allowed.
*/
function get_aggregation_info() { }
/**
* Set a LIMIT on the query, specifying a maximum number of results.
*/
function set_limit($limit) {
$this->limit = $limit;
}
/**
* Set an OFFSET on the query, specifying a number of results to skip
*/
function set_offset($offset) {
$this->offset = $offset;
}
function init_pager(&$view) {
// @todo -- probably this should be a method on the display rather than directly calling
// get plugin?
$this->pager = $view->display_handler->get_plugin('pager');
}
/**
* Render the pager, if necessary.
*/
function render_pager() {
if (!empty($this->pager) && $this->pager->use_pager()) {
$exposed_input = isset($this->view->exposed_data_raw) ? $this->view->exposed_data_raw : NULL;
return $this->pager->render($exposed_input);
}
return '';
}
}
......@@ -1058,6 +1058,14 @@ class views_plugin_query_default extends views_plugin_query {
* Builds the necessary info to execute the query.
*/
function build(&$view) {
$this->init_pager($view);
if ($this->pager->use_pager()) {
$this->pager->set_current_page($view->current_page);
}
// Let the pager modify the query to add limits.
$this->pager->query();
$view->build_info['query'] = $this->query();
$view->build_info['count_query'] = $this->query(TRUE);
}
......@@ -1067,7 +1075,7 @@ class views_plugin_query_default extends views_plugin_query {
* values.
*
* Values to set: $view->result, $view->total_rows, $view->execute_time,
* $view->pager['current_page'].
* $view->current_page.
*/
function execute(&$view) {
$external = FALSE; // Whether this query will run against an external database.
......@@ -1080,6 +1088,9 @@ class views_plugin_query_default extends views_plugin_query {
// We have already build a working count query. Views optimeses it automatically.
//$count_query = $count_query->countQuery();
// against strict problems
$args = array();
// Add additional arguments as a fake condition.
// XXX: this doesn't work... because PDO mandates that all bound arguments
// are used on the query. TODO: Find a better way to do this.
......@@ -1095,39 +1106,19 @@ class views_plugin_query_default extends views_plugin_query {
}
$start = microtime(TRUE);
if (!empty($view->pager['items_per_page'])) {