diff --git a/src/Content/Pager.php b/src/Content/Pager.php new file mode 100644 index 000000000..185c5506d --- /dev/null +++ b/src/Content/Pager.php @@ -0,0 +1,295 @@ + + */ +class Pager +{ + /** + * @var integer + */ + private $page = 1; + /** + * @var integer + */ + private $itemsPerPage = 50; + /** + * @var integer + */ + private $itemCount = 0; + + /** + * @var string + */ + private $baseQueryString = ''; + + /** + * Instantiates a new Pager with the base parameters. + * + * Guesses the page number from the GET parameter 'page'. + * + * @param string $queryString The query string of the current page + * @param integer $itemCount The total item count (for the full mode) or null (for the minimal mode) + * @param integer $itemsPerPage An optional number of items per page to override the default value + */ + public function __construct($queryString, $itemCount = null, $itemsPerPage = 50) + { + $this->setQueryString($queryString); + $this->setItemsPerPage($itemsPerPage); + $this->setItemCount(defaults($itemCount, $this->getItemsPerPage())); + $this->setPage(defaults($_GET, 'page', 1)); + } + + /** + * Returns the start offset for a LIMIT clause. Starts at 0. + * + * @return integer + */ + public function getStart() + { + return max(0, ($this->page * $this->itemsPerPage) - $this->itemsPerPage); + } + + /** + * Returns the number of items per page + * + * @return integer + */ + public function getItemsPerPage() + { + return $this->itemsPerPage; + } + + /** + * Returns the current page number + * + * @return type + */ + public function getPage() + { + return $this->page; + } + + /** + * Returns the base query string. + * + * Warning: this isn't the same value as passed to the constructor. + * See setQueryString for the inventory of transformations + * + * @see setBaseQuery() + * @return string + */ + public function getBaseQueryString() + { + return $this->baseQueryString; + } + + /** + * Sets the number of items per page, 1 minimum. + * + * @param integer $itemsPerPage + */ + public function setItemsPerPage($itemsPerPage) + { + $this->itemsPerPage = max(1, intval($itemsPerPage)); + } + + /** + * Sets the current page number. Starts at 1. + * + * @param integer $page + */ + public function setPage($page) + { + $this->page = max(1, intval($page)); + } + + /** + * Sets the item count, 0 minimum. + * + * @param integer $itemCount + */ + public function setItemCount($itemCount) + { + $this->itemCount = max(0, intval($itemCount)); + } + + /** + * Sets the base query string from a full query string. + * + * Strips the 'page' parameter, and remove the 'q=' string for some reason. + * + * @param string $queryString + */ + public function setQueryString($queryString) + { + $stripped = preg_replace('/([&?]page=[0-9]*)/', '', $queryString); + + $stripped = str_replace('q=', '', $stripped); + $stripped = trim($stripped, '/'); + + $this->baseQueryString = $stripped; + } + + /** + * Ensures the provided URI has its query string punctuation in order. + * + * @param string $uri + * @return string + */ + private function ensureQueryParameter($uri) + { + if (strpos($uri, '?') === false && ($pos = strpos($uri, '&')) !== false) { + $uri = substr($uri, 0, $pos) . '?' . substr($uri, $pos + 1); + } + + return $uri; + } + + /** + * @brief Minimal pager (newer/older) + * + * This mode is intended for reverse chronological pages and presents only two links, newer (previous) and older (next). + * The itemCount is the number of displayed items. If no items are displayed, the older button is disabled. + * + * Example usage: + * + * $pager = new Pager($a->query_string); + * + * $params = ['order' => ['sort_field' => true], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; + * $items = DBA::toArray(DBA::select($table, $fields, $condition, $params)); + * + * $html = $pager->renderMinimal(count($items)); + * + * @param integer $itemCount The number of displayed items on the page + * @return string HTML string of the pager + */ + public function renderMinimal($itemCount) + { + $this->setItemCount($itemCount); + + $data = [ + 'class' => 'pager', + 'prev' => [ + 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)), + 'text' => L10n::t('newer'), + 'class' => 'previous' . ($this->getPage() == 1 ? ' disabled' : '') + ], + 'next' => [ + 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)), + 'text' => L10n::t('older'), + 'class' => 'next' . ($this->itemCount <= 0 ? ' disabled' : '') + ] + ]; + + $tpl = get_markup_template('paginate.tpl'); + return replace_macros($tpl, ['pager' => $data]); + } + + /** + * @brief Full pager (first / prev / 1 / 2 / ... / 14 / 15 / next / last) + * + * This mode presents page numbers as well as first, previous, next and last links. + * The itemCount is the total number of items including those not displayed. + * + * Example usage: + * + * $total = DBA::count($table, $condition); + * + * $pager = new Pager($a->query_string, $total); + * + * $params = ['limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; + * $items = DBA::toArray(DBA::select($table, $fields, $condition, $params)); + * + * $html = $pager->renderFull(); + * + * @return string HTML string of the pager + */ + public function renderFull() + { + $data = []; + + $data['class'] = 'pagination'; + if ($this->itemCount > $this->getItemsPerPage()) { + $data['first'] = [ + 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=1'), + 'text' => L10n::t('first'), + 'class' => $this->getPage() == 1 ? 'disabled' : '' + ]; + $data['prev'] = [ + 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() - 1)), + 'text' => L10n::t('prev'), + 'class' => $this->getPage() == 1 ? 'disabled' : '' + ]; + + $numpages = $this->itemCount / $this->getItemsPerPage(); + + $numstart = 1; + $numstop = $numpages; + + // Limit the number of displayed page number buttons. + if ($numpages > 8) { + $numstart = (($this->getPage() > 4) ? ($this->getPage() - 4) : 1); + $numstop = (($this->getPage() > ($numpages - 7)) ? $numpages : ($numstart + 8)); + } + + $pages = []; + + for ($i = $numstart; $i <= $numstop; $i++) { + if ($i == $this->getPage()) { + $pages[$i] = [ + 'url' => '#', + 'text' => $i, + 'class' => 'current active' + ]; + } else { + $pages[$i] = [ + 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $i), + 'text' => $i, + 'class' => 'n' + ]; + } + } + + if (($this->itemCount % $this->getItemsPerPage()) != 0) { + if ($i == $this->getPage()) { + $pages[$i] = [ + 'url' => '#', + 'text' => $i, + 'class' => 'current active' + ]; + } else { + $pages[$i] = [ + 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $i), + 'text' => $i, + 'class' => 'n' + ]; + } + } + + $data['pages'] = $pages; + + $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages); + + $data['next'] = [ + 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)), + 'text' => L10n::t('next'), + 'class' => $this->getPage() == $lastpage ? 'disabled' : '' + ]; + $data['last'] = [ + 'url' => $this->ensureQueryParameter($this->baseQueryString . '&page=' . $lastpage), + 'text' => L10n::t('last'), + 'class' => $this->getPage() == $lastpage ? 'disabled' : '' + ]; + } + + $tpl = get_markup_template('paginate.tpl'); + return replace_macros($tpl, ['pager' => $data]); + } +}