| 1 | | <?php |
| 2 | | // $Id: pager.inc,v 1.64 2008/10/12 04:30:05 webchick Exp $ |
| 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 | 2366 | function pager_query($query, $limit = 10, $element = 0, $count_query =
NULL) { |
| 52 | 472 | global $pager_page_array, $pager_total, $pager_total_items; |
| 53 | 472 | $page = isset($_GET['page']) ? $_GET['page'] : ''; |
| 54 | | |
| 55 | | // Substitute in query arguments. |
| 56 | 472 | $args = func_get_args(); |
| 57 | 472 | $args = array_slice($args, 4); |
| 58 | | // Alternative syntax for '...' |
| 59 | 472 | if (isset($args[0]) && is_array($args[0])) { |
| 60 | 163 | $args = $args[0]; |
| 61 | 163 | } |
| 62 | | |
| 63 | | // Construct a count query if none was given. |
| 64 | 472 | if (!isset($count_query)) { |
| 65 | 287 | $count_query = preg_replace(array('/SELECT.*?FROM /As', '/ORDER BY
.*/'), array('SELECT COUNT(*) FROM ', ''), $query); |
| 66 | 287 | } |
| 67 | | |
| 68 | | // Convert comma-separated $page to an array, used by other functions. |
| 69 | 472 | $pager_page_array = explode(',', $page); |
| 70 | | |
| 71 | | // We calculate the total of pages as ceil(items / limit). |
| 72 | 472 | $pager_total_items[$element] = db_result(db_query($count_query, $args)); |
| 73 | 472 | $pager_total[$element] = ceil($pager_total_items[$element] / $limit); |
| 74 | 472 | $pager_page_array[$element] = max(0,
min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1)); |
| 75 | 472 | return db_query_range($query, $args, $pager_page_array[$element] *
$limit, $limit); |
| 76 | 0 | } |
| 77 | | |
| 78 | | /** |
| 79 | | * Compose a query string to append to pager requests. |
| 80 | | * |
| 81 | | * @return |
| 82 | | * A query string that consists of all components of the current page
request |
| 83 | | * except for those pertaining to paging. |
| 84 | | */ |
| 85 | 2366 | function pager_get_querystring() { |
| 86 | 8 | static $string = NULL; |
| 87 | 8 | if (!isset($string)) { |
| 88 | 8 | $string = drupal_query_string_encode($_REQUEST, array_merge(array('q',
'page'), array_keys($_COOKIE))); |
| 89 | 8 | } |
| 90 | 8 | return $string; |
| 91 | 0 | } |
| 92 | | |
| 93 | | /** |
| 94 | | * Format a query pager. |
| 95 | | * |
| 96 | | * Menu callbacks that display paged query results should call
theme('pager') to |
| 97 | | * retrieve a pager control so that users can view other results. |
| 98 | | * Format a list of nearby pages with additional query results. |
| 99 | | * |
| 100 | | * @param $tags |
| 101 | | * An array of labels for the controls in the pager. |
| 102 | | * @param $limit |
| 103 | | * The number of query results to display per page. |
| 104 | | * @param $element |
| 105 | | * An optional integer to distinguish between multiple pagers on one
page. |
| 106 | | * @param $parameters |
| 107 | | * An associative array of query string parameters to append to the pager
links. |
| 108 | | * @param $quantity |
| 109 | | * The number of pages in the list. |
| 110 | | * @return |
| 111 | | * An HTML string that generates the query pager. |
| 112 | | * |
| 113 | | * @ingroup themeable |
| 114 | | */ |
| 115 | 2366 | function theme_pager($tags = array(), $limit = 10, $element = 0,
$parameters = array(), $quantity = 9) { |
| 116 | 251 | global $pager_page_array, $pager_total; |
| 117 | | |
| 118 | | // Calculate various markers within this pager piece: |
| 119 | | // Middle is used to "center" pages around the current page. |
| 120 | 251 | $pager_middle = ceil($quantity / 2); |
| 121 | | // current is the page we are currently paged to |
| 122 | 251 | $pager_current = $pager_page_array[$element] + 1; |
| 123 | | // first is the first page listed by this pager piece (re quantity) |
| 124 | 251 | $pager_first = $pager_current - $pager_middle + 1; |
| 125 | | // last is the last page listed by this pager piece (re quantity) |
| 126 | 251 | $pager_last = $pager_current + $quantity - $pager_middle; |
| 127 | | // max is the maximum page number |
| 128 | 251 | $pager_max = $pager_total[$element]; |
| 129 | | // End of marker calculations. |
| 130 | | |
| 131 | | // Prepare for generation loop. |
| 132 | 251 | $i = $pager_first; |
| 133 | 251 | if ($pager_last > $pager_max) { |
| 134 | | // Adjust "center" if at end of query. |
| 135 | 251 | $i = $i + ($pager_max - $pager_last); |
| 136 | 251 | $pager_last = $pager_max; |
| 137 | 251 | } |
| 138 | 251 | if ($i <= 0) { |
| 139 | | // Adjust "center" if at start of query. |
| 140 | 251 | $pager_last = $pager_last + (1 - $i); |
| 141 | 251 | $i = 1; |
| 142 | 251 | } |
| 143 | | // End of generation loop preparation. |
| 144 | | |
| 145 | 251 | $li_first = theme('pager_first', (isset($tags[0]) ? $tags[0] : t('«
first')), $limit, $element, $parameters); |
| 146 | 251 | $li_previous = theme('pager_previous', (isset($tags[1]) ? $tags[1] :
t('‹ previous')), $limit, $element, 1, $parameters); |
| 147 | 251 | $li_next = theme('pager_next', (isset($tags[3]) ? $tags[3] : t('next
›')), $limit, $element, 1, $parameters); |
| 148 | 251 | $li_last = theme('pager_last', (isset($tags[4]) ? $tags[4] : t('last
»')), $limit, $element, $parameters); |
| 149 | | |
| 150 | 251 | if ($pager_total[$element] > 1) { |
| 151 | 6 | if ($li_first) { |
| 152 | 0 | $items[] = array( |
| 153 | 0 | 'class' => 'pager-first', |
| 154 | 0 | 'data' => $li_first, |
| 155 | | ); |
| 156 | 0 | } |
| 157 | 6 | if ($li_previous) { |
| 158 | 0 | $items[] = array( |
| 159 | 0 | 'class' => 'pager-previous', |
| 160 | 0 | 'data' => $li_previous, |
| 161 | | ); |
| 162 | 0 | } |
| 163 | | |
| 164 | | // When there is more than one page, create the pager list. |
| 165 | 6 | if ($i != $pager_max) { |
| 166 | 6 | if ($i > 1) { |
| 167 | 0 | $items[] = array( |
| 168 | 0 | 'class' => 'pager-ellipsis', |
| 169 | 0 | 'data' => '…', |
| 170 | | ); |
| 171 | 0 | } |
| 172 | | // Now generate the actual pager piece. |
| 173 | 6 | for (; $i <= $pager_last && $i <= $pager_max; $i++) { |
| 174 | 6 | if ($i < $pager_current) { |
| 175 | 0 | $items[] = array( |
| 176 | 0 | 'class' => 'pager-item', |
| 177 | 0 | 'data' => theme('pager_previous', $i, $limit, $element,
($pager_current - $i), $parameters), |
| 178 | | ); |
| 179 | 0 | } |
| 180 | 6 | if ($i == $pager_current) { |
| 181 | 6 | $items[] = array( |
| 182 | 6 | 'class' => 'pager-current', |
| 183 | 6 | 'data' => $i, |
| 184 | | ); |
| 185 | 6 | } |
| 186 | 6 | if ($i > $pager_current) { |
| 187 | 6 | $items[] = array( |
| 188 | 6 | 'class' => 'pager-item', |
| 189 | 6 | 'data' => theme('pager_next', $i, $limit, $element, ($i -
$pager_current), $parameters), |
| 190 | | ); |
| 191 | 6 | } |
| 192 | 6 | } |
| 193 | 6 | if ($i < $pager_max) { |
| 194 | 0 | $items[] = array( |
| 195 | 0 | 'class' => 'pager-ellipsis', |
| 196 | 0 | 'data' => '…', |
| 197 | | ); |
| 198 | 0 | } |
| 199 | 6 | } |
| 200 | | // End generation. |
| 201 | 6 | if ($li_next) { |
| 202 | 6 | $items[] = array( |
| 203 | 6 | 'class' => 'pager-next', |
| 204 | 6 | 'data' => $li_next, |
| 205 | | ); |
| 206 | 6 | } |
| 207 | 6 | if ($li_last) { |
| 208 | 6 | $items[] = array( |
| 209 | 6 | 'class' => 'pager-last', |
| 210 | 6 | 'data' => $li_last, |
| 211 | | ); |
| 212 | 6 | } |
| 213 | 6 | return theme('item_list', $items, NULL, 'ul', array('class' =>
'pager')); |
| 214 | 0 | } |
| 215 | 245 | } |
| 216 | | |
| 217 | | |
| 218 | | /** |
| 219 | | * @name Pager pieces |
| 220 | | * @{ |
| 221 | | * Use these pieces to construct your own custom pagers in your theme. Note
that |
| 222 | | * you should NOT modify this file to customize your pager. |
| 223 | | */ |
| 224 | | |
| 225 | | /** |
| 226 | | * Format a "first page" link. |
| 227 | | * |
| 228 | | * @param $text |
| 229 | | * The name (or image) of the link. |
| 230 | | * @param $limit |
| 231 | | * The number of query results to display per page. |
| 232 | | * @param $element |
| 233 | | * An optional integer to distinguish between multiple pagers on one
page. |
| 234 | | * @param $parameters |
| 235 | | * An associative array of query string parameters to append to the pager
links. |
| 236 | | * @return |
| 237 | | * An HTML string that generates this piece of the query pager. |
| 238 | | * |
| 239 | | * @ingroup themeable |
| 240 | | */ |
| 241 | 2366 | function theme_pager_first($text, $limit, $element = 0, $parameters =
array()) { |
| 242 | 251 | global $pager_page_array; |
| 243 | 251 | $output = ''; |
| 244 | | |
| 245 | | // If we are anywhere but the first page |
| 246 | 251 | if ($pager_page_array[$element] > 0) { |
| 247 | 0 | $output = theme('pager_link', $text, pager_load_array(0, $element,
$pager_page_array), $element, $parameters); |
| 248 | 0 | } |
| 249 | | |
| 250 | 251 | return $output; |
| 251 | 0 | } |
| 252 | | |
| 253 | | /** |
| 254 | | * Format a "previous page" link. |
| 255 | | * |
| 256 | | * @param $text |
| 257 | | * The name (or image) of the link. |
| 258 | | * @param $limit |
| 259 | | * The number of query results to display per page. |
| 260 | | * @param $element |
| 261 | | * An optional integer to distinguish between multiple pagers on one
page. |
| 262 | | * @param $interval |
| 263 | | * The number of pages to move backward when the link is clicked. |
| 264 | | * @param $parameters |
| 265 | | * An associative array of query string parameters to append to the pager
links. |
| 266 | | * @return |
| 267 | | * An HTML string that generates this piece of the query pager. |
| 268 | | * |
| 269 | | * @ingroup themeable |
| 270 | | */ |
| 271 | 2366 | function theme_pager_previous($text, $limit, $element = 0, $interval = 1,
$parameters = array()) { |
| 272 | 251 | global $pager_page_array; |
| 273 | 251 | $output = ''; |
| 274 | | |
| 275 | | // If we are anywhere but the first page |
| 276 | 251 | if ($pager_page_array[$element] > 0) { |
| 277 | 0 | $page_new = pager_load_array($pager_page_array[$element] - $interval,
$element, $pager_page_array); |
| 278 | | |
| 279 | | // If the previous page is the first page, mark the link as such. |
| 280 | 0 | if ($page_new[$element] == 0) { |
| 281 | 0 | $output = theme('pager_first', $text, $limit, $element, $parameters); |
| 282 | 0 | } |
| 283 | | // The previous page is not the first page. |
| 284 | | else { |
| 285 | 0 | $output = theme('pager_link', $text, $page_new, $element,
$parameters); |
| 286 | | } |
| 287 | 0 | } |
| 288 | | |
| 289 | 251 | return $output; |
| 290 | 0 | } |
| 291 | | |
| 292 | | /** |
| 293 | | * Format a "next page" link. |
| 294 | | * |
| 295 | | * @param $text |
| 296 | | * The name (or image) of the link. |
| 297 | | * @param $limit |
| 298 | | * The number of query results to display per page. |
| 299 | | * @param $element |
| 300 | | * An optional integer to distinguish between multiple pagers on one
page. |
| 301 | | * @param $interval |
| 302 | | * The number of pages to move forward when the link is clicked. |
| 303 | | * @param $parameters |
| 304 | | * An associative array of query string parameters to append to the pager
links. |
| 305 | | * @return |
| 306 | | * An HTML string that generates this piece of the query pager. |
| 307 | | * |
| 308 | | * @ingroup themeable |
| 309 | | */ |
| 310 | 2366 | function theme_pager_next($text, $limit, $element = 0, $interval = 1,
$parameters = array()) { |
| 311 | 251 | global $pager_page_array, $pager_total; |
| 312 | 251 | $output = ''; |
| 313 | | |
| 314 | | // If we are anywhere but the last page |
| 315 | 251 | if ($pager_page_array[$element] < ($pager_total[$element] - 1)) { |
| 316 | 8 | $page_new = pager_load_array($pager_page_array[$element] + $interval,
$element, $pager_page_array); |
| 317 | | // If the next page is the last page, mark the link as such. |
| 318 | 8 | if ($page_new[$element] == ($pager_total[$element] - 1)) { |
| 319 | 6 | $output = theme('pager_last', $text, $limit, $element, $parameters); |
| 320 | 6 | } |
| 321 | | // The next page is not the last page. |
| 322 | | else { |
| 323 | 8 | $output = theme('pager_link', $text, $page_new, $element,
$parameters); |
| 324 | | } |
| 325 | 8 | } |
| 326 | | |
| 327 | 251 | return $output; |
| 328 | 0 | } |
| 329 | | |
| 330 | | /** |
| 331 | | * Format a "last page" link. |
| 332 | | * |
| 333 | | * @param $text |
| 334 | | * The name (or image) of the link. |
| 335 | | * @param $limit |
| 336 | | * The number of query results to display per page. |
| 337 | | * @param $element |
| 338 | | * An optional integer to distinguish between multiple pagers on one
page. |
| 339 | | * @param $parameters |
| 340 | | * An associative array of query string parameters to append to the pager
links. |
| 341 | | * @return |
| 342 | | * An HTML string that generates this piece of the query pager. |
| 343 | | * |
| 344 | | * @ingroup themeable |
| 345 | | */ |
| 346 | 2366 | function theme_pager_last($text, $limit, $element = 0, $parameters =
array()) { |
| 347 | 251 | global $pager_page_array, $pager_total; |
| 348 | 251 | $output = ''; |
| 349 | | |
| 350 | | // If we are anywhere but the last page |
| 351 | 251 | if ($pager_page_array[$element] < ($pager_total[$element] - 1)) { |
| 352 | 8 | $output = theme('pager_link', $text,
pager_load_array($pager_total[$element] - 1, $element, $pager_page_array),
$element, $parameters); |
| 353 | 8 | } |
| 354 | | |
| 355 | 251 | return $output; |
| 356 | 0 | } |
| 357 | | |
| 358 | | |
| 359 | | /** |
| 360 | | * Format a link to a specific query result page. |
| 361 | | * |
| 362 | | * @param $page_new |
| 363 | | * The first result to display on the linked page. |
| 364 | | * @param $element |
| 365 | | * An optional integer to distinguish between multiple pagers on one
page. |
| 366 | | * @param $parameters |
| 367 | | * An associative array of query string parameters to append to the pager
link. |
| 368 | | * @param $attributes |
| 369 | | * An associative array of HTML attributes to apply to a pager anchor
tag. |
| 370 | | * @return |
| 371 | | * An HTML string that generates the link. |
| 372 | | * |
| 373 | | * @ingroup themeable |
| 374 | | */ |
| 375 | 2366 | function theme_pager_link($text, $page_new, $element, $parameters =
array(), $attributes = array()) { |
| 376 | 8 | $page = isset($_GET['page']) ? $_GET['page'] : ''; |
| 377 | 8 | if ($new_page = implode(',', pager_load_array($page_new[$element],
$element, explode(',', $page)))) { |
| 378 | 8 | $parameters['page'] = $new_page; |
| 379 | 8 | } |
| 380 | | |
| 381 | 8 | $query = array(); |
| 382 | 8 | if (count($parameters)) { |
| 383 | 8 | $query[] = drupal_query_string_encode($parameters, array()); |
| 384 | 8 | } |
| 385 | 8 | $querystring = pager_get_querystring(); |
| 386 | 8 | if ($querystring != '') { |
| 387 | 0 | $query[] = $querystring; |
| 388 | 0 | } |
| 389 | | |
| 390 | | // Set each pager link title |
| 391 | 8 | if (!isset($attributes['title'])) { |
| 392 | 8 | static $titles = NULL; |
| 393 | 8 | if (!isset($titles)) { |
| 394 | | $titles = array( |
| 395 | 8 | t('« first') => t('Go to first page'), |
| 396 | 8 | t('‹ previous') => t('Go to previous page'), |
| 397 | 8 | t('next ›') => t('Go to next page'), |
| 398 | 8 | t('last »') => t('Go to last page'), |
| 399 | 8 | ); |
| 400 | 8 | } |
| 401 | 8 | if (isset($titles[$text])) { |
| 402 | 8 | $attributes['title'] = $titles[$text]; |
| 403 | 8 | } |
| 404 | 6 | elseif (is_numeric($text)) { |
| 405 | 6 | $attributes['title'] = t('Go to page @number', array('@number' =>
$text)); |
| 406 | 6 | } |
| 407 | 8 | } |
| 408 | | |
| 409 | 8 | return l($text, $_GET['q'], array('attributes' => $attributes, 'query' =>
count($query) ? implode('&', $query) : NULL)); |
| 410 | 0 | } |
| 411 | | |
| 412 | | /** |
| 413 | | * @} End of "Pager pieces". |
| 414 | | */ |
| 415 | | |
| 416 | | /** |
| 417 | | * Helper function |
| 418 | | * |
| 419 | | * Copies $old_array to $new_array and sets $new_array[$element] = $value |
| 420 | | * Fills in $new_array[0 .. $element - 1] = 0 |
| 421 | | */ |
| 422 | 2366 | function pager_load_array($value, $element, $old_array) { |
| 423 | 8 | $new_array = $old_array; |
| 424 | | // Look for empty elements. |
| 425 | 8 | for ($i = 0; $i < $element; $i++) { |
| 426 | 0 | if (!$new_array[$i]) { |
| 427 | | // Load found empty element with 0. |
| 428 | 0 | $new_array[$i] = 0; |
| 429 | 0 | } |
| 430 | 0 | } |
| 431 | | // Update the changed element. |
| 432 | 8 | $new_array[$element] = (int)$value; |
| 433 | 8 | return $new_array; |
| 434 | 0 | } |
| 435 | 2366 | |