cff8da8936c3e358a7a4caecd490238733713b58
[plewww.git] / includes / pager.inc
1 <?php
2 // $Id: pager.inc 144 2007-03-28 07:52:20Z thierry $
3
4 /**
5  * @file
6  * Functions to aid in presenting database results as a set of pages.
7  */
8
9 /**
10  * Perform a paged database query.
11  *
12  * Use this function when doing select queries you wish to be able to page. The
13  * pager uses LIMIT-based queries to fetch only the records required to render a
14  * certain page. However, it has to learn the total number of records returned
15  * by the query to compute the number of pages (the number of records / records
16  * per page). This is done by inserting "COUNT(*)" in the original query. For
17  * example, the query "SELECT nid, type FROM node WHERE status = '1' ORDER BY
18  * sticky DESC, created DESC" would be rewritten to read "SELECT COUNT(*) FROM
19  * node WHERE status = '1' ORDER BY sticky DESC, created DESC". Rewriting the
20  * query is accomplished using a regular expression.
21  *
22  * Unfortunately, the rewrite rule does not always work as intended for queries
23  * that already have a "COUNT(*)" or a "GROUP BY" clause, and possibly for
24  * other complex queries. In those cases, you can optionally pass a query that
25  * will be used to count the records.
26  *
27  * For example, if you want to page the query "SELECT COUNT(*), TYPE FROM node
28  * GROUP BY TYPE", pager_query() would invoke the incorrect query "SELECT
29  * COUNT(*) FROM node GROUP BY TYPE". So instead, you should pass "SELECT
30  * COUNT(DISTINCT(TYPE)) FROM node" as the optional $count_query parameter.
31  *
32  * @param $query
33  *   The SQL query that needs paging.
34  * @param $limit
35  *   The number of query results to display per page.
36  * @param $element
37  *   An optional integer to distinguish between multiple pagers on one page.
38  * @param $count_query
39  *   An SQL query used to count matching records.
40  * @param ...
41  *   A variable number of arguments which are substituted into the query (and
42  *   the count query) using printf() syntax. Instead of a variable number of
43  *   query arguments, you may also pass a single array containing the query
44  *   arguments.
45  * @return
46  *   A database query result resource, or FALSE if the query was not executed
47  *   correctly.
48  *
49  * @ingroup database
50  */
51 function pager_query($query, $limit = 10, $element = 0, $count_query = NULL) {
52   global $pager_page_array, $pager_total, $pager_total_items;
53   $page = isset($_GET['page']) ? $_GET['page'] : '';
54
55   // Substitute in query arguments.
56   $args = func_get_args();
57   $args = array_slice($args, 4);
58   // Alternative syntax for '...'
59   if (isset($args[0]) && is_array($args[0])) {
60     $args = $args[0];
61   }
62
63   // Construct a count query if none was given.
64   if (!isset($count_query)) {
65     $count_query = preg_replace(array('/SELECT.*?FROM/As', '/ORDER BY .*/'), array('SELECT COUNT(*) FROM', ''), $query);
66   }
67
68   // Convert comma-separated $page to an array, used by other functions.
69   $pager_page_array = explode(',', $page);
70
71   // We calculate the total of pages as ceil(items / limit).
72   if (count($args)) {
73     $pager_total_items[$element] = db_result(db_query($count_query, $args));
74     $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
75     $pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1));
76     return db_query_range($query, $args, $pager_page_array[$element] * $limit, $limit);
77   }
78   else {
79     $pager_total_items[$element] = db_result(db_query($count_query));
80     $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
81     $pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1));
82     return db_query_range($query, $pager_page_array[$element] * $limit, $limit);
83   }
84 }
85
86 /**
87  * Compose a query string to append to pager requests.
88  *
89  * @return
90  *   A query string that consists of all components of the current page request
91  *   except for those pertaining to paging.
92  */
93 function pager_get_querystring() {
94   static $string = NULL;
95   if (!isset($string)) {
96     $string = drupal_query_string_encode($_REQUEST, array_merge(array('q', 'page'), array_keys($_COOKIE)));
97   }
98   return $string;
99 }
100
101 /**
102  * Format a query pager.
103  *
104  * Menu callbacks that display paged query results should call theme('pager') to
105  * retrieve a pager control so that users can view other results.
106  *
107  * @param $tags
108  *   An array of labels for the controls in the pager.
109  * @param $limit
110  *   The number of query results to display per page.
111  * @param $element
112  *   An optional integer to distinguish between multiple pagers on one page.
113  * @param $parameters
114  *   An associative array of query string parameters to append to the pager links.
115  * @return
116  *   An HTML string that generates the query pager.
117  *
118  * @ingroup themeable
119  */
120 function theme_pager($tags = array(), $limit = 10, $element = 0, $parameters = array()) {
121   global $pager_total;
122   $output = '';
123
124   if ($pager_total[$element] > 1) {
125     $output .= '<div id="pager">';
126     $output .= theme('pager_first', ($tags[0] ? $tags[0] : t('« first')), $limit, $element, $parameters);
127     $output .= theme('pager_previous', ($tags[1] ? $tags[1] : t('‹ previous')), $limit, $element, 1, $parameters);
128     $output .= theme('pager_list', $limit, $element, ($tags[2] ? $tags[2] : 9 ), '', $parameters);
129     $output .= theme('pager_next', ($tags[3] ? $tags[3] : t('next ›')), $limit, $element, 1, $parameters);
130     $output .= theme('pager_last', ($tags[4] ? $tags[4] : t('last »')), $limit, $element, $parameters);
131     $output .= '</div>';
132
133     return $output;
134   }
135 }
136
137 /**
138  * @name Pager pieces
139  * @{
140  * Use these pieces to construct your own custom pagers in your theme. Note that
141  * you should NOT modify this file to customize your pager.
142  */
143
144 /**
145  * Format a "first page" link.
146  *
147  * @param $text
148  *   The name (or image) of the link.
149  * @param $limit
150  *   The number of query results to display per page.
151  * @param $element
152  *   An optional integer to distinguish between multiple pagers on one page.
153  * @param $parameters
154  *   An associative array of query string parameters to append to the pager links.
155  * @return
156  *   An HTML string that generates this piece of the query pager.
157  *
158  * @ingroup themeable
159  */
160 function theme_pager_first($text, $limit, $element = 0, $parameters = array()) {
161   global $pager_page_array;
162   $output = '';
163
164   // If we are anywhere but the first page
165   if ($pager_page_array[$element] > 0) {
166     $output = theme('pager_link', $text, pager_load_array(0, $element, $pager_page_array), $element, $parameters, array('class' => 'pager-first'));
167   }
168
169   return $output;
170 }
171
172 /**
173  * Format a "previous page" link.
174  *
175  * @param $text
176  *   The name (or image) of the link.
177  * @param $limit
178  *   The number of query results to display per page.
179  * @param $element
180  *   An optional integer to distinguish between multiple pagers on one page.
181  * @param $interval
182  *   The number of pages to move backward when the link is clicked.
183  * @param $parameters
184  *   An associative array of query string parameters to append to the pager links.
185  * @return
186  *   An HTML string that generates this piece of the query pager.
187  *
188  * @ingroup themeable
189  */
190 function theme_pager_previous($text, $limit, $element = 0, $interval = 1, $parameters = array()) {
191   global $pager_page_array;
192   $output = '';
193
194   // If we are anywhere but the first page
195   if ($pager_page_array[$element] > 0) {
196     $page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
197
198     // If the previous page is the first page, mark the link as such.
199     if ($page_new[$element] == 0) {
200       $output = theme('pager_first', $text, $limit, $element, $parameters);
201     }
202     // The previous page is not the first page.
203     else {
204       $output = theme('pager_link', $text, $page_new, $element, $parameters, array('class' => 'pager-previous'));
205     }
206   }
207
208   return $output;
209 }
210
211 /**
212  * Format a "next page" link.
213  *
214  * @param $text
215  *   The name (or image) of the link.
216  * @param $limit
217  *   The number of query results to display per page.
218  * @param $element
219  *   An optional integer to distinguish between multiple pagers on one page.
220  * @param $interval
221  *   The number of pages to move forward when the link is clicked.
222  * @param $parameters
223  *   An associative array of query string parameters to append to the pager links.
224  * @return
225  *   An HTML string that generates this piece of the query pager.
226  *
227  * @ingroup themeable
228  */
229 function theme_pager_next($text, $limit, $element = 0, $interval = 1, $parameters = array()) {
230   global $pager_page_array, $pager_total;
231   $output = '';
232
233   // If we are anywhere but the last page
234   if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
235     $page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
236     // If the next page is the last page, mark the link as such.
237     if ($page_new[$element] == ($pager_total[$element] - 1)) {
238       $output = theme('pager_last', $text, $limit, $element, $parameters);
239     }
240     // The next page is not the last page.
241     else {
242       $output = theme('pager_link', $text, $page_new, $element, $parameters, array('class' => 'pager-next'));
243     }
244   }
245
246   return $output;
247 }
248
249 /**
250  * Format a "last page" link.
251  *
252  * @param $text
253  *   The name (or image) of the link.
254  * @param $limit
255  *   The number of query results to display per page.
256  * @param $element
257  *   An optional integer to distinguish between multiple pagers on one page.
258  * @param $parameters
259  *   An associative array of query string parameters to append to the pager links.
260  * @return
261  *   An HTML string that generates this piece of the query pager.
262  *
263  * @ingroup themeable
264  */
265 function theme_pager_last($text, $limit, $element = 0, $parameters = array()) {
266   global $pager_page_array, $pager_total;
267   $output = '';
268
269   // If we are anywhere but the last page
270   if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
271     $output = theme('pager_link', $text, pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), $element, $parameters, array('class' => 'pager-last'));
272   }
273
274   return $output;
275 }
276
277 /**
278  * Format a list of nearby pages with additional query results.
279  *
280  * @param $limit
281  *   The number of query results to display per page.
282  * @param $element
283  *   An optional integer to distinguish between multiple pagers on one page.
284  * @param $quantity
285  *   The number of pages in the list.
286  * @param $text
287  *   A string of text to display before the page list.
288  * @param $parameters
289  *   An associative array of query string parameters to append to the pager links.
290  * @return
291  *   An HTML string that generates this piece of the query pager.
292  *
293  * @ingroup themeable
294  */
295 function theme_pager_list($limit, $element = 0, $quantity = 5, $text = '', $parameters = array()) {
296   global $pager_page_array, $pager_total;
297
298   $output = '<span class="pager-list">';
299   // Calculate various markers within this pager piece:
300   // Middle is used to "center" pages around the current page.
301   $pager_middle = ceil($quantity / 2);
302   // current is the page we are currently paged to
303   $pager_current = $pager_page_array[$element] + 1;
304   // first is the first page listed by this pager piece (re quantity)
305   $pager_first = $pager_current - $pager_middle + 1;
306   // last is the last page listed by this pager piece (re quantity)
307   $pager_last = $pager_current + $quantity - $pager_middle;
308   // max is the maximum page number
309   $pager_max = $pager_total[$element];
310   // End of marker calculations.
311
312   // Prepare for generation loop.
313   $i = $pager_first;
314   if ($pager_last > $pager_max) {
315     // Adjust "center" if at end of query.
316     $i = $i + ($pager_max - $pager_last);
317     $pager_last = $pager_max;
318   }
319   if ($i <= 0) {
320     // Adjust "center" if at start of query.
321     $pager_last = $pager_last + (1 - $i);
322     $i = 1;
323   }
324   // End of generation loop preparation.
325
326   // When there is more than one page, create the pager list.
327   if ($i != $pager_max) {
328     $output .= $text;
329     if ($i > 1) {
330       $output .= '<span class="pager-ellipsis">…</span>';
331     }
332
333     // Now generate the actual pager piece.
334     for (; $i <= $pager_last && $i <= $pager_max; $i++) {
335       if ($i < $pager_current) {
336         $output .= theme('pager_previous', $i, $limit, $element, ($pager_current - $i), $parameters);
337       }
338       if ($i == $pager_current) {
339         $output .= '<strong class="pager-current">'. $i .'</strong>';
340       }
341       if ($i > $pager_current) {
342         $output .= theme('pager_next', $i, $limit, $element, ($i - $pager_current), $parameters);
343       }
344     }
345
346     if ($i < $pager_max) {
347       $output .= '<span class="pager-ellipsis">…</span>';
348     }
349   }
350   $output .= '</span>';
351
352   return $output;
353 }
354
355 /**
356  * Format a link to a specific query result page.
357  *
358  * @param $page_new
359  *   The first result to display on the linked page.
360  * @param $element
361  *   An optional integer to distinguish between multiple pagers on one page.
362  * @param $parameters
363  *   An associative array of query string parameters to append to the pager link.
364  * @param $attributes
365  *   An associative array of HTML attributes to apply to a pager anchor tag.
366  * @return
367  *   An HTML string that generates the link.
368  */
369 function theme_pager_link($text, $page_new, $element, $parameters = array(), $attributes = array()) {
370   $page = isset($_GET['page']) ? $_GET['page'] : '';
371   if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
372     $parameters['page'] = $new_page;
373   }
374
375   $query = array();
376   if (count($parameters)) {
377     $query[] = drupal_query_string_encode($parameters, array());
378   }
379   $querystring = pager_get_querystring();
380   if ($querystring != '') {
381     $query[] = $querystring;
382   }
383
384   // Set each pager link title
385   if (!isset($attributes['title'])) {
386     static $titles = null;
387     if (!isset($titles)) {
388       $titles = array(
389         t('« first') => t('Go to first page'),
390         t('‹ previous') => t('Go to previous page'),
391         t('next ›') => t('Go to next page'),
392         t('last »') => t('Go to last page'),
393       );
394     }
395     if (isset($titles[$text])) {
396       $attributes['title'] = $titles[$text];
397     }
398     else if (is_numeric($text)) {
399       $attributes['title'] = t('Go to page %number', array('%number' => $text));
400     }
401   }
402
403   return l($text, $_GET['q'], $attributes, count($query) ? implode('&', $query) : NULL);
404 }
405
406 /**
407  * @} End of "Pager pieces".
408  */
409
410 /**
411  * Helper function
412  *
413  * Copies $old_array to $new_array and sets $new_array[$element] = $value
414  * Fills in $new_array[0 .. $element - 1] = 0
415  */
416 function pager_load_array($value, $element, $old_array) {
417   $new_array = $old_array;
418   // Look for empty elements.
419   for ($i = 0; $i < $element; $i++) {
420     if (!$new_array[$i]) {
421       // Load found empty element with 0.
422       $new_array[$i] = 0;
423     }
424   }
425   // Update the changed element.
426   $new_array[$element] = (int)$value;
427   return $new_array;
428 }
429
430