Simpletest Coverage - includes/pager.inc

1 <?php
2 // $Id: pager.inc,v 1.69 2009/08/11 16:43:11 webchick Exp $
3
4 /**
5 * @file
6 * Functions to aid in presenting database results as a set of pages.
7 */
8
9
10 /**
11 * Query extender for pager queries.
12 *
13 * This is the "default" pager mechanism. It creates a paged query with a fixed
14 * number of entries per page.
15 */
16 class PagerDefault extends SelectQueryExtender {
17
18 /**
19 * The highest element we've autogenerated so far.
20 *
21 * @var int
22 */
23 static protected $maxElement = 0;
24
25 /**
26 * The number of elements per page to allow.
27 *
28 * @var int
29 */
30 protected $limit = 10;
31
32 /**
33 * The unique ID of this pager on this page.
34 *
35 * @var int
36 */
37 protected $element = NULL;
38
39 /**
40 * The count query that will be used for this pager.
41 *
42 * @var SelectQueryInterface
43 */
44 protected $customCountQuery = FALSE;
45
46 /**
47 * Override the execute method.
48 *
49 * Before we run the query, we need to add pager-based range() instructions
50 * to it.
51 */
52 public function execute() {
53 global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
54
55 // A NULL limit is the "kill switch" for pager queries.
56 if (empty($this->limit)) {
57 return;
58 }
59 $this->ensureElement();
60
61 $page = isset($_GET['page']) ? $_GET['page'] : '';
62
63 // Convert comma-separated $page to an array, used by other functions.
64 $pager_page_array = explode(',', $page);
65
66 if (!isset($pager_page_array[$this->element])) {
67 $pager_page_array[$this->element] = 0;
68 }
69
70 // We calculate the total of pages as ceil(items / limit).
71 $pager_total_items[$this->element] = $this->getCountQuery()->execute()->fetchField();
72 $pager_total[$this->element] = ceil($pager_total_items[$this->element] / $this->limit);
73 $pager_page_array[$this->element] = max(0, min((int)$pager_page_array[$this->element], ((int)$pager_total[$this->element]) - 1));
74 $pager_limits[$this->element] = $this->limit;
75 $this->range($pager_page_array[$this->element] * $this->limit, $this->limit);
76
77 // Now that we've added our pager-based range instructions, run the query normally.
78 return $this->query->execute();
79 }
80
81 /**
82 * Ensure that there is an element associated with this query.
83 *
84 * After running this query, access $this->element to get the element for this
85 * query.
86 */
87 protected function ensureElement() {
88 if (!empty($this->element)) {
89 return;
90 }
91
92 $this->element = self::$maxElement++;
93 }
94
95 /**
96 * Specify the count query object to use for this pager.
97 *
98 * You will rarely need to specify a count query directly. If not specified,
99 * one is generated off of the pager query itself.
100 *
101 * @param SelectQueryInterface $query
102 * The count query object. It must return a single row with a single column,
103 * which is the total number of records.
104 */
105 public function setCountQuery(SelectQueryInterface $query) {
106 $this->customCountQuery = $query;
107 }
108
109 /**
110 * Retrieve the count query for this pager.
111 *
112 * The count query may be specified manually or, by default, taken from the
113 * query we are extending.
114 *
115 * @return
116 * A count SelectQueryInterface object.
117 */
118 protected function getCountQuery() {
119 if ($this->customCountQuery) {
120 return $this->customCountQuery;
121 }
122 else {
123 return $this->query->countQuery();
124 }
125 }
126
127 /**
128 * Specify the maximum number of elements per page for this query.
129 *
130 * The default if not specified is 10 items per page.
131 *
132 * @param $limit
133 * An integer specifying the number of elements per page. If passed a false
134 * value (FALSE, 0, NULL), the pager is disabled.
135 */
136 public function limit($limit = 10) {
137 $this->limit = $limit;
138 return $this;
139 }
140
141 /**
142 * Specify the element ID for this pager query.
143 *
144 * The element is used to differentiate different pager queries on the same
145 * page so that they may be operated independently. If you do not specify an
146 * element, every pager query on the page will get a unique element. If for
147 * whatever reason you want to explicitly define an element for a given query,
148 * you may do so here.
149 *
150 * @param $element
151 */
152 public function element($element) {
153 $this->element = $element;
154 return $this;
155 }
156 }
157
158 /**
159 * Perform a paged database query.
160 *
161 * Use this function when doing select queries you wish to be able to page. The
162 * pager uses LIMIT-based queries to fetch only the records required to render a
163 * certain page. However, it has to learn the total number of records returned
164 * by the query to compute the number of pages (the number of records / records
165 * per page). This is done by inserting "COUNT(*)" in the original query. For
166 * example, the query "SELECT nid, type FROM node WHERE status = '1' ORDER BY
167 * sticky DESC, created DESC" would be rewritten to read "SELECT COUNT(*) FROM
168 * node WHERE status = '1' ORDER BY sticky DESC, created DESC". Rewriting the
169 * query is accomplished using a regular expression.
170 *
171 * Unfortunately, the rewrite rule does not always work as intended for queries
172 * that already have a "COUNT(*)" or a "GROUP BY" clause, and possibly for
173 * other complex queries. In those cases, you can optionally pass a query that
174 * will be used to count the records.
175 *
176 * For example, if you want to page the query "SELECT COUNT(*), TYPE FROM node
177 * GROUP BY TYPE", pager_query() would invoke the incorrect query "SELECT
178 * COUNT(*) FROM node GROUP BY TYPE". So instead, you should pass "SELECT
179 * COUNT(DISTINCT(TYPE)) FROM node" as the optional $count_query parameter.
180 *
181 * @param $query
182 * The SQL query that needs paging.
183 * @param $limit
184 * The number of query results to display per page.
185 * @param $element
186 * An optional integer to distinguish between multiple pagers on one page.
187 * @param $count_query
188 * An SQL query used to count matching records.
189 * @param ...
190 * A variable number of arguments which are substituted into the query (and
191 * the count query) using printf() syntax. Instead of a variable number of
192 * query arguments, you may also pass a single array containing the query
193 * arguments.
194 * @return
195 * A database query result resource, or FALSE if the query was not executed
196 * correctly.
197 *
198 * @ingroup database
199 */
200 function pager_query($query, $limit = 10, $element = 0, $count_query = NULL) {
201 global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
202 $page = isset($_GET['page']) ? $_GET['page'] : '';
203
204 // Substitute in query arguments.
205 $args = func_get_args();
206 $args = array_slice($args, 4);
207 // Alternative syntax for '...'
208 if (isset($args[0]) && is_array($args[0])) {
209 $args = $args[0];
210 }
211
212 // Construct a count query if none was given.
213 if (!isset($count_query)) {
214 $count_query = preg_replace(array('/SELECT.*?FROM /As', '/ORDER BY .*/'), array('SELECT COUNT(*) FROM ', ''), $query);
215 }
216
217 // Convert comma-separated $page to an array, used by other functions.
218 $pager_page_array = explode(',', $page);
219
220 // We calculate the total of pages as ceil(items / limit).
221 $pager_total_items[$element] = db_query($count_query, $args)->fetchField();
222 $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
223 $pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1));
224 $pager_limits[$element] = $limit;
225 return db_query_range($query, $args, $pager_page_array[$element] * $limit, $limit);
226 }
227
228 /**
229 * Compose a query string to append to pager requests.
230 *
231 * @return
232 * A query string that consists of all components of the current page request
233 * except for those pertaining to paging.
234 */
235 function pager_get_querystring() {
236 static $string = NULL;
237 if (!isset($string)) {
238 $string = drupal_query_string_encode($_REQUEST, array_merge(array('q', 'page'), array_keys($_COOKIE)));
239 }
240 return $string;
241 }
242
243 /**
244 * Format a query pager.
245 *
246 * Menu callbacks that display paged query results should call theme('pager') to
247 * retrieve a pager control so that users can view other results.
248 * Format a list of nearby pages with additional query results.
249 *
250 * @param $tags
251 * An array of labels for the controls in the pager.
252 * @param $element
253 * An optional integer to distinguish between multiple pagers on one page.
254 * @param $parameters
255 * An associative array of query string parameters to append to the pager links.
256 * @param $quantity
257 * The number of pages in the list.
258 * @return
259 * An HTML string that generates the query pager.
260 *
261 * @ingroup themeable
262 */
263 function theme_pager($tags = array(), $element = 0, $parameters = array(), $quantity = 9) {
264 global $pager_page_array, $pager_total;
265
266 // Calculate various markers within this pager piece:
267 // Middle is used to "center" pages around the current page.
268 $pager_middle = ceil($quantity / 2);
269 // current is the page we are currently paged to
270 $pager_current = $pager_page_array[$element] + 1;
271 // first is the first page listed by this pager piece (re quantity)
272 $pager_first = $pager_current - $pager_middle + 1;
273 // last is the last page listed by this pager piece (re quantity)
274 $pager_last = $pager_current + $quantity - $pager_middle;
275 // max is the maximum page number
276 $pager_max = $pager_total[$element];
277 // End of marker calculations.
278
279 // Prepare for generation loop.
280 $i = $pager_first;
281 if ($pager_last > $pager_max) {
282 // Adjust "center" if at end of query.
283 $i = $i + ($pager_max - $pager_last);
284 $pager_last = $pager_max;
285 }
286 if ($i <= 0) {
287 // Adjust "center" if at start of query.
288 $pager_last = $pager_last + (1 - $i);
289 $i = 1;
290 }
291 // End of generation loop preparation.
292
293 $li_first = theme('pager_first', (isset($tags[0]) ? $tags[0] : t('« first')), $element, $parameters);
294 $li_previous = theme('pager_previous', (isset($tags[1]) ? $tags[1] : t('‹ previous')), $element, 1, $parameters);
295 $li_next = theme('pager_next', (isset($tags[3]) ? $tags[3] : t('next ›')), $element, 1, $parameters);
296 $li_last = theme('pager_last', (isset($tags[4]) ? $tags[4] : t('last »')), $element, $parameters);
297
298 if ($pager_total[$element] > 1) {
299 if ($li_first) {
300 $items[] = array(
301 'class' => 'pager-first',
302 'data' => $li_first,
303 );
304 }
305 if ($li_previous) {
306 $items[] = array(
307 'class' => 'pager-previous',
308 'data' => $li_previous,
309 );
310 }
311
312 // When there is more than one page, create the pager list.
313 if ($i != $pager_max) {
314 if ($i > 1) {
315 $items[] = array(
316 'class' => 'pager-ellipsis',
317 'data' => '…',
318 );
319 }
320 // Now generate the actual pager piece.
321 for (; $i <= $pager_last && $i <= $pager_max; $i++) {
322 if ($i < $pager_current) {
323 $items[] = array(
324 'class' => 'pager-item',
325 'data' => theme('pager_previous', $i, $element, ($pager_current - $i), $parameters),
326 );
327 }
328 if ($i == $pager_current) {
329 $items[] = array(
330 'class' => 'pager-current',
331 'data' => $i,
332 );
333 }
334 if ($i > $pager_current) {
335 $items[] = array(
336 'class' => 'pager-item',
337 'data' => theme('pager_next', $i, $element, ($i - $pager_current), $parameters),
338 );
339 }
340 }
341 if ($i < $pager_max) {
342 $items[] = array(
343 'class' => 'pager-ellipsis',
344 'data' => '…',
345 );
346 }
347 }
348 // End generation.
349 if ($li_next) {
350 $items[] = array(
351 'class' => 'pager-next',
352 'data' => $li_next,
353 );
354 }
355 if ($li_last) {
356 $items[] = array(
357 'class' => 'pager-last',
358 'data' => $li_last,
359 );
360 }
361 return theme('item_list', $items, NULL, 'ul', array('class' => 'pager'));
362 }
363 }
364
365
366 /**
367 * @name Pager pieces
368 * @{
369 * Use these pieces to construct your own custom pagers in your theme. Note that
370 * you should NOT modify this file to customize your pager.
371 */
372
373 /**
374 * Format a "first page" link.
375 *
376 * @param $text
377 * The name (or image) of the link.
378 * @param $element
379 * An optional integer to distinguish between multiple pagers on one page.
380 * @param $parameters
381 * An associative array of query string parameters to append to the pager links.
382 * @return
383 * An HTML string that generates this piece of the query pager.
384 *
385 * @ingroup themeable
386 */
387 function theme_pager_first($text, $element = 0, $parameters = array()) {
388 global $pager_page_array;
389 $output = '';
390
391 // If we are anywhere but the first page
392 if ($pager_page_array[$element] > 0) {
393 $output = theme('pager_link', $text, pager_load_array(0, $element, $pager_page_array), $element, $parameters);
394 }
395
396 return $output;
397 }
398
399 /**
400 * Format a "previous page" link.
401 *
402 * @param $text
403 * The name (or image) of the link.
404 * @param $element
405 * An optional integer to distinguish between multiple pagers on one page.
406 * @param $interval
407 * The number of pages to move backward when the link is clicked.
408 * @param $parameters
409 * An associative array of query string parameters to append to the pager links.
410 * @return
411 * An HTML string that generates this piece of the query pager.
412 *
413 * @ingroup themeable
414 */
415 function theme_pager_previous($text, $element = 0, $interval = 1, $parameters = array()) {
416 global $pager_page_array;
417 $output = '';
418
419 // If we are anywhere but the first page
420 if ($pager_page_array[$element] > 0) {
421 $page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
422
423 // If the previous page is the first page, mark the link as such.
424 if ($page_new[$element] == 0) {
425 $output = theme('pager_first', $text, $element, $parameters);
426 }
427 // The previous page is not the first page.
428 else {
429 $output = theme('pager_link', $text, $page_new, $element, $parameters);
430 }
431 }
432
433 return $output;
434 }
435
436 /**
437 * Format a "next page" link.
438 *
439 * @param $text
440 * The name (or image) of the link.
441 * @param $element
442 * An optional integer to distinguish between multiple pagers on one page.
443 * @param $interval
444 * The number of pages to move forward when the link is clicked.
445 * @param $parameters
446 * An associative array of query string parameters to append to the pager links.
447 * @return
448 * An HTML string that generates this piece of the query pager.
449 *
450 * @ingroup themeable
451 */
452 function theme_pager_next($text, $element = 0, $interval = 1, $parameters = array()) {
453 global $pager_page_array, $pager_total;
454 $output = '';
455
456 // If we are anywhere but the last page
457 if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
458 $page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
459 // If the next page is the last page, mark the link as such.
460 if ($page_new[$element] == ($pager_total[$element] - 1)) {
461 $output = theme('pager_last', $text, $element, $parameters);
462 }
463 // The next page is not the last page.
464 else {
465 $output = theme('pager_link', $text, $page_new, $element, $parameters);
466 }
467 }
468
469 return $output;
470 }
471
472 /**
473 * Format a "last page" link.
474 *
475 * @param $text
476 * The name (or image) of the link.
477 * @param $element
478 * An optional integer to distinguish between multiple pagers on one page.
479 * @param $parameters
480 * An associative array of query string parameters to append to the pager links.
481 * @return
482 * An HTML string that generates this piece of the query pager.
483 *
484 * @ingroup themeable
485 */
486 function theme_pager_last($text, $element = 0, $parameters = array()) {
487 global $pager_page_array, $pager_total;
488 $output = '';
489
490 // If we are anywhere but the last page
491 if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
492 $output = theme('pager_link', $text, pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), $element, $parameters);
493 }
494
495 return $output;
496 }
497
498
499 /**
500 * Format a link to a specific query result page.
501 *
502 * @param $page_new
503 * The first result to display on the linked page.
504 * @param $element
505 * An optional integer to distinguish between multiple pagers on one page.
506 * @param $parameters
507 * An associative array of query string parameters to append to the pager link.
508 * @param $attributes
509 * An associative array of HTML attributes to apply to a pager anchor tag.
510 * @return
511 * An HTML string that generates the link.
512 *
513 * @ingroup themeable
514 */
515 function theme_pager_link($text, $page_new, $element, $parameters = array(), $attributes = array()) {
516 $page = isset($_GET['page']) ? $_GET['page'] : '';
517 if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
518 $parameters['page'] = $new_page;
519 }
520
521 $query = array();
522 if (count($parameters)) {
523 $query[] = drupal_query_string_encode($parameters, array());
524 }
525 $querystring = pager_get_querystring();
526 if ($querystring != '') {
527 $query[] = $querystring;
528 }
529
530 // Set each pager link title
531 if (!isset($attributes['title'])) {
532 static $titles = NULL;
533 if (!isset($titles)) {
534 $titles = array(
535 t('« first') => t('Go to first page'),
536 t('‹ previous') => t('Go to previous page'),
537 t('next ›') => t('Go to next page'),
538 t('last »') => t('Go to last page'),
539 );
540 }
541 if (isset($titles[$text])) {
542 $attributes['title'] = $titles[$text];
543 }
544 elseif (is_numeric($text)) {
545 $attributes['title'] = t('Go to page @number', array('@number' => $text));
546 }
547 }
548
549 return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => count($query) ? implode('&', $query) : NULL));
550 }
551
552 /**
553 * @} End of "Pager pieces".
554 */
555
556 /**
557 * Helper function
558 *
559 * Copies $old_array to $new_array and sets $new_array[$element] = $value
560 * Fills in $new_array[0 .. $element - 1] = 0
561 */
562 function pager_load_array($value, $element, $old_array) {
563 $new_array = $old_array;
564 // Look for empty elements.
565 for ($i = 0; $i < $element; $i++) {
566 if (!$new_array[$i]) {
567 // Load found empty element with 0.
568 $new_array[$i] = 0;
569 }
570 }
571 // Update the changed element.
572 $new_array[$element] = (int)$value;
573 return $new_array;
574 }
575

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.