| 1 |
<?php |
| 2 |
|
| 3 |
// ====================================================================================== |
| 4 |
// This library is free software; you can redistribute it and/or |
| 5 |
// modify it under the terms of the GNU Lesser General Public |
| 6 |
// License as published by the Free Software Foundation; either |
| 7 |
// version 2.1 of the License, or (at your option) any later version. |
| 8 |
// |
| 9 |
// This library is distributed in the hope that it will be useful, |
| 10 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 |
// Lesser General Public License for more details. |
| 13 |
// ====================================================================================== |
| 14 |
// @author John Godley (http://urbangiraffe.com) |
| 15 |
// @version 0.2.8 |
| 16 |
// @copyright Copyright © 2007 John Godley, All Rights Reserved |
| 17 |
// ====================================================================================== |
| 18 |
// 0.2.3 - Remember pager details in user data |
| 19 |
// 0.2.4 - Add phpdoc comments |
| 20 |
// 0.2.5 - Allow orderby to use tags to hide database columns |
| 21 |
// 0.2.6 - Fix sortable columns with only 1 page |
| 22 |
// 0.2.7 - Add a GROUP BY feature, make search work when position not 0 |
| 23 |
// 0.2.8 - WP 2.7 functions |
| 24 |
// ====================================================================================== |
| 25 |
|
| 26 |
|
| 27 |
/** |
| 28 |
* Provides pagination, column-based ordering, searching, and filtering |
| 29 |
* |
| 30 |
* The class does no database queries itself but instead relies on the user modifying their queries with data |
| 31 |
* from the pager. For correct pagination you must set the total number of results |
| 32 |
* |
| 33 |
* For example, |
| 34 |
* |
| 35 |
* $pager->set_total ($wpdb->get_var ("SELECT COUNT(*) FROM wp_posts").$pager->to_conditions ()); |
| 36 |
* $rows = $wpdb->get_results ("SELECT * FROM wp_posts".$pager->to_limits ("post_type=page")); |
| 37 |
* |
| 38 |
* Searching is achieved by specifying the columns that can be searched: |
| 39 |
* |
| 40 |
* $rows = $wpdb->get_results ("SELECT * FROM wp_posts".$pager->to_limits ("post_type=page", array ('post_content', 'post_excerpt'))); |
| 41 |
* |
| 42 |
* Additionally you can output column headings with correct URLs: |
| 43 |
* <th><?php echo $pager->sortable ('post_username', 'Username') ?></th> |
| 44 |
* |
| 45 |
* @package default |
| 46 |
**/ |
| 47 |
|
| 48 |
class RE_Pager |
| 49 |
{ |
| 50 |
var $url = null; |
| 51 |
var $current_page = 1; |
| 52 |
var $per_page = 25; |
| 53 |
var $total = 0; |
| 54 |
var $order_by = null; |
| 55 |
var $order_original = null; |
| 56 |
var $order_direction = null; |
| 57 |
var $order_tags = array (); |
| 58 |
var $steps = array (); |
| 59 |
var $search = null; |
| 60 |
var $filters = array (); |
| 61 |
var $id; |
| 62 |
|
| 63 |
|
| 64 |
/** |
| 65 |
* Construct a pager object using the $_GET data, the current URL, and default preferences |
| 66 |
* |
| 67 |
* @param array $data Array of values, typically from $_GET |
| 68 |
* @param string $url The current URL |
| 69 |
* @param string $orderby Default database column to order data by |
| 70 |
* @param string $direction Default direction of ordering (DESC or ASC) |
| 71 |
* @param string $id An ID for the pager to separate it from other pagers (typically the plugin name) |
| 72 |
* @return void |
| 73 |
**/ |
| 74 |
function RE_Pager ($data, $url, $orderby = '', $direction = 'DESC', $id = 'default', $tags = '') |
| 75 |
{ |
| 76 |
// Remove all pager params from the url |
| 77 |
$this->id = $id; |
| 78 |
$this->url = $url; |
| 79 |
|
| 80 |
if (isset ($data['curpage'])) |
| 81 |
$this->current_page = intval ($data['curpage']); |
| 82 |
|
| 83 |
global $user_ID; |
| 84 |
|
| 85 |
if (isset ($data['perpage'])) |
| 86 |
{ |
| 87 |
$this->per_page = intval ($data['perpage']); |
| 88 |
$per_page[get_class ($this)][$this->id] = $this->per_page; |
| 89 |
} |
| 90 |
else if (isset ($per_page[get_class ($this)]) && isset ($per_page[get_class ($this)][$this->id])) |
| 91 |
$this->per_page = $per_page[get_class ($this)][$this->id]; |
| 92 |
|
| 93 |
if ($orderby != '') |
| 94 |
$this->order_by = $orderby; |
| 95 |
|
| 96 |
if (isset ($data['orderby'])) |
| 97 |
$this->order_by = $data['orderby']; |
| 98 |
|
| 99 |
if (!empty ($tags)) |
| 100 |
{ |
| 101 |
$this->order_tags = $tags; |
| 102 |
if (isset ($this->order_tags[$this->order_by])) |
| 103 |
$this->order_by = $this->order_tags[$this->order_by]; |
| 104 |
} |
| 105 |
|
| 106 |
$this->order_direction = $direction; |
| 107 |
$this->order_original = $orderby; |
| 108 |
if (isset ($data['order'])) |
| 109 |
$this->order_direction = $data['order']; |
| 110 |
|
| 111 |
$this->search = isset($data['search']) ? $data['search'] : ''; |
| 112 |
$this->steps = array (10, 25, 50, 100, 250); |
| 113 |
$this->url = str_replace ('&', '&', $this->url); |
| 114 |
$this->url = str_replace ('&&', '&', $this->url); |
| 115 |
} |
| 116 |
|
| 117 |
|
| 118 |
/** |
| 119 |
* Set the total number of entries that match the conditions |
| 120 |
* |
| 121 |
* @param int $total Count |
| 122 |
* @return void |
| 123 |
**/ |
| 124 |
|
| 125 |
function set_total ($total) |
| 126 |
{ |
| 127 |
$this->total = $total; |
| 128 |
|
| 129 |
if ($this->current_page <= 0 || $this->current_page > $this->total_pages ()) |
| 130 |
$this->current_page = 1; |
| 131 |
} |
| 132 |
|
| 133 |
|
| 134 |
/** |
| 135 |
* Return the current page offset |
| 136 |
* |
| 137 |
* @return int Current page offset |
| 138 |
**/ |
| 139 |
|
| 140 |
function offset () |
| 141 |
{ |
| 142 |
return ($this->current_page - 1) * $this->per_page; |
| 143 |
} |
| 144 |
|
| 145 |
|
| 146 |
/** |
| 147 |
* @todo explain |
| 148 |
* @return void |
| 149 |
**/ |
| 150 |
// XXX |
| 151 |
|
| 152 |
function is_secondary_sort () |
| 153 |
{ |
| 154 |
return substr ($this->order_by, 0, 1) == '_' ? true : false; |
| 155 |
} |
| 156 |
|
| 157 |
|
| 158 |
/** |
| 159 |
* Returns a set of conditions without any limits. This is suitable for a COUNT SQL |
| 160 |
* |
| 161 |
* @param string $conditions WHERE conditions |
| 162 |
* @param array $searches Array of columns to search on |
| 163 |
* @param array $filters Array of columns to filter on |
| 164 |
* @return string SQL |
| 165 |
**/ |
| 166 |
|
| 167 |
function to_conditions ($conditions, $searches = '', $filters = '') |
| 168 |
{ |
| 169 |
$sql = ''; |
| 170 |
if ($conditions != '') |
| 171 |
$sql .= ' WHERE '.$conditions; |
| 172 |
|
| 173 |
// Add on search conditions |
| 174 |
if (is_array ($searches) && $this->search != '') |
| 175 |
{ |
| 176 |
if ($sql == '') |
| 177 |
$sql .= ' WHERE ('; |
| 178 |
else |
| 179 |
$sql .= ' AND ('; |
| 180 |
|
| 181 |
$searchbits = array (); |
| 182 |
foreach ($searches AS $search) |
| 183 |
$searchbits[] = "$search LIKE \"%{$this->search}%\""; |
| 184 |
|
| 185 |
$sql .= implode (' OR ', $searchbits); |
| 186 |
$sql .= ')'; |
| 187 |
} |
| 188 |
|
| 189 |
// Add filters |
| 190 |
if (is_array ($filters) && !empty ($this->filters)) |
| 191 |
{ |
| 192 |
$searchbits = array (); |
| 193 |
foreach ($filters AS $filter) |
| 194 |
{ |
| 195 |
if (isset ($this->filters[$filter])) |
| 196 |
{ |
| 197 |
if ($this->filters[$filter] != '') |
| 198 |
$searchbits[] = sprintf ("%s = '%s'", $filter, $this->filters[$filter]); |
| 199 |
} |
| 200 |
} |
| 201 |
|
| 202 |
if (count ($searchbits) > 0) |
| 203 |
{ |
| 204 |
if ($sql == '') |
| 205 |
$sql .= ' WHERE ('; |
| 206 |
else |
| 207 |
$sql .= ' AND ('; |
| 208 |
|
| 209 |
$sql .= implode (' AND ', $searchbits); |
| 210 |
$sql .= ')'; |
| 211 |
} |
| 212 |
} |
| 213 |
|
| 214 |
return $sql; |
| 215 |
} |
| 216 |
|
| 217 |
|
| 218 |
/** |
| 219 |
* Returns a set of conditions with limits. |
| 220 |
* |
| 221 |
* @param string $conditions WHERE conditions |
| 222 |
* @param array $searches Array of columns to search on |
| 223 |
* @param array $filters Array of columns to filter on |
| 224 |
* @return string SQL |
| 225 |
**/ |
| 226 |
|
| 227 |
function to_limits ($conditions = '', $searches = '', $filters = '', $group_by = '') |
| 228 |
{ |
| 229 |
$sql = $this->to_conditions ($conditions, $searches, $filters); |
| 230 |
|
| 231 |
if ($group_by) |
| 232 |
$sql .= ' '.$group_by.' '; |
| 233 |
|
| 234 |
if (strlen ($this->order_by) > 0) |
| 235 |
{ |
| 236 |
if (!$this->is_secondary_sort ()) |
| 237 |
$sql .= " ORDER BY ".$this->order_by.' '.$this->order_direction; |
| 238 |
else |
| 239 |
$sql .= " ORDER BY ".$this->order_original.' '.$this->order_direction; |
| 240 |
} |
| 241 |
|
| 242 |
if ($this->per_page > 0) |
| 243 |
$sql .= ' LIMIT '.$this->offset ().','.$this->per_page; |
| 244 |
return $sql; |
| 245 |
} |
| 246 |
|
| 247 |
|
| 248 |
/** |
| 249 |
* Return the url with all the params added back |
| 250 |
* |
| 251 |
* @param int Page offset |
| 252 |
* @param string $orderby Optional order |
| 253 |
* @return string URL |
| 254 |
**/ |
| 255 |
|
| 256 |
function url ($offset, $orderby = '') |
| 257 |
{ |
| 258 |
// Position |
| 259 |
if (strpos ($this->url, 'curpage=') !== false) |
| 260 |
$url = preg_replace ('/curpage=\d*/', 'curpage='.$offset, $this->url); |
| 261 |
else |
| 262 |
$url = $this->url.'&curpage='.$offset; |
| 263 |
|
| 264 |
// Order |
| 265 |
if ($orderby != '') |
| 266 |
{ |
| 267 |
if (strpos ($url, 'orderby=') !== false) |
| 268 |
$url = preg_replace ('/orderby=\w*/', 'orderby='.$orderby, $url); |
| 269 |
else |
| 270 |
$url = $url.'&orderby='.$orderby; |
| 271 |
|
| 272 |
if (!empty ($this->order_tags) && isset ($this->order_tags[$orderby])) |
| 273 |
$dir = $this->order_direction == 'ASC' ? 'DESC' : 'ASC'; |
| 274 |
else if ($this->order_by == $orderby) |
| 275 |
$dir = $this->order_direction == 'ASC' ? 'DESC' : 'ASC'; |
| 276 |
else |
| 277 |
$dir = $this->order_direction; |
| 278 |
|
| 279 |
if (strpos ($url, 'order=') !== false) |
| 280 |
$url = preg_replace ('/order=\w*/', 'order='.$dir, $url); |
| 281 |
else |
| 282 |
$url = $url.'&order='.$dir; |
| 283 |
} |
| 284 |
|
| 285 |
return str_replace ('&go=go', '', $url); |
| 286 |
} |
| 287 |
|
| 288 |
|
| 289 |
/** |
| 290 |
* Return current page |
| 291 |
* |
| 292 |
* @return int |
| 293 |
**/ |
| 294 |
|
| 295 |
function current_page () { return $this->current_page; } |
| 296 |
|
| 297 |
|
| 298 |
/** |
| 299 |
* Return total number of pages |
| 300 |
* |
| 301 |
* @return int |
| 302 |
**/ |
| 303 |
|
| 304 |
function total_pages () |
| 305 |
{ |
| 306 |
if ($this->per_page == 0) |
| 307 |
return 1; |
| 308 |
return ceil ($this->total / $this->per_page); |
| 309 |
} |
| 310 |
|
| 311 |
|
| 312 |
/** |
| 313 |
* Determine if we have a next page |
| 314 |
* |
| 315 |
* @return boolean |
| 316 |
**/ |
| 317 |
|
| 318 |
function have_next_page () |
| 319 |
{ |
| 320 |
if ($this->current_page < $this->total_pages ()) |
| 321 |
return true; |
| 322 |
return false; |
| 323 |
} |
| 324 |
|
| 325 |
|
| 326 |
/** |
| 327 |
* Determine if we have a previous page |
| 328 |
* |
| 329 |
* @return boolean |
| 330 |
**/ |
| 331 |
|
| 332 |
function have_previous_page () |
| 333 |
{ |
| 334 |
if ($this->current_page > 1) |
| 335 |
return true; |
| 336 |
return false; |
| 337 |
} |
| 338 |
|
| 339 |
|
| 340 |
function sortable_class ($column, $class = true) |
| 341 |
{ |
| 342 |
if ($column == $this->order_by) |
| 343 |
{ |
| 344 |
if ($class) |
| 345 |
printf (' class="sorted"'); |
| 346 |
else |
| 347 |
echo ' sorted'; |
| 348 |
} |
| 349 |
} |
| 350 |
|
| 351 |
/** |
| 352 |
* Return a string suitable for a sortable column heading |
| 353 |
* |
| 354 |
* @param string $column Column to search upon |
| 355 |
* @param string $text Text to display for the column |
| 356 |
* @param boolean $image Whether to show a direction image |
| 357 |
* @return string URL |
| 358 |
**/ |
| 359 |
|
| 360 |
function sortable ($column, $text, $image = true) |
| 361 |
{ |
| 362 |
$url = $this->url ($this->current_page, $column); |
| 363 |
$img = ''; |
| 364 |
|
| 365 |
if (isset ($this->order_tags[$column])) |
| 366 |
$column = $this->order_tags[$column]; |
| 367 |
|
| 368 |
if ($column == $this->order_by) |
| 369 |
{ |
| 370 |
if (defined ('WP_PLUGIN_URL')) |
| 371 |
$dir = WP_PLUGIN_URL.'/'.basename (dirname (dirname (__FILE__))); |
| 372 |
else |
| 373 |
$dir = get_bloginfo ('wpurl').'/wp-content/plugins/'.basename (dirname (dirname (__FILE__))); |
| 374 |
|
| 375 |
if (strpos ($url, 'ASC') !== false) |
| 376 |
$img = '<img align="bottom" src="'.$dir.'/images/up.gif" alt="dir" width="16" height="7"/>'; |
| 377 |
else |
| 378 |
$img = '<img align="bottom" src="'.$dir.'/images/down.gif" alt="dir" width="16" height="7"/>'; |
| 379 |
|
| 380 |
if ($image == false) |
| 381 |
$img = ''; |
| 382 |
} |
| 383 |
|
| 384 |
return '<a href="'.$url.'">'.$text.'</a>'.$img; |
| 385 |
} |
| 386 |
|
| 387 |
|
| 388 |
/** |
| 389 |
* Returns an array of page numbers => link, given the current page (next and previous etc) |
| 390 |
* |
| 391 |
* @return array Array of page links |
| 392 |
**/ |
| 393 |
|
| 394 |
function area_pages () |
| 395 |
{ |
| 396 |
// First page |
| 397 |
$allow_dot = true; |
| 398 |
$pages = array (); |
| 399 |
|
| 400 |
if ($this->total_pages () > 1) |
| 401 |
{ |
| 402 |
$previous = __ ('Previous', 'redirection'); |
| 403 |
$next = __ ('Next', 'redirection'); |
| 404 |
|
| 405 |
if ($this->have_previous_page ()) |
| 406 |
$pages[] = '<a href="'.$this->url ($this->current_page - 1).'">'.$previous.'</a> |'; |
| 407 |
else |
| 408 |
$pages[] = $previous.' |'; |
| 409 |
|
| 410 |
for ($pos = 1; $pos <= $this->total_pages (); $pos++) |
| 411 |
{ |
| 412 |
if ($pos == $this->current_page) |
| 413 |
{ |
| 414 |
$pages[] = '<span class="active">'.$pos.'</span>'; |
| 415 |
$allow_dot = true; |
| 416 |
} |
| 417 |
else if ($pos == 1 || abs ($this->current_page - $pos) <= 2 || $pos == $this->total_pages ()) |
| 418 |
$pages[] = '<a href="'.$this->url ($pos).'">'.$pos."</a>"; |
| 419 |
else if ($allow_dot) |
| 420 |
{ |
| 421 |
$allow_dot = false; |
| 422 |
$pages[] = '…'; |
| 423 |
} |
| 424 |
} |
| 425 |
|
| 426 |
if ($this->have_next_page ()) |
| 427 |
$pages[] = '| <a href="'.$this->url ($this->current_page + 1).'">'.$next.'</a>'; |
| 428 |
else |
| 429 |
$pages[] = '| '.$next; |
| 430 |
} |
| 431 |
|
| 432 |
return $pages; |
| 433 |
} |
| 434 |
|
| 435 |
|
| 436 |
/** |
| 437 |
* @todo |
| 438 |
* @return boolean |
| 439 |
**/ |
| 440 |
|
| 441 |
function filtered ($field, $value) |
| 442 |
{ |
| 443 |
if (isset ($this->filters[$field]) && $this->filters[$field] == $value) |
| 444 |
return true; |
| 445 |
return false; |
| 446 |
} |
| 447 |
|
| 448 |
|
| 449 |
/** |
| 450 |
* Display a SELECT box suitable for a per-page |
| 451 |
* |
| 452 |
* @return void |
| 453 |
**/ |
| 454 |
|
| 455 |
function per_page ($plugin = '') |
| 456 |
{ |
| 457 |
?> |
| 458 |
<select name="perpage"> |
| 459 |
<?php foreach ($this->steps AS $step) : ?> |
| 460 |
<option value="<?php echo $step ?>"<?php if ($this->per_page == $step) echo ' selected="selected"' ?>> |
| 461 |
<?php printf (__ ('%d per-page', $plugin), $step) ?> |
| 462 |
</option> |
| 463 |
<?php endforeach; ?> |
| 464 |
</select> |
| 465 |
<?php |
| 466 |
} |
| 467 |
|
| 468 |
function page_links () |
| 469 |
{ |
| 470 |
$text = sprintf( '<span class="displaying-num">' . __( 'Displaying %s–%s of %s' ) . '</span>', |
| 471 |
number_format_i18n (($this->current_page () - 1) * $this->per_page + 1), |
| 472 |
number_format_i18n ($this->current_page () * $this->per_page > $this->total ? $this->total : $this->current_page () * $this->per_page), |
| 473 |
number_format_i18n ($this->total)); |
| 474 |
|
| 475 |
$links = paginate_links (array ('base' => str_replace ('99', '%#%', $this->url (99)), 'format' => '%#%', 'current' => $this->current_page (), 'total' => $this->total_pages (), 'end_size' => 3, 'mid_size' => 2, 'prev_next' => true)); |
| 476 |
return $text.$links; |
| 477 |
} |
| 478 |
} |
| 479 |
|
| 480 |
?> |