PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/cake/libs/view/helpers/paginator.php

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