PageRenderTime 21ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/PieCrust/Page/Paginator.php

https://bitbucket.org/ndj/piecrust
PHP | 350 lines | 230 code | 25 blank | 95 comment | 13 complexity | 09794c19b6b696c5bdf4ef21e5f4c25a MD5 | raw file
  1. <?php
  2. namespace PieCrust\Page;
  3. use PieCrust\IPage;
  4. use PieCrust\PieCrustDefaults;
  5. use PieCrust\PieCrustException;
  6. use PieCrust\Page\Filtering\PaginationFilter;
  7. use PieCrust\Page\Iteration\PageIterator;
  8. use PieCrust\Util\PageHelper;
  9. use PieCrust\Util\PieCrustHelper;
  10. /**
  11. * The pagination manager for a page split into sub-pages.
  12. *
  13. * Pages that display a large number of posts may be split into
  14. * several sub-pages. The Paginator class handles figuring out what
  15. * posts to include for the current page number.
  16. *
  17. */
  18. class Paginator
  19. {
  20. protected $page;
  21. protected $postsIterator;
  22. /**
  23. * Creates a new Paginator instance.
  24. *
  25. * @ignore
  26. */
  27. public function __construct(IPage $page)
  28. {
  29. $this->page = $page;
  30. $this->postsIterator = null;
  31. }
  32. // {{{ Template members
  33. /**
  34. * Gets the posts for this page.
  35. *
  36. * @noCall
  37. * @documentation The list of posts for this page.
  38. */
  39. public function posts()
  40. {
  41. $this->ensurePaginationData();
  42. return $this->postsIterator;
  43. }
  44. /**
  45. * Gets the maximum number of posts to be displayed on the page.
  46. */
  47. public function posts_per_page()
  48. {
  49. $blogKey = $this->page->getConfig()->getValue('blog');
  50. return PageHelper::getConfigValue($this->page, 'posts_per_page', $blogKey);
  51. }
  52. /**
  53. * Gets the actual number of posts on the page.
  54. */
  55. public function posts_this_page()
  56. {
  57. $this->ensurePaginationData();
  58. return $this->postsIterator->count();
  59. }
  60. /**
  61. * Gets the previous page's page number.
  62. */
  63. public function prev_page_number()
  64. {
  65. return ($this->page->getPageNumber() > 1) ? $this->page->getPageNumber() - 1 : null;
  66. }
  67. /**
  68. * Gets this page's page number.
  69. */
  70. public function this_page_number()
  71. {
  72. return $this->page->getPageNumber();
  73. }
  74. /**
  75. * Gets the next page's page number.
  76. */
  77. public function next_page_number()
  78. {
  79. if (PageHelper::isPost($this->page) or
  80. $this->page->getConfig()->getValue('single_page'))
  81. return null;
  82. $this->ensurePaginationData();
  83. if ($this->postsIterator->hasMorePosts())
  84. {
  85. return $this->page->getPageNumber() + 1;
  86. }
  87. return null;
  88. }
  89. /**
  90. * Gets the previous page's URI.
  91. *
  92. * This method is meant to be called from the layouts via the template engine.
  93. */
  94. public function prev_page()
  95. {
  96. $previousPageIndex = $this->prev_page_number();
  97. if ($previousPageIndex != null)
  98. {
  99. return $this->getSubPageUri($previousPageIndex);
  100. }
  101. return null;
  102. }
  103. /**
  104. * Gets this page's URI.
  105. *
  106. * This method is meant to be called from the layouts via the template engine.
  107. */
  108. public function this_page()
  109. {
  110. return $this->getSubPageUri($this->page->getPageNumber());
  111. }
  112. /**
  113. * Gets the next page's URI.
  114. *
  115. * This method is meant to be called from the layouts via the template engine.
  116. */
  117. public function next_page()
  118. {
  119. $nextPageIndex = $this->next_page_number();
  120. if ($nextPageIndex != null)
  121. {
  122. return $this->getSubPageUri($nextPageIndex);
  123. }
  124. return null;
  125. }
  126. /**
  127. * Gets the total number of posts.
  128. *
  129. * This method is meant to be called from the layouts via the template engine.
  130. */
  131. public function total_post_count()
  132. {
  133. $this->ensurePaginationData();
  134. return $this->postsIterator->count();
  135. }
  136. /**
  137. * Gets the total number of pages.
  138. *
  139. * This method is meant to be called from the layouts via the template engine.
  140. */
  141. public function total_page_count()
  142. {
  143. if (PageHelper::isPost($this->page) or
  144. $this->page->getConfig()->getValue('single_page'))
  145. return 1;
  146. $totalPostCount = $this->total_post_count();
  147. $postsPerPage = $this->posts_per_page();
  148. if (is_int($postsPerPage) && $postsPerPage > 0)
  149. return ceil($totalPostCount / $postsPerPage);
  150. return $totalPostCount;
  151. }
  152. /**
  153. * Gets all the page numbers.
  154. *
  155. * This method is meant to be called from the layouts via the template engine.
  156. */
  157. public function all_page_numbers($beforeEllipsis = false, $afterEllipsis = false)
  158. {
  159. if (!$afterEllipsis)
  160. $afterEllipsis = $beforeEllipsis;
  161. $totalPageCount = $this->total_page_count();
  162. if (!$beforeEllipsis and !$afterEllipsis)
  163. return range(1, $totalPageCount);
  164. elseif ($totalPageCount <= ($beforeEllipsis + $afterEllipsis))
  165. return range(1, $totalPageCount);
  166. else
  167. return array_merge(
  168. range(1, $beforeEllipsis),
  169. array(false),
  170. range($totalPageCount - $afterEllipsis + 1, $totalPageCount)
  171. );
  172. }
  173. /**
  174. * Get the link to a given page.
  175. *
  176. * This method is meant to be called from the layouts via the template engine.
  177. */
  178. public function page($index)
  179. {
  180. return $this->getSubPageUri($index);
  181. }
  182. /**
  183. * Gets the post coming after the current page,
  184. * if it is a post, and it's not the last one.
  185. *
  186. * This method is meant to be called from the layouts via the template engine.
  187. */
  188. public function next_post()
  189. {
  190. $this->ensurePaginationData();
  191. return $this->postsIterator->getNextPost();
  192. }
  193. /**
  194. * Gets the post coming before the current page,
  195. * if it is a post, and it's not the first one.
  196. *
  197. * This method is meant to be called from the layouts via the template engine.
  198. */
  199. public function prev_post()
  200. {
  201. $this->ensurePaginationData();
  202. return $this->postsIterator->getPreviousPost();
  203. }
  204. // }}}
  205. // {{{ Pagination manipulation
  206. protected $dataSource;
  207. /**
  208. * Gets the list of posts to use for the pagination.
  209. * If `null`, it will use all of the posts in the website.
  210. *
  211. * @ignore
  212. */
  213. public function getPaginationDataSource()
  214. {
  215. return $this->dataSource;
  216. }
  217. /**
  218. * Sets the list of posts to use for the pagination.
  219. *
  220. * @ignore
  221. */
  222. public function setPaginationDataSource(array $posts)
  223. {
  224. if ($this->postsIterator != null)
  225. throw new PieCrustException("Can't set the pagination data source after the pagination data has been loaded.");
  226. $this->dataSource = $posts;
  227. }
  228. /**
  229. * Resets the pagination data, as if it had never been accessed.
  230. *
  231. * @ignore
  232. */
  233. public function resetPaginationData()
  234. {
  235. $this->postsIterator = null;
  236. }
  237. /**
  238. * Gets whether the pagination data was requested by the page.
  239. *
  240. * @ignore
  241. */
  242. public function wasPaginationDataAccessed()
  243. {
  244. return ($this->postsIterator != null);
  245. }
  246. /**
  247. * Gets whether the current page has more pages to show.
  248. *
  249. * @ignore
  250. */
  251. public function hasMorePages()
  252. {
  253. return ($this->next_page() != null);
  254. }
  255. // }}}
  256. protected function ensurePaginationData()
  257. {
  258. if ($this->postsIterator != null)
  259. return;
  260. // Get the post infos.
  261. $posts = $this->dataSource;
  262. $blogKey = $this->page->getConfig()->getValue('blog');
  263. if ($posts === null)
  264. {
  265. $posts = PageHelper::getPosts($this->page->getApp(), $blogKey);
  266. }
  267. // Create the pagination iterator.
  268. $this->postsIterator = new PageIterator($this->page->getApp(), $blogKey, $posts);
  269. // Set our current page.
  270. $this->postsIterator->setCurrentPage($this->page);
  271. // Add the filters for the current page.
  272. $postsFilter = $this->getPaginationFilter();
  273. if ($postsFilter->hasClauses())
  274. $this->postsIterator->setFilter($postsFilter);
  275. // If the `posts_per_page` setting is valid, paginate accordingly.
  276. $postsPerPage = $this->posts_per_page();
  277. if (is_int($postsPerPage) && $postsPerPage > 0)
  278. {
  279. // Limit to posts that should be on this page.
  280. $offset = ($this->page->getPageNumber() - 1) * $postsPerPage;
  281. $this->postsIterator->slice($offset, $postsPerPage);
  282. }
  283. $this->postsIterator->setLocked();
  284. }
  285. protected function getPaginationFilter()
  286. {
  287. $filter = new PaginationFilter();
  288. $filterInfo = $this->page->getConfig()->getValue('posts_filters');
  289. if ($filterInfo == 'none' or $filterInfo == 'nil' or $filterInfo == '')
  290. $filterInfo = null;
  291. if (PageHelper::isTag($this->page) or PageHelper::isCategory($this->page))
  292. {
  293. // If the current page is a tag/category page, add filtering
  294. // for that.
  295. $filter->addPageClauses($this->page, $filterInfo);
  296. }
  297. else if ($filterInfo != null)
  298. {
  299. // Add custom filtering clauses specified by the user in the
  300. // page configuration header.
  301. $filter->addClauses($filterInfo);
  302. }
  303. return $filter;
  304. }
  305. public function getSubPageUri($index)
  306. {
  307. $uri = $this->page->getUri();
  308. if ($index > 1)
  309. {
  310. if ($uri != '')
  311. $uri .= '/';
  312. $uri .= $index;
  313. }
  314. return PieCrustHelper::formatUri($this->page->getApp(), $uri);
  315. }
  316. }