Passed
Pull Request — development (#3829)
by Spuds
10:19
created

ConstructPageIndex::simpleLinks()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 *
5
 * @package   ElkArte Forum
6
 * @copyright ElkArte Forum contributors
7
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
8
 *
9
 * This file contains code covered by:
10
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
11
 *
12
 * @version 2.0 Beta 1
13
 *
14
 */
15
16
namespace ElkArte\Helper;
17
18
use ElkArte\AbstractModel;
19
20
/**
21
 * Constructs a page list.
22
 *
23
 * What it does:
24
 *
25
 * - Builds the page list, e.g., 1 ... 6 7 [8] 9 10 ... 15.
26
 * - Flexible_start causes it to use "url.page" instead of "url;start=page".
27
 * - Very importantly, cleans up the start value passed, and forces it to
28
 *   be a multiple of num_per_page.
29
 * - Checks that start is not more than max_value.
30
 * - Base_url should be the URL without any start parameter on it.
31
 * - Uses the compactTopicPagesEnable and compactTopicPagesContiguous
32
 *   settings to decide how to display the menu.
33
 * - Substitutes {scripturl} to $scripturl and {base_link} based on Flexible_start
34
 *
35
 * @param string $base_url The base URL to be used for each link.
36
 * @param int &$start The start position, by reference. If this is not a multiple of the number
37
 * of items per page, it is sanitized to be so and the value will persist upon the function's return.
38
 * @param int $max_value The total number of items you are paginating for.
39
 * @param int $num_per_page The number of items to be displayed on a given page.
40
 * @param bool $flexible_start = false Use "url.page" instead of "url;start=page"
41
 * @param array $show associative array of option => boolean paris
42
 *
43
 * @return string
44
 * @example $pageindex = constructPageIndex({scripturl} . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true);
45
 */
46
class ConstructPageIndex extends AbstractModel
47
{
48
	/** @var int stating page */
49
	private $start;
50
51
	/** @var array holds the desired options for the index all, prev_next, all_selected */
52
	private $show;
53
54
	/** @var int max page # to show */
55
	private $max_value;
56
57
	/** @var int */
58
	private $num_per_page;
59
60
	/** @var bool  */
61
	private $flexible_start;
62
63
	/** @var string  */
64
	private $base_url;
65
66
	/** @var string */
67
	private $base_link;
68
69
	/** @var bool If we have tried to navigate off the end */
70
	private $start_invalid;
71
72
	/** @var int */
73
	private $counter;
74
75
	/** @var mixed|string What we are after */
76
	private $pageindex;
77
78
	/** @var int the maximum number of pages in the index */
79
	private $maxPages;
80
81
	/**
82
	 * ConstructPageIndex constructor.
83
	 *
84
	 * @param string $base_url
85
	 * @param int $start
86
	 * @param int $max_value
87
	 * @param int $num_per_page
88
	 * @param bool $flexible_start
89
	 * @param array $show
90
	 */
91
	public function __construct($base_url, &$start, $max_value, $num_per_page, $flexible_start = false, $show = [])
92
	{
93
		$this->start = (int) $start;
94
		$this->show = array_merge(['prev_next' => true, 'all' => false], $show);
95
		$this->max_value = (int) $max_value;
96
		$this->num_per_page = (int) $num_per_page;
97
		$this->flexible_start = $flexible_start;
98
		$this->base_url = $base_url;
99
100
		// Need to pull in modsettings, db and user
101
		parent::__construct();
102
103
		// Do it!
104
		$this->createPageIndex();
105
		$start = $this->start;
106
	}
107
108
	/**
109
	 * Does what it says, creates the handy pageindex navigation bar
110
	 */
111
	public function createPageIndex(): void
112
	{
113
		global $context;
114
115
		$this->setStart();
116
		$context['current_page'] = $this->start / $this->num_per_page;
117
118
		$this->setBaseLink();
119
120
		if ($this->max_value <= $this->num_per_page)
121
		{
122
			$pageindex = $this->noLinks();
123
		}
124
		elseif (empty($this->_modSettings['compactTopicPagesEnable']))
125
		{
126
			$pageindex = $this->simpleLinks();
127
		}
128
		else
129
		{
130
			$pageindex = $this->compactLinks();
131
		}
132
133
		// Here it is, use getPageIndex to fetch it
134
		$this->pageindex = $this->showAll($pageindex);
135
	}
136
137
	/**
138
	 * Returns the completed page index
139
	 *
140
	 * @return string
141
	 */
142
	public function getPageIndex(): string
143
	{
144
		return $this->pageindex;
145
	}
146
147
	/**
148
	 * Validate and sets the page starting point
149
	 *
150
	 * @return int
151
	 */
152
	public function setStart(): int
153
	{
154
		// Save whether $start was less than 0 or not.
155
		$this->start_invalid = $this->start < 0;
156
157
		// Make sure $start is a proper variable - not less than 0.
158
		if ($this->start_invalid)
159
		{
160
			return $this->start = 0;
161
		}
162
163
		// Not greater than the upper bound.
164
		if ($this->start >= $this->max_value)
165
		{
166
			$upper = $this->max_value % $this->num_per_page === 0
167
				? $this->num_per_page
168
				: $this->max_value % $this->num_per_page;
169
170
			return $this->start = max(0, $this->max_value - $upper);
171
		}
172
173
		// And it has to be a multiple of $num_per_page!
174
		return $this->start = max(0, $this->start - ($this->start % $this->num_per_page));
175
	}
176
177
	/**
178
	 * Sets the base url that navigation will start from.
179
	 * Will replace {base_link} and {scripturl} as needed.
180
	 * Uses ['page_index_template']['base_link'] template
181
	 */
182
	private function setBaseLink(): void
183
	{
184
		global $scripturl, $settings;
185
186
		$base_link = str_replace('{base_link}', ($this->flexible_start
187
			? $this->base_url
188
			: strtr($this->base_url, ['%' => '%%']) . ';start=%1$d'), $settings['page_index_template']['base_link']);
189
190
		$this->base_link = str_replace('{scripturl}', $scripturl, $base_link);
191
	}
192
193
	/**
194
	 * When there is only one page, no need to show an index
195
	 *
196
	 * @return string
197
	 */
198
	private function noLinks(): string
199
	{
200
		global $settings;
201
202
		return $settings['page_index_template']['none'] ?? '<li class="hide"></li>';
203
	}
204
205
	/**
206
	 * Simple prev 1 2 3 4 next style links
207
	 *
208
	 * @return string
209
	 */
210
	private function simpleLinks(): string
211
	{
212
		$pageindex = $this->setLeftNavigation();
213
		$pageindex .= $this->setAll();
214
215
		return $pageindex . $this->setRightNavigation();
216
	}
217
218
	/**
219
	 * AKA previous button for simple navigation index
220
	 * uses ['page_index_template']['previous_page'] template
221
	 *
222
	 * @return string
223
	 */
224
	private function setLeftNavigation(): string
225
	{
226
		global $settings, $txt;
227
228
		// Previous page language substitution into the page index template
229
		$previous = str_replace('{prev_txt}', $txt['prev'], $settings['page_index_template']['previous_page']);
230
231
		return ($this->start === 0 || !$this->show['prev_next'])
232
			? ' '
233
			: sprintf($this->base_link, $this->start - $this->num_per_page, $previous);
234
	}
235
236
	/**
237
	 * AKA all the pages 1 2 3 4 5 for simple navigation
238
	 * Uses ['page_index_template']['current_page'] template
239
	 *
240
	 * @return string
241
	 */
242
	private function setAll(): string
243
	{
244
		global $settings;
245
246
		// Show all the pages.
247
		$display_page = 1;
248
		$pageindex = '';
249
		for ($counter = 0; $counter < $this->max_value; $counter += $this->num_per_page)
250
		{
251
			$pageindex .= $this->start === $counter && !$this->start_invalid && empty($this->show['all_selected'])
252
				? sprintf($settings['page_index_template']['current_page'], $display_page++)
253
				: sprintf($this->base_link, $counter, $display_page++);
254
		}
255
256
		$this->counter = $counter;
257
258
		return $pageindex;
259
	}
260
261
	/**
262
	 * AKA the next button for simple navigation.  Uses 'page_index_template']['next_page']
263
	 * template
264
	 *
265
	 * @return string
266
	 */
267
	private function setRightNavigation(): string
268
	{
269
		global $settings, $txt;
270
271
		$pageindex = '';
272
		$display_page = ($this->start + $this->num_per_page) > $this->max_value
273
			? $this->max_value
274
			: $this->start + $this->num_per_page;
275
276
		if ($this->start !== $this->counter - $this->max_value && !$this->start_invalid
277
			&& $this->show['prev_next'] && empty($this->show['all_selected']))
278
		{
279
			$next = str_replace('{next_txt}', $txt['next'], $settings['page_index_template']['next_page']);
280
281
			$pageindex .= $display_page > $this->counter - $this->num_per_page
282
				? ' '
283
				: sprintf($this->base_link, $display_page, $next);
284
		}
285
286
		return $pageindex;
287
	}
288
289
	/**
290
	 * Compact links, good for displaying many available pages bar
291
	 * prev page >1< ... 6 7 [8] 9 10 ... 15)
292
	 *
293
	 * @return string
294
	 */
295
	private function compactLinks(): string
296
	{
297
		$pageindex = '';
298
299
		// If they didn't enter an odd value, pretend they did.
300
		$PageContiguous = ($this->_modSettings['compactTopicPagesContiguous'] - ($this->_modSettings['compactTopicPagesContiguous'] % 2)) / 2;
301
302
		// Start with previous if there is one
303
		$pageindex .= $this->compactPreviousNavigation();
304
305
		// Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15)
306
		if ($this->start > $this->num_per_page * $PageContiguous)
307
		{
308
			$pageindex .= sprintf($this->base_link, 0, '1');
309
		}
310
311
		// Show the ... after the first page.  (prev page 1 >...< 6 7 [8] 9 10 ... 15 next page)
312
		if ($this->start > $this->num_per_page * ($PageContiguous + 1))
313
		{
314
			$pageindex .= $this->compactContinuation($PageContiguous, 'before');
315
		}
316
317
		// Show a few pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
318
		$pageindex .= $this->compactBeforeCurrent($PageContiguous);
319
320
		// Show the current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
321
		$pageindex .= $this->compactCurrent();
322
323
		// Show a few pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
324
		$pageindex .= $this->compactAfterCurrent($PageContiguous);
325
326
		// Show the '...' part near the end. (prev page 1 ... 6 7 [8] 9 10 >...< 15 next page)
327
		if ($this->start + $this->num_per_page * ($PageContiguous + 1) < $this->getMaxPages())
328
		{
329
			$pageindex .= $this->compactContinuation($PageContiguous, 'after');
330
		}
331
332
		// Show the last number in the list. (prev page 1 ... 6 7 [8] 9 10 ... >15<  next page)
333
		if ($this->start + $this->num_per_page * $PageContiguous < $this->getMaxPages())
334
		{
335
			$pageindex .= sprintf($this->base_link, $this->getMaxPages(), $this->getMaxPages() / $this->num_per_page + 1);
336
		}
337
338
		// Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
339
		$pageindex .= $this->compactNextNavigation();
340
341
		return $pageindex;
342
	}
343
344
	/**
345
	 * Shows the previous page link
346
	 * Uses ['page_index_template']['previous_page' template
347
	 *
348
	 * @return string
349
	 */
350
	private function compactPreviousNavigation(): string
351
	{
352
		global $settings, $txt;
353
354
		// Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page)
355
		if (!empty($this->start) && $this->show['prev_next'])
356
		{
357
			$previous = str_replace('{prev_txt}', $txt['prev'], $settings['page_index_template']['previous_page']);
358
359
			return sprintf($this->base_link, $this->start - $this->num_per_page, $previous);
360
		}
361
362
		return '';
363
	}
364
365
	/**
366
	 * Shows the ... continuation link, helper function for before or after
367
	 *
368
	 * @param int $PageContiguous
369
	 * @param string $position before or after
370
	 * @return string|string[]
371
	 */
372
	private function compactContinuation($PageContiguous, $position)
373
	{
374
		global $settings;
375
376
		if ($position === 'before')
377
		{
378
			$firstpage = $this->num_per_page;
379
			$lastpage = $this->start - $this->num_per_page * $PageContiguous;
380
		}
381
		else
382
		{
383
			$firstpage = $this->start + $this->num_per_page * ($PageContiguous + 1);
384
			$lastpage = $this->getMaxPages();
385
		}
386
387
		return str_replace(
388
			'{custom}',
389
			'data-baseurl="' . htmlspecialchars(JavaScriptEscape(
390
				strtr($this->flexible_start
391
					? $this->base_url
392
					: strtr($this->base_url, ['%' => '%%']) . ';start=%1$d', ['{scripturl}' => '']
393
				)
394
			), ENT_COMPAT, 'UTF-8') .
395
			'" data-perpage="' . $this->num_per_page .
396
			'" data-firstpage="' . $firstpage .
397
			'" data-lastpage="' . $lastpage . '"',
398
			$settings['page_index_template']['expand_pages']
399
		);
400
	}
401
402
	/**
403
	 * The maximum number of pages for this index
404
	 *
405
	 * @return int
406
	 */
407
	private function tmpMaxPages()
408
	{
409
		return (int) (($this->max_value - 1) / $this->num_per_page) * $this->num_per_page;
410
	}
411
412
	/**
413
	 * Simply returns the maxpages for the index
414
	 *
415
	 * @return int
416
	 */
417
	private function getMaxPages()
418
	{
419
		$this->maxPages = $this->maxPages ?? $this->tmpMaxPages();
420
421
		return $this->maxPages;
422
	}
423
424
	/**
425
	 * The numbered pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
426
	 *
427
	 * @param $PageContiguous
428
	 * @return string
429
	 */
430
	private function compactBeforeCurrent($PageContiguous): string
431
	{
432
		$pageindex = '';
433
		for ($nCont = $PageContiguous; $nCont >= 1; $nCont--)
434
		{
435
			if ($this->start >= $this->num_per_page * $nCont)
436
			{
437
				$tmpStart = $this->start - $this->num_per_page * $nCont;
438
				$pageindex .= sprintf($this->base_link, $tmpStart, $tmpStart / $this->num_per_page + 1);
439
			}
440
		}
441
442
		return $pageindex;
443
	}
444
445
	/**
446
	 * The current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
447
	 * Uses ['page_index_template']['current_page'] template
448
	 *
449
	 * @return string
450
	 */
451
	private function compactCurrent(): string
452
	{
453
		global $settings;
454
455
		if (!$this->start_invalid && empty($this->show['all_selected']))
456
		{
457
			return sprintf($settings['page_index_template']['current_page'], ($this->start / $this->num_per_page + 1));
458
		}
459
460
		return sprintf($this->base_link, $this->start, $this->start / $this->num_per_page + 1);
461
	}
462
463
	/**
464
	 * The pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
465
	 *
466
	 * @param $PageContiguous
467
	 * @return string
468
	 */
469
	private function compactAfterCurrent($PageContiguous): string
470
	{
471
		$pageindex = '';
472
		for ($nCont = 1; $nCont <= $PageContiguous; $nCont++)
473
		{
474
			if ($this->start + $this->num_per_page * $nCont <= $this->getMaxPages())
475
			{
476
				$tmpStart = $this->start + $this->num_per_page * $nCont;
477
				$pageindex .= sprintf($this->base_link, $tmpStart, $tmpStart / $this->num_per_page + 1);
478
			}
479
		}
480
481
		return $pageindex;
482
	}
483
484
	/**
485
	 * Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
486
	 * Uses ['page_index_template']['next_page'] template
487
	 *
488
	 * @return string
489
	 */
490
	private function compactNextNavigation(): string
491
	{
492
		global $settings, $txt;
493
494
		if ($this->start !== $this->getMaxPages() && $this->show['prev_next'] && empty($this->show['all_selected']))
495
		{
496
			$next = str_replace('{next_txt}', $txt['next'], $settings['page_index_template']['next_page']);
497
498
			return sprintf($this->base_link, $this->start + $this->num_per_page, $next);
499
		}
500
501
		return '';
502
	}
503
504
	/**
505
	 * The show-all button if requested/
506
	 * Uses 'page_index_template']['current_page'] template
507
	 *
508
	 * @param $pageindex
509
	 * @return mixed|string
510
	 */
511
	private function showAll($pageindex)
512
	{
513
		global $settings, $txt;
514
515
		// The "all" button
516
		if ($this->show['all'])
517
		{
518
			if (!empty($this->show['all_selected']))
519
			{
520
				$pageindex .= sprintf($settings['page_index_template']['current_page'], $txt['all']);
521
			}
522
			else
523
			{
524
				$all = str_replace('{all_txt}', $txt['all'], $settings['page_index_template']['all']);
525
				$pageindex .= sprintf(str_replace('%1$d', '%1$s', $this->base_link), '0;all', $all);
526
			}
527
		}
528
529
		return $pageindex;
530
	}
531
}
532