Passed
Push — development ( e3a728...c7cb34 )
by Spuds
01:05 queued 20s
created

ConstructPageIndex   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 490
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 151
dl 0
loc 490
rs 4.5599
c 2
b 0
f 0
wmc 58

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 1
A getPageIndex() 0 3 1
A createPageIndex() 0 24 3
B setRightNavigation() 0 20 7
A compactBeforeCurrent() 0 13 3
A noLinks() 0 5 1
A compactPreviousNavigation() 0 13 3
A setBaseLink() 0 9 2
A setLeftNavigation() 0 10 3
A compactLinks() 0 47 5
A compactContinuation() 0 27 3
A tmpMaxPages() 0 3 1
A simpleLinks() 0 6 1
A compactCurrent() 0 10 3
A setAll() 0 17 5
A compactNextNavigation() 0 12 4
A compactAfterCurrent() 0 13 3
A getMaxPages() 0 5 1
A setStart() 0 29 5
A showAll() 0 19 3

How to fix   Complexity   

Complex Class

Complex classes like ConstructPageIndex often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ConstructPageIndex, and based on these observations, apply Extract Interface, too.

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 we have a valid number per page to avoid division by zero
158
		if ($this->num_per_page <= 0)
159
		{
160
			return $this->start = 0;
161
		}
162
163
		// Make sure $start is a proper variable - not less than 0.
164
		if ($this->start_invalid)
165
		{
166
			return $this->start = 0;
167
		}
168
169
		// Not greater than the upper bound.
170
		if ($this->start >= $this->max_value)
171
		{
172
			$upper = $this->max_value % $this->num_per_page === 0
173
				? $this->num_per_page
174
				: $this->max_value % $this->num_per_page;
175
176
			return $this->start = max(0, $this->max_value - $upper);
177
		}
178
179
		// And it has to be a multiple of $num_per_page!
180
		return $this->start = max(0, $this->start - ($this->start % $this->num_per_page));
181
	}
182
183
	/**
184
	 * Sets the base url that navigation will start from.
185
	 * Will replace {base_link} and {scripturl} as needed.
186
	 * Uses ['page_index_template']['base_link'] template
187
	 */
188
	private function setBaseLink(): void
189
	{
190
		global $scripturl, $settings;
191
192
		$base_link = str_replace('{base_link}', ($this->flexible_start
193
			? $this->base_url
194
			: strtr($this->base_url, ['%' => '%%']) . ';start=%1$d'), $settings['page_index_template']['base_link']);
195
196
		$this->base_link = str_replace('{scripturl}', $scripturl, $base_link);
197
	}
198
199
	/**
200
	 * When there is only one page, no need to show an index
201
	 *
202
	 * @return string
203
	 */
204
	private function noLinks(): string
205
	{
206
		global $settings;
207
208
		return $settings['page_index_template']['none'] ?? '<li class="hide"></li>';
209
	}
210
211
	/**
212
	 * Simple prev 1 2 3 4 next style links
213
	 *
214
	 * @return string
215
	 */
216
	private function simpleLinks(): string
217
	{
218
		$pageindex = $this->setLeftNavigation();
219
		$pageindex .= $this->setAll();
220
221
		return $pageindex . $this->setRightNavigation();
222
	}
223
224
	/**
225
	 * AKA previous button for simple navigation index
226
	 * uses ['page_index_template']['previous_page'] template
227
	 *
228
	 * @return string
229
	 */
230
	private function setLeftNavigation(): string
231
	{
232
		global $settings, $txt;
233
234
		// Previous page language substitution into the page index template
235
		$previous = str_replace('{prev_txt}', $txt['prev'], $settings['page_index_template']['previous_page']);
236
237
		return ($this->start === 0 || !$this->show['prev_next'])
238
			? ' '
239
			: sprintf($this->base_link, $this->start - $this->num_per_page, $previous);
240
	}
241
242
	/**
243
	 * AKA all the pages 1 2 3 4 5 for simple navigation
244
	 * Uses ['page_index_template']['current_page'] template
245
	 *
246
	 * @return string
247
	 */
248
	private function setAll(): string
249
	{
250
		global $settings;
251
252
		// Show all the pages.
253
		$display_page = 1;
254
		$pageindex = '';
255
		for ($counter = 0; $counter < $this->max_value; $counter += $this->num_per_page)
256
		{
257
			$pageindex .= $this->start === $counter && !$this->start_invalid && empty($this->show['all_selected'])
258
				? sprintf($settings['page_index_template']['current_page'], $display_page++)
259
				: sprintf($this->base_link, $counter, $display_page++);
260
		}
261
262
		$this->counter = $counter;
263
264
		return $pageindex;
265
	}
266
267
	/**
268
	 * AKA the next button for simple navigation.  Uses 'page_index_template']['next_page']
269
	 * template
270
	 *
271
	 * @return string
272
	 */
273
	private function setRightNavigation(): string
274
	{
275
		global $settings, $txt;
276
277
		$pageindex = '';
278
		$display_page = ($this->start + $this->num_per_page) > $this->max_value
279
			? $this->max_value
280
			: $this->start + $this->num_per_page;
281
282
		if ($this->start !== $this->counter - $this->max_value && !$this->start_invalid
283
			&& $this->show['prev_next'] && empty($this->show['all_selected']))
284
		{
285
			$next = str_replace('{next_txt}', $txt['next'], $settings['page_index_template']['next_page']);
286
287
			$pageindex .= $display_page > $this->counter - $this->num_per_page
288
				? ' '
289
				: sprintf($this->base_link, $display_page, $next);
290
		}
291
292
		return $pageindex;
293
	}
294
295
	/**
296
	 * Compact links, good for displaying many available pages bar
297
	 * prev page >1< ... 6 7 [8] 9 10 ... 15)
298
	 *
299
	 * @return string
300
	 */
301
	private function compactLinks(): string
302
	{
303
		$pageindex = '';
304
305
		// If they didn't enter an odd value, pretend they did.
306
		$PageContiguous = ($this->_modSettings['compactTopicPagesContiguous'] - ($this->_modSettings['compactTopicPagesContiguous'] % 2)) / 2;
307
308
		// Start with previous if there is one
309
		$pageindex .= $this->compactPreviousNavigation();
310
311
		// Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15)
312
		if ($this->start > $this->num_per_page * $PageContiguous)
313
		{
314
			$pageindex .= sprintf($this->base_link, 0, '1');
315
		}
316
317
		// Show the ... after the first page.  (prev page 1 >...< 6 7 [8] 9 10 ... 15 next page)
318
		if ($this->start > $this->num_per_page * ($PageContiguous + 1))
319
		{
320
			$pageindex .= $this->compactContinuation($PageContiguous, 'before');
321
		}
322
323
		// Show a few pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
324
		$pageindex .= $this->compactBeforeCurrent($PageContiguous);
325
326
		// Show the current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
327
		$pageindex .= $this->compactCurrent();
328
329
		// Show a few pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
330
		$pageindex .= $this->compactAfterCurrent($PageContiguous);
331
332
		// Show the '...' part near the end. (prev page 1 ... 6 7 [8] 9 10 >...< 15 next page)
333
		if ($this->start + $this->num_per_page * ($PageContiguous + 1) < $this->getMaxPages())
334
		{
335
			$pageindex .= $this->compactContinuation($PageContiguous, 'after');
336
		}
337
338
		// Show the last number in the list. (prev page 1 ... 6 7 [8] 9 10 ... >15<  next page)
339
		if ($this->start + $this->num_per_page * $PageContiguous < $this->getMaxPages())
340
		{
341
			$pageindex .= sprintf($this->base_link, $this->getMaxPages(), $this->getMaxPages() / $this->num_per_page + 1);
342
		}
343
344
		// Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
345
		$pageindex .= $this->compactNextNavigation();
346
347
		return $pageindex;
348
	}
349
350
	/**
351
	 * Shows the previous page link
352
	 * Uses ['page_index_template']['previous_page' template
353
	 *
354
	 * @return string
355
	 */
356
	private function compactPreviousNavigation(): string
357
	{
358
		global $settings, $txt;
359
360
		// Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page)
361
		if (!empty($this->start) && $this->show['prev_next'])
362
		{
363
			$previous = str_replace('{prev_txt}', $txt['prev'], $settings['page_index_template']['previous_page']);
364
365
			return sprintf($this->base_link, $this->start - $this->num_per_page, $previous);
366
		}
367
368
		return '';
369
	}
370
371
	/**
372
	 * Shows the ... continuation link, helper function for before or after
373
	 *
374
	 * @param int $PageContiguous
375
	 * @param string $position before or after
376
	 * @return string|string[]
377
	 */
378
	private function compactContinuation($PageContiguous, $position)
379
	{
380
		global $settings;
381
382
		if ($position === 'before')
383
		{
384
			$firstpage = $this->num_per_page;
385
			$lastpage = $this->start - $this->num_per_page * $PageContiguous;
386
		}
387
		else
388
		{
389
			$firstpage = $this->start + $this->num_per_page * ($PageContiguous + 1);
390
			$lastpage = $this->getMaxPages();
391
		}
392
393
		return str_replace(
394
			'{custom}',
395
			'data-baseurl="' . htmlspecialchars(JavaScriptEscape(
396
				strtr($this->flexible_start
397
					? $this->base_url
398
					: strtr($this->base_url, ['%' => '%%']) . ';start=%1$d', ['{scripturl}' => '']
399
				)
400
			), ENT_COMPAT, 'UTF-8') .
401
			'" data-perpage="' . $this->num_per_page .
402
			'" data-firstpage="' . $firstpage .
403
			'" data-lastpage="' . $lastpage . '"',
404
			$settings['page_index_template']['expand_pages']
405
		);
406
	}
407
408
	/**
409
	 * The maximum number of pages for this index
410
	 *
411
	 * @return int
412
	 */
413
	private function tmpMaxPages()
414
	{
415
		return (int) (($this->max_value - 1) / $this->num_per_page) * $this->num_per_page;
416
	}
417
418
	/**
419
	 * Simply returns the maxpages for the index
420
	 *
421
	 * @return int
422
	 */
423
	private function getMaxPages()
424
	{
425
		$this->maxPages = $this->maxPages ?? $this->tmpMaxPages();
426
427
		return $this->maxPages;
428
	}
429
430
	/**
431
	 * The numbered pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
432
	 *
433
	 * @param $PageContiguous
434
	 * @return string
435
	 */
436
	private function compactBeforeCurrent($PageContiguous): string
437
	{
438
		$pageindex = '';
439
		for ($nCont = $PageContiguous; $nCont >= 1; $nCont--)
440
		{
441
			if ($this->start >= $this->num_per_page * $nCont)
442
			{
443
				$tmpStart = $this->start - $this->num_per_page * $nCont;
444
				$pageindex .= sprintf($this->base_link, $tmpStart, $tmpStart / $this->num_per_page + 1);
445
			}
446
		}
447
448
		return $pageindex;
449
	}
450
451
	/**
452
	 * The current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
453
	 * Uses ['page_index_template']['current_page'] template
454
	 *
455
	 * @return string
456
	 */
457
	private function compactCurrent(): string
458
	{
459
		global $settings;
460
461
		if (!$this->start_invalid && empty($this->show['all_selected']))
462
		{
463
			return sprintf($settings['page_index_template']['current_page'], ($this->start / $this->num_per_page + 1));
464
		}
465
466
		return sprintf($this->base_link, $this->start, $this->start / $this->num_per_page + 1);
467
	}
468
469
	/**
470
	 * The pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
471
	 *
472
	 * @param $PageContiguous
473
	 * @return string
474
	 */
475
	private function compactAfterCurrent($PageContiguous): string
476
	{
477
		$pageindex = '';
478
		for ($nCont = 1; $nCont <= $PageContiguous; $nCont++)
479
		{
480
			if ($this->start + $this->num_per_page * $nCont <= $this->getMaxPages())
481
			{
482
				$tmpStart = $this->start + $this->num_per_page * $nCont;
483
				$pageindex .= sprintf($this->base_link, $tmpStart, $tmpStart / $this->num_per_page + 1);
484
			}
485
		}
486
487
		return $pageindex;
488
	}
489
490
	/**
491
	 * Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
492
	 * Uses ['page_index_template']['next_page'] template
493
	 *
494
	 * @return string
495
	 */
496
	private function compactNextNavigation(): string
497
	{
498
		global $settings, $txt;
499
500
		if ($this->start !== $this->getMaxPages() && $this->show['prev_next'] && empty($this->show['all_selected']))
501
		{
502
			$next = str_replace('{next_txt}', $txt['next'], $settings['page_index_template']['next_page']);
503
504
			return sprintf($this->base_link, $this->start + $this->num_per_page, $next);
505
		}
506
507
		return '';
508
	}
509
510
	/**
511
	 * The show-all button if requested/
512
	 * Uses 'page_index_template']['current_page'] template
513
	 *
514
	 * @param $pageindex
515
	 * @return mixed|string
516
	 */
517
	private function showAll($pageindex)
518
	{
519
		global $settings, $txt;
520
521
		// The "all" button
522
		if ($this->show['all'])
523
		{
524
			if (!empty($this->show['all_selected']))
525
			{
526
				$pageindex .= sprintf($settings['page_index_template']['current_page'], $txt['all']);
527
			}
528
			else
529
			{
530
				$all = str_replace('{all_txt}', $txt['all'], $settings['page_index_template']['all']);
531
				$pageindex .= sprintf(str_replace('%1$d', '%1$s', $this->base_link), '0;all', $all);
532
			}
533
		}
534
535
		return $pageindex;
536
	}
537
}
538