1<?php 2 3namespace JasonGrimes; 4 5class Paginator 6{ 7 const NUM_PLACEHOLDER = '(:num)'; 8 9 protected $totalItems; 10 protected $numPages; 11 protected $itemsPerPage; 12 protected $currentPage; 13 protected $urlPattern; 14 protected $maxPagesToShow = 10; 15 protected $previousText = 'Previous'; 16 protected $nextText = 'Next'; 17 18 /** 19 * @param int $totalItems The total number of items. 20 * @param int $itemsPerPage The number of items per page. 21 * @param int $currentPage The current page number. 22 * @param string $urlPattern A URL for each page, with (:num) as a placeholder for the page number. Ex. '/foo/page/(:num)' 23 */ 24 public function __construct($totalItems, $itemsPerPage, $currentPage, $urlPattern = '') 25 { 26 $this->totalItems = $totalItems; 27 $this->itemsPerPage = $itemsPerPage; 28 $this->currentPage = $currentPage; 29 $this->urlPattern = $urlPattern; 30 31 $this->updateNumPages(); 32 } 33 34 protected function updateNumPages() 35 { 36 $this->numPages = ($this->itemsPerPage == 0 ? 0 : (int) ceil($this->totalItems/$this->itemsPerPage)); 37 } 38 39 /** 40 * @param int $maxPagesToShow 41 * @throws \InvalidArgumentException if $maxPagesToShow is less than 3. 42 */ 43 public function setMaxPagesToShow($maxPagesToShow) 44 { 45 if ($maxPagesToShow < 3) { 46 throw new \InvalidArgumentException('maxPagesToShow cannot be less than 3.'); 47 } 48 $this->maxPagesToShow = $maxPagesToShow; 49 } 50 51 /** 52 * @return int 53 */ 54 public function getMaxPagesToShow() 55 { 56 return $this->maxPagesToShow; 57 } 58 59 /** 60 * @param int $currentPage 61 */ 62 public function setCurrentPage($currentPage) 63 { 64 $this->currentPage = $currentPage; 65 } 66 67 /** 68 * @return int 69 */ 70 public function getCurrentPage() 71 { 72 return $this->currentPage; 73 } 74 75 /** 76 * @param int $itemsPerPage 77 */ 78 public function setItemsPerPage($itemsPerPage) 79 { 80 $this->itemsPerPage = $itemsPerPage; 81 $this->updateNumPages(); 82 } 83 84 /** 85 * @return int 86 */ 87 public function getItemsPerPage() 88 { 89 return $this->itemsPerPage; 90 } 91 92 /** 93 * @param int $totalItems 94 */ 95 public function setTotalItems($totalItems) 96 { 97 $this->totalItems = $totalItems; 98 $this->updateNumPages(); 99 } 100 101 /** 102 * @return int 103 */ 104 public function getTotalItems() 105 { 106 return $this->totalItems; 107 } 108 109 /** 110 * @return int 111 */ 112 public function getNumPages() 113 { 114 return $this->numPages; 115 } 116 117 /** 118 * @param string $urlPattern 119 */ 120 public function setUrlPattern($urlPattern) 121 { 122 $this->urlPattern = $urlPattern; 123 } 124 125 /** 126 * @return string 127 */ 128 public function getUrlPattern() 129 { 130 return $this->urlPattern; 131 } 132 133 /** 134 * @param int $pageNum 135 * @return string 136 */ 137 public function getPageUrl($pageNum) 138 { 139 return str_replace(self::NUM_PLACEHOLDER, $pageNum, $this->urlPattern); 140 } 141 142 public function getNextPage() 143 { 144 if ($this->currentPage < $this->numPages) { 145 return $this->currentPage + 1; 146 } 147 148 return null; 149 } 150 151 public function getPrevPage() 152 { 153 if ($this->currentPage > 1) { 154 return $this->currentPage - 1; 155 } 156 157 return null; 158 } 159 160 public function getNextUrl() 161 { 162 if (!$this->getNextPage()) { 163 return null; 164 } 165 166 return $this->getPageUrl($this->getNextPage()); 167 } 168 169 /** 170 * @return string|null 171 */ 172 public function getPrevUrl() 173 { 174 if (!$this->getPrevPage()) { 175 return null; 176 } 177 178 return $this->getPageUrl($this->getPrevPage()); 179 } 180 181 /** 182 * Get an array of paginated page data. 183 * 184 * Example: 185 * array( 186 * array ('num' => 1, 'url' => '/example/page/1', 'isCurrent' => false), 187 * array ('num' => '...', 'url' => NULL, 'isCurrent' => false), 188 * array ('num' => 3, 'url' => '/example/page/3', 'isCurrent' => false), 189 * array ('num' => 4, 'url' => '/example/page/4', 'isCurrent' => true ), 190 * array ('num' => 5, 'url' => '/example/page/5', 'isCurrent' => false), 191 * array ('num' => '...', 'url' => NULL, 'isCurrent' => false), 192 * array ('num' => 10, 'url' => '/example/page/10', 'isCurrent' => false), 193 * ) 194 * 195 * @return array 196 */ 197 public function getPages() 198 { 199 $pages = array(); 200 201 if ($this->numPages <= 1) { 202 return array(); 203 } 204 205 if ($this->numPages <= $this->maxPagesToShow) { 206 for ($i = 1; $i <= $this->numPages; $i++) { 207 $pages[] = $this->createPage($i, $i == $this->currentPage); 208 } 209 } else { 210 211 // Determine the sliding range, centered around the current page. 212 $numAdjacents = (int) floor(($this->maxPagesToShow - 3) / 2); 213 214 if ($this->currentPage + $numAdjacents > $this->numPages) { 215 $slidingStart = $this->numPages - $this->maxPagesToShow + 2; 216 } else { 217 $slidingStart = $this->currentPage - $numAdjacents; 218 } 219 if ($slidingStart < 2) $slidingStart = 2; 220 221 $slidingEnd = $slidingStart + $this->maxPagesToShow - 3; 222 if ($slidingEnd >= $this->numPages) $slidingEnd = $this->numPages - 1; 223 224 // Build the list of pages. 225 $pages[] = $this->createPage(1, $this->currentPage == 1); 226 if ($slidingStart > 2) { 227 $pages[] = $this->createPageEllipsis(); 228 } 229 for ($i = $slidingStart; $i <= $slidingEnd; $i++) { 230 $pages[] = $this->createPage($i, $i == $this->currentPage); 231 } 232 if ($slidingEnd < $this->numPages - 1) { 233 $pages[] = $this->createPageEllipsis(); 234 } 235 $pages[] = $this->createPage($this->numPages, $this->currentPage == $this->numPages); 236 } 237 238 239 return $pages; 240 } 241 242 243 /** 244 * Create a page data structure. 245 * 246 * @param int $pageNum 247 * @param bool $isCurrent 248 * @return Array 249 */ 250 protected function createPage($pageNum, $isCurrent = false) 251 { 252 return array( 253 'num' => $pageNum, 254 'url' => $this->getPageUrl($pageNum), 255 'isCurrent' => $isCurrent, 256 ); 257 } 258 259 /** 260 * @return array 261 */ 262 protected function createPageEllipsis() 263 { 264 return array( 265 'num' => '...', 266 'url' => null, 267 'isCurrent' => false, 268 ); 269 } 270 271 /** 272 * Render an HTML pagination control. 273 * 274 * @return string 275 */ 276 public function toHtml() 277 { 278 if ($this->numPages <= 1) { 279 return ''; 280 } 281 282 $html = '<ul class="pagination">'; 283 if ($this->getPrevUrl()) { 284 $html .= '<li><a href="' . $this->getPrevUrl() . '">« '. $this->previousText .'</a></li>'; 285 } 286 287 foreach ($this->getPages() as $page) { 288 if ($page['url']) { 289 $html .= '<li' . ($page['isCurrent'] ? ' class="active"' : '') . '><a href="' . $page['url'] . '">' . $page['num'] . '</a></li>'; 290 } else { 291 $html .= '<li class="disabled"><span>' . $page['num'] . '</span></li>'; 292 } 293 } 294 295 if ($this->getNextUrl()) { 296 $html .= '<li><a href="' . $this->getNextUrl() . '">'. $this->nextText .' »</a></li>'; 297 } 298 $html .= '</ul>'; 299 300 return $html; 301 } 302 303 public function __toString() 304 { 305 return $this->toHtml(); 306 } 307 308 public function getCurrentPageFirstItem() 309 { 310 $first = ($this->currentPage - 1) * $this->itemsPerPage + 1; 311 312 if ($first > $this->totalItems) { 313 return null; 314 } 315 316 return $first; 317 } 318 319 public function getCurrentPageLastItem() 320 { 321 $first = $this->getCurrentPageFirstItem(); 322 if ($first === null) { 323 return null; 324 } 325 326 $last = $first + $this->itemsPerPage - 1; 327 if ($last > $this->totalItems) { 328 return $this->totalItems; 329 } 330 331 return $last; 332 } 333 334 public function setPreviousText($text) 335 { 336 $this->previousText = $text; 337 return $this; 338 } 339 340 public function setNextText($text) 341 { 342 $this->nextText = $text; 343 return $this; 344 } 345}