1: <?php
2: /**
3: * This file is part of the PHPLucidFrame library.
4: * Core utility for pagination
5: *
6: * @package PHPLucidFrame\Core
7: * @since PHPLucidFrame v 1.0.0
8: * @copyright Copyright (c), PHPLucidFrame.
9: * @link http://phplucidframe.com
10: * @license http://www.opensource.org/licenses/mit-license.php MIT License
11: *
12: * This source file is subject to the MIT license that is bundled
13: * with this source code in the file LICENSE
14: */
15:
16: namespace LucidFrame\Core;
17:
18: /**
19: * Core utility for pagination
20: */
21: class Pager
22: {
23: /** @var int The current page no. */
24: private $page = 1;
25: /** @var int The customized query string name for "page" */
26: private $pageQueryStr = 'page';
27: /** @var int No. of items per page to display */
28: private $itemsPerPage = 15;
29: /** @var int How many page no. to show in the pagination */
30: private $pageNumLimit = 5;
31: /** @var string The absolute image directory path where the navigaion arrow images reside */
32: private $imagePath = '';
33: /** @var boolean AJAX pager or not */
34: private $ajax = false;
35: /** @var string The URL to request if it is different than the current URL; it must be relative to APP ROOT */
36: private $url = '';
37: /** @var int Total number of records for the pager */
38: private $total = 0;
39: /** @var int The calculated offset for the page */
40: private $offset = 0;
41: /** @var boolean The page is enabled or not */
42: private $enabled = true;
43: /** @var string HTML tag for the pagination display; default is table. ul and div are also allowed. */
44: private $htmlTag = 'table';
45: /** @var string HTML tag for internal use */
46: private $parentOpenTag;
47: /** @var string HTML tag for internal use */
48: private $parentCloseTag;
49: /** @var string HTML tag for internal use */
50: private $childTag;
51: /** @var array The array of calculated result pages and offset */
52: private $result;
53: /** @var string The session name for the last page number */
54: private $name = 'lc_last_page';
55: /** @var callable The callback function for customized display */
56: private $displayCallback;
57: /** @var string CSS class name for pagination container */
58: private $className = 'lc-pager';
59:
60: /**
61: * Constructor
62: * @param string $pageQueryStr The customized page query string name
63: */
64: public function __construct($pageQueryStr = '')
65: {
66: if ($pageQueryStr) {
67: $this->pageQueryStr = $pageQueryStr;
68: }
69: $page = _arg($this->pageQueryStr);
70: $this->page = $page ?: 1;
71: }
72:
73: /**
74: * Setter functions for the properties
75: * @param string $key The property name
76: * @param mixed $value The value to be set to the property
77: * @return object Pager
78: */
79: public function set($key, $value = '')
80: {
81: if (isset($this->$key)) {
82: $this->$key = $value;
83: }
84: if ($key == 'htmlTag') {
85: $this->setHtmlTag($value);
86: }
87:
88: return $this;
89: }
90:
91: /**
92: * Getter functions for the properties
93: * @param string $key The property name
94: * @return mixed The value of the property
95: */
96: public function get($key)
97: {
98: return isset($this->$key) ? $this->$key : '';
99: }
100:
101: /**
102: * Setter functions for the property "htmlTag"
103: *
104: * @param string $tag The HTML tag - table, ul or div
105: * @return object Pager
106: * @internal
107: * @ignore
108: */
109: private function setHtmlTag($tag = 'table')
110: {
111: if (!in_array($tag, array('table', 'ul', 'div'))) {
112: $this->htmlTag = 'table';
113: }
114:
115: switch ($this->htmlTag) {
116: case 'table':
117: $this->parentOpenTag = '<table class="' . $this->className . '" border="0" cellpadding="0" cellspacing="0"><tr>';
118: $this->parentCloseTag = '</tr></table>';
119: $this->childTag = 'td';
120: break;
121:
122: case 'ul':
123: $this->parentOpenTag = '<ul class="' . $this->className . '">';
124: $this->parentCloseTag = '</ul>';
125: $this->childTag = 'li';
126: break;
127:
128: case 'div':
129: $this->parentOpenTag = '<div class="' . $this->className . '">';
130: $this->parentCloseTag = '</div>';
131: $this->childTag = 'div';
132: break;
133: }
134:
135: return $this;
136: }
137:
138: /**
139: * Pager calculation function
140: * Before calling this function, the following property must be set:
141: *
142: * - $page
143: * - $itemsPerPage
144: * - $pageNumLimit
145: * - $total
146: *
147: * The array of the offsets
148: * Array(
149: * [offset] => xx
150: * [thisPage] => xx
151: * [beforePages] => Array()
152: * [afterPages] => Array()
153: * [firstPageEnable] => xx
154: * [prePageEnable] => xx
155: * [nextPageNo] => xx
156: * [nextPageEnable] => xx
157: * [lastPageNo] => xx
158: * [lastPageEnable] => xx
159: * )
160: *
161: * @return object Pager
162: */
163: public function calculate()
164: {
165:
166: if (!($this->page && $this->itemsPerPage && $this->pageNumLimit && $this->total)) {
167: $this->enabled = false;
168: return $this;
169: }
170:
171: if (!is_numeric($this->page)) {
172: $this->page = 1;
173: }
174: $this->offset = ($this->page - 1) * $this->itemsPerPage;
175:
176: $nav = array();
177: $nav['offset'] = $this->offset;
178: $nav['thisPage'] = $this->page;
179:
180: $maxPage = ceil($this->total / $this->itemsPerPage);
181: if ($this->page <= $this->pageNumLimit) {
182: $startPage = 1;
183: } else {
184: $startPage = (floor(($this->page - 1) / $this->pageNumLimit) * $this->pageNumLimit) + 1;
185: }
186:
187: $j = 0;
188: $k = 0;
189: $nav['beforePages'] = array();
190: $nav['afterPages'] = array();
191: for ($pageCount = 0, $i = $startPage; $i <= $maxPage; $i++) {
192: if ($i < $this->page) {
193: $nav['beforePages'][$j] = $i;
194: $j++;
195: }
196:
197: if ($i > $this->page) {
198: $nav['afterPages'][$k] = $i;
199: $k++;
200: }
201:
202: $pageCount++;
203: if ($pageCount == $this->pageNumLimit) {
204: # display page number only.
205: break;
206: }
207: }
208:
209: # First Page
210: if ($this->page > 1) {
211: $nav['firstPageNo'] = 1;
212: $nav['firstPageEnable'] = 1;
213: } else {
214: $nav['firstPageEnable'] = 0;
215: }
216:
217: # Previous Page
218: if ($this->page > 1) {
219: $nav['prePageNo'] = $this->page - 1;
220: $nav['prePageEnable'] = 1;
221: } else {
222: $nav['prePageEnable'] = 0;
223: }
224:
225: # Next page no.
226: if ($this->page < $maxPage) {
227: $nav['nextPageNo'] = $this->page + 1;
228: $nav['nextPageEnable'] = 1;
229:
230: $nav['lastPageNo'] = $maxPage;
231: $nav['lastPageEnable'] = 1;
232:
233: } else {
234: $nav['nextPageEnable'] = 0;
235: $nav['lastPageEnable'] = 0;
236: }
237: # Display multi page or not
238: if (($maxPage <= 1) || ($this->page > $maxPage)) {
239: $this->enabled = false;
240: } else {
241: $this->enabled = true;
242: }
243:
244: # if page count is less than page num limit, fill page num till page num limit
245: if ($maxPage > $this->pageNumLimit) {
246: $allPagesCount = count($nav['beforePages']) + count($nav['afterPages']) + 1;
247: if ($allPagesCount < $this->pageNumLimit) {
248: $page = $this->page - 1;
249: $filledPageCount = $this->pageNumLimit - $allPagesCount;
250: if (isset($nav['beforePages'])) {
251: $filledPageCount += count($nav['beforePages']);
252: }
253: $x = 0;
254: while ($filledPageCount != $x) {
255: $filledPages[] = $page;
256: $page--;
257: $x++;
258: }
259: $nav['beforePages'] = array();
260: sort($filledPages);
261: $nav['beforePages'] = $filledPages;
262: }
263: }
264:
265: $this->result = $nav;
266: return $this;
267: }
268:
269: /**
270: * Display the pagination
271: * @param callable $callback The callback function for customized display
272: * @return void
273: */
274: public function display($callback = null)
275: {
276: if ($callback) {
277: $this->displayCallback = $callback;
278: }
279:
280: session_set($this->name, $this->page);
281:
282: $url = $this->url ?: null;
283: $ajax = $this->ajax;
284: $imagePath = isset($this->imagePath) ? $this->imagePath : '';
285:
286: $this->setHtmlTag($this->htmlTag);
287:
288: if ($this->enabled && $this->result) {
289: if ($this->displayCallback) {
290: $this->result['url'] = $url;
291: $this->result['ajax'] = $ajax;
292: call_user_func($this->displayCallback, $this->result);
293: return;
294: }
295:
296: extract($this->result);
297:
298: echo $this->parentOpenTag;
299: # first
300: if ($firstPageEnable) {
301: echo '<' . $this->childTag . ' class="first-enabled">';
302: if ($ajax) {
303: echo '<a href="' . _url($url) . '" rel="' . $firstPageNo . '">';
304: } else {
305: echo '<a href="' . _url($url, array($this->pageQueryStr => $firstPageNo)) . '">';
306: }
307: if ($imagePath) {
308: echo '<img border="0" src="' . $imagePath . 'start.png" />';
309: } else {
310: echo '<label>' . _t('First') . '</label>';
311: }
312: echo '</a>';
313: echo '</' . $this->childTag . '>';
314: } else {
315: echo '<' . $this->childTag . ' class="first-disabled">';
316: if ($imagePath) {
317: echo '<img border="0" src="' . $imagePath . 'start_disabled.png" />';
318: } else {
319: echo '<label>' . _t('First') . '</label>';
320: }
321: echo '</' . $this->childTag . '>';
322: }
323: # prev
324: if ($prePageEnable) {
325: echo '<' . $this->childTag . ' class="prev-enabled">';
326: if ($ajax) {
327: echo '<a href="' . _url($url) . '" rel="' . $prePageNo . '">';
328: } else {
329: echo '<a href="' . _url($url, array($this->pageQueryStr => $prePageNo)) . '">';
330: }
331: if ($imagePath) {
332: echo '<img border="0" src="' . $imagePath . 'previous.png" />';
333: } else {
334: echo '<label>' . _t('&laquo; Prev') . '</label>';
335: }
336: echo '</a>';
337: echo '</' . $this->childTag . '>';
338: } else {
339: echo '<' . $this->childTag . ' class="prev-disabled">';
340: if ($imagePath) {
341: echo '<img border="0" src="' . $imagePath . 'previous_disabled.png" />';
342: } else {
343: echo '<label>' . _t('&laquo; Prev') . '</label>';
344: }
345: echo '</' . $this->childTag . '>';
346: }
347: echo '<' . $this->childTag . ' class="pages">';
348:
349: # before pages
350: if (isset($beforePages) && $beforePages) {
351: foreach ($beforePages as $oneBeforePage) {
352: echo '<span>';
353: if ($ajax) {
354: echo '<a href="' . _url($url) . '" rel="' . $oneBeforePage . '">' . $oneBeforePage . '</a>';
355: } else {
356: echo '<a href="' . _url($url, array($this->pageQueryStr => $oneBeforePage)) . '">';
357: echo $oneBeforePage;
358: echo '</a>';
359: }
360: echo '</span>';
361: }
362: }
363: echo '<span class="current-page">' . $thisPage . '</span>';
364:
365: # after pages
366: if (isset($afterPages) && $afterPages) {
367: foreach ($afterPages as $oneAfterPage) {
368: echo '<span>';
369: if ($ajax) {
370: echo '<a href="' . _url($url) . '" rel="' . $oneAfterPage . '">' . $oneAfterPage . '</a>';
371: } else {
372: echo '<a href="' . _url($url, array($this->pageQueryStr => $oneAfterPage)) . '">';
373: echo $oneAfterPage;
374: echo '</a>';
375: }
376: echo '</span>';
377: }
378: }
379: echo '</' . $this->childTag . '>';
380:
381: # next
382: if ($nextPageEnable) {
383: echo '<' . $this->childTag . ' class="next-enabled">';
384: if ($ajax) {
385: echo '<a href="' . _url($url) . '" rel="' . $nextPageNo . '">';
386: } else {
387: echo '<a href="' . _url($url, array($this->pageQueryStr => $nextPageNo)) . '">';
388: }
389: if ($imagePath) {
390: echo '<img border="0" src="' . $imagePath . 'next.png" />';
391: } else {
392: echo '<label>' . _t('Next &raquo;') . '</label>';
393: }
394: echo '</a>';
395: echo '</' . $this->childTag . '>';
396: } else {
397: echo '<' . $this->childTag . ' class="next-disabled">';
398: if ($imagePath) {
399: echo '<img border="0" src="' . $imagePath . 'next_disabled.png" />';
400: } else {
401: echo '<label>' . _t('Next &raquo;') . '</label>';
402: }
403: echo '</' . $this->childTag . '>';
404: }
405:
406: # last
407: if ($lastPageEnable) {
408: echo '<' . $this->childTag . ' class="last-enabled">';
409: if ($ajax) {
410: echo '<a href="' . _url($url) . '" rel="' . $lastPageNo . '">';
411: } else {
412: echo '<a href="' . _url($url, array($this->pageQueryStr => $lastPageNo)) . '">';
413: }
414: if ($imagePath) {
415: echo '<img border="0" src="' . $imagePath . 'end.png" />';
416: } else {
417: echo '<label>' . _t('Last') . '</label>';
418: }
419: echo '</a>';
420: echo '</' . $this->childTag . '>';
421: } else {
422: echo '<' . $this->childTag . ' class="last-disabled">';
423: if ($imagePath) {
424: echo '<img border="0" src="' . $imagePath . 'end_disabled.png" />';
425: } else {
426: echo '<label>' . _t('Last') . '</label>';
427: }
428: echo '</' . $this->childTag . '>';
429: }
430:
431: echo $this->parentCloseTag;
432: }
433: }
434: }
435: