1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
<?php
declare(strict_types=1);
class Paginate
{
private $query;
private $currentPage;
private $perPage;
private $url;
private $totalRows = false;
/**
* @param string $query - The query that will return lines to show
* @param int $perPage - Number of items to show per page
* @param ?string $url - URL of current wegpage
*/
public function __construct(string $query, int $perPage, string $url = null)
{
$this->currentPage = (isset($_GET['page']) ? (int)$_GET['page'] : 0);
$this->perPage = $perPage;
if ($this->currentPage < 0) {
ErrorHandler::traceError('Current page < 0');
}
if ($this->perPage < 1) {
ErrorHandler::traceError('Per page < 1');
}
// Query
if (!preg_match('/\s*SELECT\s/i', $query)) {
ErrorHandler::traceError('Query has to start with SELECT!');
}
// XXX: MySQL only
if (preg_match('/^(mysql|mariadb)/i', CONFIG_SQL_DSN)) {
// Sanity: Check for LIMIT specification at the end
if (preg_match('/LIMIT\s+(\d+|\:\w+|\?)\s*,\s*(\d+|\:\w+|\?)(\s|;)*(\-\-.*)?$/is', $query)) {
ErrorHandler::traceError("You cannot pass a query containing a LIMIT to the Paginator class!");
}
// Sanity: no comment or semi-colon at end (sloppy, might lead to false negatives)
if (preg_match('/(\-\-|;)(\s|[^\'"`])*$/i', $query)) {
ErrorHandler::traceError("Your query must not end in a comment or semi-colon!");
}
// Don't use SQL_CALC_FOUND_ROWS as it leads to filesort frequently thus being slower than two queries
// See https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/
} else {
ErrorHandler::traceError('Unsupported database engine');
}
// Mangle URL
if ($url === null) {
$url = $_SERVER['REQUEST_URI'];
}
if (strpos($url, '?') === false) {
$url .= '?';
} else {
$url = preg_replace('/(\?|&)&*page=[^&]*(&+|$)/i', '$1', $url);
if (substr($url, -1) !== '&') $url .= '&';
}
//
$this->query = $query;
$this->url = $url;
}
/**
* Execute the query, returning the PDO query object
*/
public function exec(array $args = [])
{
$countQuery = preg_replace('/ORDER\s+BY\s.*?(\sASC|\sDESC|$)/is', '', $this->query);
$countQuery = preg_replace('/SELECT\s.*?\sFROM\s/is', 'SELECT Count(*) AS rowcount FROM ', $countQuery);
$countRes = Database::queryFirst($countQuery, $args);
$query = $this->query . ' LIMIT ' . ($this->currentPage * $this->perPage) . ', ' . $this->perPage;
$retval = Database::simpleQuery($query, $args);
$this->totalRows = (int)$countRes['rowcount'];
return $retval;
}
public function render(string $template, array $data): void
{
if ($this->totalRows == 0) {
// Shortcut for no content
Render::addTemplate($template, $data);
return;
}
// The real thing
$pages = array();
$pageCount = floor(($this->totalRows - 1) / $this->perPage) + 1;
$skip = false;
for ($i = 0; $i < $pageCount; ++$i) {
if (($i > 0 && $i < $this->currentPage - 3) || ($i > $this->currentPage + 3 && $i < $pageCount - 1)) {
if (!$skip) {
$skip = true;
$pages[] = array(
'text' => false,
'current' => false
);
}
continue;
}
$skip = false;
$pages[] = array(
'text' => $i + 1,
'page' => $i,
'current' => $i == $this->currentPage,
);
}
$pages = Render::parse('pagenav', array(
'url' => $this->url,
'pages' => $pages,
),'main');
$data['page'] = $this->currentPage;
$data['pagenav'] = $pages;
Render::addTemplate($template, $data);
}
}
|