Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* SVN FILE: $Id: paginator.php 8004 2009-01-16 20:15:21Z gwoo $ */
3
/**
4
 * Pagination Helper class file.
5
 *
6
 * Generates pagination links
7
 *
8
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
9
 * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
10
 *
11
 * Licensed under The MIT License
12
 * Redistributions of files must retain the above copyright notice.
13
 *
14
 * @filesource
15
 * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
16
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
17
 * @package       cake
18
 * @subpackage    cake.cake.libs.view.helpers
19
 * @since         CakePHP(tm) v 1.2.0
20
 * @version       $Revision: 8004 $
21
 * @modifiedby    $LastChangedBy: gwoo $
22
 * @lastmodified  $Date: 2009-01-16 12:15:21 -0800 (Fri, 16 Jan 2009) $
23
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
24
 */
25
/**
26
 * Pagination Helper class for easy generation of pagination links.
27
 *
28
 * PaginationHelper encloses all methods needed when working with pagination.
29
 *
30
 * @package       cake
31
 * @subpackage    cake.cake.libs.view.helpers
32
 */
33
class PaginatorHelper extends AppHelper {
34
/**
35
 * Helper dependencies
36
 *
37
 * @var array
38
 */
39
	var $helpers = array('Html', 'Ajax');
40
/**
41
 * Holds the default model for paged recordsets
42
 *
43
 * @var string
44
 */
45
	var $__defaultModel = null;
46
/**
47
 * Holds the default options for pagination links
48
 *
49
 * The values that may be specified are:
50
 * - <i>$options['format']</i> Format of the counter. Supported formats are 'range' and 'pages'
51
 *                             and custom (default). In the default mode the supplied string is
52
 *                             parsed and constants are replaced by their actual values.
53
 *                             Constants: %page%, %pages%, %current%, %count%, %start%, %end% .
54
 * - <i>$options['separator']</i> The separator of the actual page and number of pages (default: ' of ').
55
 * - <i>$options['url']</i> Url of the action. See Router::url()
56
 * - <i>$options['url']['sort']</i>  the key that the recordset is sorted.
57
 * - <i>$options['url']['direction']</i> Direction of the sorting (default: 'asc').
58
 * - <i>$options['url']['page']</i> Page # to display.
59
 * - <i>$options['model']</i> The name of the model.
60
 * - <i>$options['escape']</i> Defines if the title field for the link should be escaped (default: true).
61
 * - <i>$options['update']</i> DOM id of the element updated with the results of the AJAX call.
62
 *                             If this key isn't specified Paginator will use plain HTML links.
63
 * - <i>$options['indicator']</i> DOM id of the element that will be shown when doing AJAX requests.
64
 *
65
 * @var array
66
 */
67
	var $options = array();
68
/**
69
 * Gets the current page of the in the recordset for the given model
70
 *
71
 * @param  string $model Optional model name.  Uses the default if none is specified.
72
 * @return string The current page number of the paginated resultset.
73
 */
74
	function params($model = null) {
75
		if (empty($model)) {
76
			$model = $this->defaultModel();
77
		}
78
		if (!isset($this->params['paging']) || empty($this->params['paging'][$model])) {
79
			return null;
80
		}
81
		return $this->params['paging'][$model];
82
	}
83
/**
84
 * Sets default options for all pagination links
85
 *
86
 * @param  mixed $options Default options for pagination links. If a string is supplied - it
87
 *                        is used as the DOM id element to update. See #options for list of keys.
88
 */
89
	function options($options = array()) {
90
		if (is_string($options)) {
91
			$options = array('update' => $options);
92
		}
93
 
94
		if (!empty($options['paging'])) {
95
			if (!isset($this->params['paging'])) {
96
				$this->params['paging'] = array();
97
			}
98
			$this->params['paging'] = array_merge($this->params['paging'], $options['paging']);
99
			unset($options['paging']);
100
		}
101
		$model = $this->defaultModel();
102
 
103
		if (!empty($options[$model])) {
104
			if (!isset($this->params['paging'][$model])) {
105
				$this->params['paging'][$model] = array();
106
			}
107
			$this->params['paging'][$model] = array_merge($this->params['paging'][$model], $options[$model]);
108
			unset($options[$model]);
109
		}
110
		$this->options = array_filter(array_merge($this->options, $options));
111
	}
112
/**
113
 * Gets the current page of the recordset for the given model
114
 *
115
 * @param  string $model Optional model name.  Uses the default if none is specified.
116
 * @return string The current page number of the recordset.
117
 */
118
	function current($model = null) {
119
		$params = $this->params($model);
120
 
121
		if (isset($params['page'])) {
122
			return $params['page'];
123
		}
124
		return 1;
125
	}
126
/**
127
 * Gets the current key by which the recordset is sorted
128
 *
129
 * @param  string $model Optional model name.  Uses the default if none is specified.
130
 * @param  mixed $options Options for pagination links. See #options for list of keys.
131
 * @return string The name of the key by which the recordset is being sorted, or
132
 *                null if the results are not currently sorted.
133
 */
134
	function sortKey($model = null, $options = array()) {
135
		if (empty($options)) {
136
			$params = $this->params($model);
137
			$options = array_merge($params['defaults'], $params['options']);
138
		}
139
 
140
		if (isset($options['sort']) && !empty($options['sort'])) {
141
			if (preg_match('/(?:\w+\.)?(\w+)/', $options['sort'], $result) && isset($result[1])) {
142
				return $result[1];
143
			}
144
			return $options['sort'];
145
		} elseif (isset($options['order']) && is_array($options['order'])) {
146
			return preg_replace('/.*\./', '', key($options['order']));
147
		} elseif (isset($options['order']) && is_string($options['order'])) {
148
			if (preg_match('/(?:\w+\.)?(\w+)/', $options['order'], $result) && isset($result[1])) {
149
				return $result[1];
150
			}
151
		}
152
		return null;
153
	}
154
/**
155
 * Gets the current direction the recordset is sorted
156
 *
157
 * @param  string $model Optional model name.  Uses the default if none is specified.
158
 * @param  mixed $options Options for pagination links. See #options for list of keys.
159
 * @return string The direction by which the recordset is being sorted, or
160
 *                null if the results are not currently sorted.
161
 */
162
	function sortDir($model = null, $options = array()) {
163
		$dir = null;
164
 
165
		if (empty($options)) {
166
			$params = $this->params($model);
167
			$options = array_merge($params['defaults'], $params['options']);
168
		}
169
 
170
		if (isset($options['direction'])) {
171
			$dir = strtolower($options['direction']);
172
		} elseif (isset($options['order']) && is_array($options['order'])) {
173
			$dir = strtolower(current($options['order']));
174
		}
175
 
176
		if ($dir == 'desc') {
177
			return 'desc';
178
		}
179
		return 'asc';
180
	}
181
/**
182
 * Generates a "previous" link for a set of paged records
183
 *
184
 * @param  string $title Title for the link. Defaults to '<< Previous'.
185
 * @param  mixed $options Options for pagination link. See #options for list of keys.
186
 * @param  string $disabledTitle Title when the link is disabled.
187
 * @param  mixed $disabledOptions Options for the disabled pagination link. See #options for list of keys.
188
 * @return string A "previous" link or $disabledTitle text if the link is disabled.
189
 */
190
	function prev($title = '<< Previous', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
191
		return $this->__pagingLink('Prev', $title, $options, $disabledTitle, $disabledOptions);
192
	}
193
/**
194
 * Generates a "next" link for a set of paged records
195
 *
196
 * @param  string $title Title for the link. Defaults to 'Next >>'.
197
 * @param  mixed $options Options for pagination link. See #options for list of keys.
198
 * @param  string $disabledTitle Title when the link is disabled.
199
 * @param  mixed $disabledOptions Options for the disabled pagination link. See #options for list of keys.
200
 * @return string A "next" link or or $disabledTitle text if the link is disabled.
201
 */
202
	function next($title = 'Next >>', $options = array(), $disabledTitle = null, $disabledOptions = array()) {
203
		return $this->__pagingLink('Next', $title, $options, $disabledTitle, $disabledOptions);
204
	}
205
/**
206
 * Generates a sorting link
207
 *
208
 * @param  string $title Title for the link.
209
 * @param  string $key The name of the key that the recordset should be sorted.
210
 * @param  array $options Options for sorting link. See #options for list of keys.
211
 * @return string A link sorting default by 'asc'. If the resultset is sorted 'asc' by the specified
212
 *                key the returned link will sort by 'desc'.
213
 */
214
	function sort($title, $key = null, $options = array()) {
215
		$options = array_merge(array('url' => array(), 'model' => null), $options);
216
		$url = $options['url'];
217
		unset($options['url']);
218
 
219
		if (empty($key)) {
220
			$key = $title;
221
			$title = __(Inflector::humanize(preg_replace('/_id$/', '', $title)), true);
222
		}
223
		$dir = 'asc';
224
		$model = null;
225
 
226
		if (strpos($key, '.') !== false) {
227
			list($model, $key) = explode('.', $key);
228
			$model = $model . '.';
229
		}
230
		if ($this->sortKey($options['model']) == $key && $this->sortDir($options['model']) == 'asc') {
231
			$dir = 'desc';
232
		}
233
		if (is_array($title) && array_key_exists($dir, $title)) {
234
			$title = $title[$dir];
235
		}
236
		$url = array_merge(array('sort' => $model . $key, 'direction' => $dir), $url, array('order' => null));
237
		return $this->link($title, $url, $options);
238
	}
239
/**
240
 * Generates a plain or Ajax link with pagination parameters
241
 *
242
 * @param  string $title Title for the link.
243
 * @param  mixed $url Url for the action. See Router::url()
244
 * @param  array $options Options for the link. See #options for list of keys.
245
 * @return string A link with pagination parameters.
246
 */
247
	function link($title, $url = array(), $options = array()) {
248
		$options = array_merge(array('model' => null, 'escape' => true), $options);
249
		$model = $options['model'];
250
		unset($options['model']);
251
 
252
		if (!empty($this->options)) {
253
			$options = array_merge($this->options, $options);
254
		}
255
		if (isset($options['url'])) {
256
			$url = array_merge((array)$options['url'], (array)$url);
257
			unset($options['url']);
258
		}
259
		$url = $this->url($url, true, $model);
260
 
261
		$obj = isset($options['update']) ? 'Ajax' : 'Html';
262
		$url = array_merge(array('page' => $this->current($model)), $url);
263
		return $this->{$obj}->link($title, Set::filter($url, true), $options);
264
	}
265
/**
266
 * Merges passed URL options with current pagination state to generate a pagination URL.
267
 *
268
 * @param  array $options Pagination/URL options array
269
 * @param  boolean $asArray
270
 * @param  string $model Which model to paginate on
271
 * @return mixed By default, returns a full pagination URL string for use in non-standard contexts (i.e. JavaScript)
272
 */
273
	function url($options = array(), $asArray = false, $model = null) {
274
		$paging = $this->params($model);
275
		$url = array_merge(array_filter(Set::diff(array_merge($paging['defaults'], $paging['options']), $paging['defaults'])), $options);
276
 
277
		if (isset($url['order'])) {
278
			$sort = $direction = null;
279
			if (is_array($url['order'])) {
280
				list($sort, $direction) = array($this->sortKey($model, $url), current($url['order']));
281
				$key = array_keys($url['order']);
282
 
283
				if (strpos($key[0], '.') !== false) {
284
					list($model) = explode('.', $key[0]);
285
					$sort = $model . '.' . $sort;
286
				}
287
			}
288
			unset($url['order']);
289
			$url = array_merge($url, compact('sort', 'direction'));
290
		}
291
 
292
		if ($asArray) {
293
			return $url;
294
		}
295
		return parent::url($url);
296
	}
297
/**
298
 * Protected method for generating prev/next links
299
 *
300
 */
301
	function __pagingLink($which, $title = null, $options = array(), $disabledTitle = null, $disabledOptions = array()) {
302
		$check = 'has' . $which;
303
		$_defaults = array('url' => array(), 'step' => 1, 'escape' => true, 'model' => null, 'tag' => 'div');
304
		$options = array_merge($_defaults, (array)$options);
305
		$paging = $this->params($options['model']);
306
 
307
		if (!$this->{$check}($options['model']) && (!empty($disabledTitle) || !empty($disabledOptions))) {
308
			if (!empty($disabledTitle) && $disabledTitle !== true) {
309
				$title = $disabledTitle;
310
			}
311
			$options = array_merge($_defaults, (array)$disabledOptions);
312
		} elseif (!$this->{$check}($options['model'])) {
313
			return null;
314
		}
315
 
316
		foreach (array_keys($_defaults) as $key) {
317
			${$key} = $options[$key];
318
			unset($options[$key]);
319
		}
320
		$url = array_merge(array('page' => $paging['page'] + ($which == 'Prev' ? $step * -1 : $step)), $url);
321
 
322
		if ($this->{$check}($model)) {
323
			return $this->link($title, $url, array_merge($options, array('escape' => $escape)));
324
		} else {
325
			return $this->Html->tag($tag, $title, $options, $escape);
326
		}
327
	}
328
/**
329
 * Returns true if the given result set is not at the first page
330
 *
331
 * @param string $model Optional model name.  Uses the default if none is specified.
332
 * @return boolean True if the result set is not at the first page.
333
 */
334
	function hasPrev($model = null) {
335
		return $this->__hasPage($model, 'prev');
336
	}
337
/**
338
 * Returns true if the given result set is not at the last page
339
 *
340
 * @param string $model Optional model name.  Uses the default if none is specified.
341
 * @return boolean True if the result set is not at the last page.
342
 */
343
	function hasNext($model = null) {
344
		return $this->__hasPage($model, 'next');
345
	}
346
/**
347
 * Returns true if the given result set has the page number given by $page
348
 *
349
 * @param  string $model Optional model name.  Uses the default if none is specified.
350
 * @param  int $page The page number - if not set defaults to 1.
351
 * @return boolean True if the given result set has the specified page number.
352
 */
353
	function hasPage($model = null, $page = 1) {
354
		if (is_numeric($model)) {
355
			$page = $model;
356
			$model = null;
357
		}
358
		$paging = $this->params($model);
359
		return $page <= $paging['pageCount'];
360
	}
361
/**
362
 * Protected method
363
 *
364
 */
365
	function __hasPage($model, $page) {
366
		$params = $this->params($model);
367
		if (!empty($params)) {
368
			if ($params["{$page}Page"] == true) {
369
				return true;
370
			}
371
		}
372
		return false;
373
	}
374
/**
375
 * Gets the default model of the paged sets
376
 *
377
 * @return string Model name or null if the pagination isn't initialized.
378
 */
379
	function defaultModel() {
380
		if ($this->__defaultModel != null) {
381
			return $this->__defaultModel;
382
		}
383
		if (empty($this->params['paging'])) {
384
			return null;
385
		}
386
		list($this->__defaultModel) = array_keys($this->params['paging']);
387
		return $this->__defaultModel;
388
	}
389
/**
390
 * Returns a counter string for the paged result set
391
 *
392
 * @param  mixed $options Options for the counter string. See #options for list of keys.
393
 * @return string Counter string.
394
 */
395
	function counter($options = array()) {
396
		if (is_string($options)) {
397
			$options = array('format' => $options);
398
		}
399
 
400
		$options = array_merge(
401
			array(
402
				'model' => $this->defaultModel(),
403
				'format' => 'pages',
404
				'separator' => ' of '
405
			),
406
		$options);
407
 
408
		$paging = $this->params($options['model']);
409
		if ($paging['pageCount'] == 0) {
410
			$paging['pageCount'] = 1;
411
		}
412
		$start = 0;
413
		if ($paging['count'] >= 1) {
414
			$start = (($paging['page'] - 1) * $paging['options']['limit']) + 1;
415
		}
416
		$end = $start + $paging['options']['limit'] - 1;
417
		if ($paging['count'] < $end) {
418
			$end = $paging['count'];
419
		}
420
 
421
		switch ($options['format']) {
422
			case 'range':
423
				if (!is_array($options['separator'])) {
424
					$options['separator'] = array(' - ', $options['separator']);
425
				}
426
				$out = $start . $options['separator'][0] . $end . $options['separator'][1] . $paging['count'];
427
			break;
428
			case 'pages':
429
				$out = $paging['page'] . $options['separator'] . $paging['pageCount'];
430
			break;
431
			default:
432
				$replace = array(
433
					'%page%' => $paging['page'],
434
					'%pages%' => $paging['pageCount'],
435
					'%current%' => $paging['current'],
436
					'%count%' => $paging['count'],
437
					'%start%' => $start,
438
					'%end%' => $end
439
				);
440
				$out = str_replace(array_keys($replace), array_values($replace), $options['format']);
441
			break;
442
		}
443
		return $this->output($out);
444
	}
445
/**
446
 * Returns a set of numbers for the paged result set
447
 * uses a modulus to decide how many numbers to show on each side of the current page (default: 8)
448
 *
449
 * @param  mixed $options Options for the numbers, (before, after, model, modulus, separator)
450
 * @return string numbers string.
451
 */
452
	function numbers($options = array()) {
453
		if ($options === true) {
454
			$options = array(
455
						'before' => ' | ', 'after' => ' | ',
456
						'first' => 'first', 'last' => 'last',
457
						);
458
		}
459
 
460
		$options = array_merge(
461
			array(
462
				'tag' => 'span',
463
				'before'=> null, 'after'=> null,
464
				'model' => $this->defaultModel(),
465
				'modulus' => '8', 'separator' => ' | ',
466
				'first' => null, 'last' => null,
467
			),
468
		(array)$options);
469
 
470
		$params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
471
		unset($options['model']);
472
 
473
		if ($params['pageCount'] <= 1) {
474
			return false;
475
		}
476
 
477
		extract($options);
478
		unset($options['tag'], $options['before'], $options['after'], $options['model'],
479
			$options['modulus'], $options['separator'], $options['first'], $options['last']);
480
 
481
		$out = '';
482
 
483
		if ($modulus && $params['pageCount'] > $modulus) {
484
			$half = intval($modulus / 2);
485
			$end = $params['page'] + $half;
486
 
487
			if ($end > $params['pageCount']) {
488
				$end = $params['pageCount'];
489
			}
490
			$start = $params['page'] - ($modulus - ($end - $params['page']));
491
			if ($start <= 1) {
492
				$start = 1;
493
				$end = $params['page'] + ($modulus  - $params['page']) + 1;
494
			}
495
 
496
			if ($first && $start > (int)$first) {
497
				if ($start == $first + 1) {
498
					$out .= $this->first($first, array('tag' => $tag, 'after' => $separator));
499
				} else {
500
					$out .= $this->first($first, array('tag' => $tag));
501
				}
502
			}
503
 
504
			$out .= $before;
505
 
506
			for ($i = $start; $i < $params['page']; $i++) {
507
				$out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options)) . $separator;
508
			}
509
 
510
			$out .= $this->Html->tag($tag, $params['page'], array('class' => 'current'));
511
			if ($i != $params['pageCount']) {
512
				$out .= $separator;
513
			}
514
 
515
			$start = $params['page'] + 1;
516
			for ($i = $start; $i < $end; $i++) {
517
				$out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options)). $separator;
518
			}
519
 
520
			if ($end != $params['page']) {
521
				$out .= $this->Html->tag($tag, $this->link($i, array('page' => $end), $options));
522
			}
523
 
524
			$out .= $after;
525
 
526
			if ($last && $end <= $params['pageCount'] - (int)$last) {
527
				if ($end + 1 == $params['pageCount']) {
528
					$out .= $this->last($last, array('tag' => $tag, 'before' => $separator));
529
				} else {
530
					$out .= $this->last($last, array('tag' => $tag));
531
				}
532
			}
533
 
534
		} else {
535
			$out .= $before;
536
 
537
			for ($i = 1; $i <= $params['pageCount']; $i++) {
538
				if ($i == $params['page']) {
539
					$out .= $this->Html->tag($tag, $i, array('class' => 'current'));
540
				} else {
541
					$out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
542
				}
543
				if ($i != $params['pageCount']) {
544
					$out .= $separator;
545
				}
546
			}
547
 
548
			$out .= $after;
549
		}
550
 
551
		return $this->output($out);
552
	}
553
/**
554
 * Returns a first or set of numbers for the first pages
555
 *
556
 * @param  mixed $first if string use as label for the link, if numeric print page numbers
557
 * @param  mixed $options
558
 * @return string numbers string.
559
 */
560
	function first($first = '<< first', $options = array()) {
561
		$options = array_merge(
562
			array(
563
				'tag' => 'span',
564
				'after'=> null,
565
				'model' => $this->defaultModel(),
566
				'separator' => ' | ',
567
			),
568
		(array)$options);
569
 
570
		$params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
571
		unset($options['model']);
572
 
573
		if ($params['pageCount'] <= 1) {
574
			return false;
575
		}
576
		extract($options);
577
		unset($options['tag'], $options['after'], $options['model'], $options['separator']);
578
 
579
		$out = '';
580
 
581
		if (is_int($first) && $params['page'] > $first) {
582
			if ($after === null) {
583
				$after = '...';
584
			}
585
			for ($i = 1; $i <= $first; $i++) {
586
				$out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
587
				if ($i != $first) {
588
					$out .= $separator;
589
				}
590
			}
591
			$out .= $after;
592
		} elseif ($params['page'] > 1) {
593
			$out = $this->Html->tag($tag, $this->link($first, array('page' => 1), $options)) . $after;
594
		}
595
		return $out;
596
	}
597
/**
598
 * Returns a last or set of numbers for the last pages
599
 *
600
 * @param  mixed $last if string use as label for the link, if numeric print page numbers
601
 * @param  mixed $options
602
 * @return string numbers string.
603
 */
604
	function last($last = 'last >>', $options = array()) {
605
		$options = array_merge(
606
			array(
607
				'tag' => 'span',
608
				'before'=> null,
609
				'model' => $this->defaultModel(),
610
				'separator' => ' | ',
611
			),
612
		(array)$options);
613
 
614
		$params = array_merge(array('page'=> 1), (array)$this->params($options['model']));
615
		unset($options['model']);
616
 
617
		if ($params['pageCount'] <= 1) {
618
			return false;
619
		}
620
 
621
		extract($options);
622
		unset($options['tag'], $options['before'], $options['model'], $options['separator']);
623
 
624
		$out = '';
625
		$lower = $params['pageCount'] - $last + 1;
626
 
627
		if (is_int($last) && $params['page'] < $lower) {
628
			if ($before === null) {
629
				$before = '...';
630
			}
631
			for ($i = $lower; $i <= $params['pageCount']; $i++) {
632
				$out .= $this->Html->tag($tag, $this->link($i, array('page' => $i), $options));
633
				if ($i != $params['pageCount']) {
634
					$out .= $separator;
635
				}
636
			}
637
			$out = $before . $out;
638
		} elseif ($params['page'] < $params['pageCount']) {
639
			$out = $before . $this->Html->tag($tag, $this->link($last, array('page' => $params['pageCount']), $options));
640
		}
641
		return $out;
642
	}
643
}
644
?>