1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @package Fuel\Common |
4
|
|
|
* @version 2.0 |
5
|
|
|
* @author Fuel Development Team |
6
|
|
|
* @license MIT License |
7
|
|
|
* @copyright 2010 - 2015 Fuel Development Team |
8
|
|
|
* @link http://fuelphp.com |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Fuel\Common; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* @since 1.0 |
15
|
|
|
*/ |
16
|
|
|
class Pagination |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* @var Fuel\Foundation\Input current input instance |
20
|
|
|
*/ |
21
|
|
|
protected $input; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var Fuel\Display\View pagination view |
25
|
|
|
*/ |
26
|
|
|
protected $view; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var array configuration values |
30
|
|
|
*/ |
31
|
|
|
protected $config = array( |
32
|
|
|
'current' => 0, |
33
|
|
|
'offset' => 0, |
34
|
|
|
'limit' => 0, |
35
|
|
|
'totalPages' => 0, |
36
|
|
|
'totalItems' => 0, |
37
|
|
|
'numberOfLinks' => 3, |
38
|
|
|
'uriSegment' => 3, |
39
|
|
|
'getVariable' => '', |
40
|
|
|
'showFirst' => false, |
41
|
|
|
'showLast' => false, |
42
|
|
|
'linkOffset' => 0.5, |
43
|
|
|
'align' => 'center', |
44
|
|
|
); |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var string the pagination Url template. Use {page} for the location of the page number |
48
|
|
|
*/ |
49
|
|
|
protected $paginationUrl = null; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Construct a pagination object |
53
|
|
|
* |
54
|
|
|
* @param Fuel\Display\Manager The current applications ViewManager object |
55
|
|
|
* @param Fuel\Foundation\Input The current requests Input container |
56
|
|
|
* @param string View to be used to generate the pagination HTML |
57
|
|
|
* |
58
|
|
|
*/ |
59
|
|
|
public function __construct($viewmanager, $input, $view) |
60
|
|
|
{ |
61
|
|
|
// store the input instance passed |
62
|
|
|
$this->input = $input; |
63
|
|
|
|
64
|
|
|
// construct the pagination view |
65
|
|
|
$this->view = $viewmanager->forge($view); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Render the pagination view when the object is cast to string or echo'd |
70
|
|
|
*/ |
71
|
|
|
public function __toString() |
72
|
|
|
{ |
73
|
|
|
return (string) $this->render(); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Getter for configuration items |
78
|
|
|
*/ |
79
|
|
|
public function __get($key) |
80
|
|
|
{ |
81
|
|
|
return $this->get($key); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Getter for configuration items |
86
|
|
|
*/ |
87
|
|
|
public function get($key) |
88
|
|
|
{ |
89
|
|
|
if (isset($this->config[$key])) |
90
|
|
|
{ |
91
|
|
|
return $this->config[$key]; |
92
|
|
|
} |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Setter for configuration items |
97
|
|
|
*/ |
98
|
|
|
public function __set($key, $value) |
99
|
|
|
{ |
100
|
|
|
$this->set($key, $value); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Setter for configuration items |
105
|
|
|
*/ |
106
|
|
|
public function set($var, $value = null) |
107
|
|
|
{ |
108
|
|
|
if ( ! is_array($var)) |
109
|
|
|
{ |
110
|
|
|
$var = array ($var => $value); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
foreach ($var as $key => $value) |
114
|
|
|
{ |
115
|
|
|
$value = $this->validateConfig($key, $value); |
116
|
|
|
|
117
|
|
|
if (isset($this->config[$key])) |
118
|
|
|
{ |
119
|
|
|
// preserve the type |
120
|
|
|
if (is_bool($this->config[$key])) |
121
|
|
|
{ |
122
|
|
|
$this->config[$key] = (bool) $value; |
123
|
|
|
} |
124
|
|
|
elseif (is_string($this->config[$key])) |
125
|
|
|
{ |
126
|
|
|
$this->config[$key] = (string) $value; |
127
|
|
|
} |
128
|
|
|
else |
129
|
|
|
{ |
130
|
|
|
$this->config[$key] = (int) $value; |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Setter for the pagination url. It must contain a {page} placeholder for the page number |
138
|
|
|
* |
139
|
|
|
* @param string $url The pagination Url template to be used to generate link urls |
140
|
|
|
*/ |
141
|
|
|
public function setUrl($url) |
142
|
|
|
{ |
143
|
|
|
$this->paginationUrl = $url; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Render the pagination view, and return the view |
148
|
|
|
* |
149
|
|
|
* @return View the configured view object |
150
|
|
|
*/ |
151
|
|
|
public function render() |
152
|
|
|
{ |
153
|
|
|
// make sure we have a correct url |
154
|
|
|
$this->paginationUrl(); |
155
|
|
|
|
156
|
|
|
// and a current page number |
157
|
|
|
$this->calculateNumbers(); |
158
|
|
|
|
159
|
|
|
$urls = array(); |
160
|
|
|
|
161
|
|
|
// generate the URL's for the pagination block |
162
|
|
|
if ($this->config['totalPages'] > 1) |
163
|
|
|
{ |
164
|
|
|
// calculate start- and end page numbers |
165
|
|
|
$start = $this->config['current'] - floor($this->config['numberOfLinks'] * $this->config['linkOffset']); |
166
|
|
|
$end = $this->config['current'] + floor($this->config['numberOfLinks'] * ( 1 - $this->config['linkOffset'])); |
167
|
|
|
|
168
|
|
|
// adjust for the first few pages |
169
|
|
|
if ($start < 1) |
170
|
|
|
{ |
171
|
|
|
$end -= $start - 1; |
172
|
|
|
$start = 1; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
// make sure we don't overshoot the current page due to rounding issues |
176
|
|
|
if ($end < $this->config['current']) |
177
|
|
|
{ |
178
|
|
|
$start++; |
179
|
|
|
$end++; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
// make sure we don't overshoot the total |
183
|
|
|
if ($end > $this->config['totalPages']) |
184
|
|
|
{ |
185
|
|
|
$start = max(1, $start - $end + $this->config['totalPages']); |
186
|
|
|
$end = $this->config['totalPages']; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
// now generate the URL's for the pagination block |
190
|
|
|
for($i = $start; $i <= $end; $i++) |
191
|
|
|
{ |
192
|
|
|
$urls[$i] = $this->generateUrl($i); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
// send the generated url's to the view |
197
|
|
|
$this->view->set('urls', $urls); |
198
|
|
|
|
199
|
|
|
// store the current and total pages |
200
|
|
|
$this->view->set('active', $this->config['current']); |
201
|
|
|
$this->view->set('total', $this->config['totalPages']); |
202
|
|
|
|
203
|
|
|
// do we need to add a first link? |
204
|
|
|
if ($this->config['showFirst']) |
205
|
|
|
{ |
206
|
|
|
$this->view->set('first', $this->generateUrl(1)); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
if (isset($start) and $start > 1) |
210
|
|
|
{ |
211
|
|
|
$this->view->set('previous', $this->generateUrl($start-1)); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
if (isset($end) and $end !== $this->config['totalPages']) |
215
|
|
|
{ |
216
|
|
|
$this->view->set('next', $this->generateUrl($end+1)); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
// do we need to add a last link? |
220
|
|
|
if ($this->config['showLast']) |
221
|
|
|
{ |
222
|
|
|
$this->view->set('last', $this->generateUrl($this->config['totalPages'])); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
$this->view->set('align', $this->config['align']); |
226
|
|
|
|
227
|
|
|
// return the view |
228
|
|
|
return $this->view; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Generate the link to a particular page |
233
|
|
|
* |
234
|
|
|
* @param int $link page number |
235
|
|
|
* |
236
|
|
|
* @return string generated link to a page |
237
|
|
|
*/ |
238
|
|
|
protected function generateUrl($link) |
239
|
|
|
{ |
240
|
|
|
return str_replace('{page}', $link, $this->paginationUrl); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Construct the pagination Url from the current Url and the configuration set |
245
|
|
|
*/ |
246
|
|
|
protected function paginationUrl() |
247
|
|
|
{ |
248
|
|
|
// if we have one, don't bother |
249
|
|
|
if ( ! empty($this->paginationUrl)) |
250
|
|
|
{ |
251
|
|
|
return; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
// do we have any GET variables? |
255
|
|
|
$get = $this->input->getQuery(); |
256
|
|
|
|
257
|
|
|
// do we need to set one |
258
|
|
|
if ( ! empty($this->config['getVariable'])) |
259
|
|
|
{ |
260
|
|
|
// don't use curly braces here, http_build_query will encode them |
261
|
|
|
$get[$this->config['getVariable']] = '___PAGE___'; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
// do we need to create a segment? |
265
|
|
|
if ( ! empty($this->config['uriSegment'])) |
266
|
|
|
{ |
267
|
|
|
$segments = explode('/', trim($this->input->getPathInfo(),'/')); |
268
|
|
|
$segments[$this->config['uriSegment']] = '{page}'; |
269
|
|
|
|
270
|
|
|
// construct the Uri |
271
|
|
|
$this->paginationUrl = '/'.implode('/', $segments); |
272
|
|
|
} |
273
|
|
|
else |
274
|
|
|
{ |
275
|
|
|
// start with the current Uri |
276
|
|
|
$this->paginationUrl = $this->input->getPathInfo(); |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
// attach the extension if needed |
280
|
|
|
$this->paginationUrl .= $this->input->getExtension(); |
281
|
|
|
|
282
|
|
|
// any get variables? |
283
|
|
|
if ( ! empty($get)) |
284
|
|
|
{ |
285
|
|
|
$this->paginationUrl .= '?'.str_replace('___PAGE___', '{page}', http_build_query($get->getContents())); |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* If no current page number is given, calculate it |
291
|
|
|
*/ |
292
|
|
|
protected function calculateNumbers() |
293
|
|
|
{ |
294
|
|
|
// do we need to fetch or calculate the current page number? |
295
|
|
|
if (empty($this->config['current'])) |
296
|
|
|
{ |
297
|
|
|
// do we have a segment number? |
298
|
|
|
if ( ! empty($this->config['uriSegment'])) |
299
|
|
|
{ |
300
|
|
|
$segments = explode('/', trim($this->input->getPathInfo(),'/')); |
301
|
|
View Code Duplication |
if (isset($segments[$this->config['uriSegment']]) and is_numeric($segments[$this->config['uriSegment']])) |
|
|
|
|
302
|
|
|
{ |
303
|
|
|
$this->config['current'] = $segments[$this->config['uriSegment']]; |
304
|
|
|
} |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
// do we have a getVariable set? |
308
|
|
|
if ( ! empty($this->config['getVariable']) and $get = $this->input->getQuery()) |
309
|
|
|
{ |
310
|
|
View Code Duplication |
if (isset($get[$this->config['getVariable']]) and is_numeric($get[$this->config['getVariable']])) |
|
|
|
|
311
|
|
|
{ |
312
|
|
|
$this->config['current'] = $get[$this->config['uriSegment']]; |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
// if none could be determine, try to calculate it |
317
|
|
View Code Duplication |
if (empty($this->config['current']) and $this->config['offset'] and $this->config['limit']) |
|
|
|
|
318
|
|
|
{ |
319
|
|
|
$this->config['current'] = (int) ($this->config['offset'] / $this->config['limit']) + 1; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
// if all else fails, default to one |
323
|
|
|
if (empty($this->config['current'])) |
324
|
|
|
{ |
325
|
|
|
$this->config['current'] = 1; |
326
|
|
|
} |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
// do we need to calculate the total number of pages |
330
|
|
View Code Duplication |
if (empty($this->config['totalPages']) and ! empty($this->config['totalItems']) and ! empty($this->config['limit'])) |
|
|
|
|
331
|
|
|
{ |
332
|
|
|
$this->config['totalPages'] = (int) ($this->config['totalItems'] / $this->config['limit']) + 1; |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Generate a pagination link |
338
|
|
|
*/ |
339
|
|
|
protected function validateConfig($name, $value) |
340
|
|
|
{ |
341
|
|
|
switch ($name) |
342
|
|
|
{ |
343
|
|
|
case 'offset': |
344
|
|
|
case 'totalItems': |
345
|
|
|
// make sure it's an integer |
346
|
|
|
if ($value != intval($value)) |
347
|
|
|
{ |
348
|
|
|
$value = 0; |
349
|
|
|
} |
350
|
|
|
// and that it's within bounds |
351
|
|
|
$value = max(0, $value); |
352
|
|
|
break; |
353
|
|
|
|
354
|
|
|
// validate integer values |
355
|
|
|
case 'current': |
356
|
|
|
case 'limit': |
357
|
|
|
case 'totalPages': |
358
|
|
|
case 'numberOfLinks': |
359
|
|
|
case 'uriSegment': |
360
|
|
|
// make sure it's an integer |
361
|
|
|
if ($value != intval($value)) |
362
|
|
|
{ |
363
|
|
|
$value = 1; |
364
|
|
|
} |
365
|
|
|
// and that it's within bounds |
366
|
|
|
$value = max(1, $value); |
367
|
|
|
break; |
368
|
|
|
|
369
|
|
|
// validate booleans |
370
|
|
|
case 'showFirst': |
371
|
|
|
case 'showLast': |
372
|
|
|
if ( ! is_bool($value)) |
373
|
|
|
{ |
374
|
|
|
$value = (bool) $value; |
375
|
|
|
} |
376
|
|
|
break; |
377
|
|
|
|
378
|
|
|
// possible alignment values |
379
|
|
|
case 'align': |
380
|
|
|
if ( ! in_array($value = strtolower($value), array('left', 'center', 'right'))) |
381
|
|
|
{ |
382
|
|
|
$value = 'center'; |
383
|
|
|
} |
384
|
|
|
break; |
385
|
|
|
|
386
|
|
|
// validate the link offset, and adjust if needed |
387
|
|
|
case 'linkOffset': |
388
|
|
|
// make sure we have a fraction between 0 and 1 |
389
|
|
|
if ($value > 1) |
390
|
|
|
{ |
391
|
|
|
$value = $value / 100; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
// and that it's within bounds |
395
|
|
|
$value = max(0.01, min($value, 0.99)); |
396
|
|
|
break; |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
return $value; |
400
|
|
|
} |
401
|
|
|
} |
402
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.