AbstractPaginator.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. <?php
  2. namespace Illuminate\Pagination;
  3. use Closure;
  4. use Illuminate\Contracts\Support\Htmlable;
  5. use Illuminate\Support\Arr;
  6. use Illuminate\Support\Collection;
  7. use Illuminate\Support\Traits\ForwardsCalls;
  8. use Illuminate\Support\Traits\Tappable;
  9. use Stringable;
  10. use Traversable;
  11. /**
  12. * @mixin \Illuminate\Support\Collection
  13. */
  14. abstract class AbstractPaginator implements Htmlable, Stringable
  15. {
  16. use ForwardsCalls, Tappable;
  17. /**
  18. * All of the items being paginated.
  19. *
  20. * @var \Illuminate\Support\Collection
  21. */
  22. protected $items;
  23. /**
  24. * The number of items to be shown per page.
  25. *
  26. * @var int
  27. */
  28. protected $perPage;
  29. /**
  30. * The current page being "viewed".
  31. *
  32. * @var int
  33. */
  34. protected $currentPage;
  35. /**
  36. * The base path to assign to all URLs.
  37. *
  38. * @var string
  39. */
  40. protected $path = '/';
  41. /**
  42. * The query parameters to add to all URLs.
  43. *
  44. * @var array
  45. */
  46. protected $query = [];
  47. /**
  48. * The URL fragment to add to all URLs.
  49. *
  50. * @var string|null
  51. */
  52. protected $fragment;
  53. /**
  54. * The query string variable used to store the page.
  55. *
  56. * @var string
  57. */
  58. protected $pageName = 'page';
  59. /**
  60. * The number of links to display on each side of current page link.
  61. *
  62. * @var int
  63. */
  64. public $onEachSide = 3;
  65. /**
  66. * The paginator options.
  67. *
  68. * @var array
  69. */
  70. protected $options;
  71. /**
  72. * The current path resolver callback.
  73. *
  74. * @var \Closure
  75. */
  76. protected static $currentPathResolver;
  77. /**
  78. * The current page resolver callback.
  79. *
  80. * @var \Closure
  81. */
  82. protected static $currentPageResolver;
  83. /**
  84. * The query string resolver callback.
  85. *
  86. * @var \Closure
  87. */
  88. protected static $queryStringResolver;
  89. /**
  90. * The view factory resolver callback.
  91. *
  92. * @var \Closure
  93. */
  94. protected static $viewFactoryResolver;
  95. /**
  96. * The default pagination view.
  97. *
  98. * @var string
  99. */
  100. public static $defaultView = 'pagination::tailwind';
  101. /**
  102. * The default "simple" pagination view.
  103. *
  104. * @var string
  105. */
  106. public static $defaultSimpleView = 'pagination::simple-tailwind';
  107. /**
  108. * Determine if the given value is a valid page number.
  109. *
  110. * @param int $page
  111. * @return bool
  112. */
  113. protected function isValidPageNumber($page)
  114. {
  115. return $page >= 1 && filter_var($page, FILTER_VALIDATE_INT) !== false;
  116. }
  117. /**
  118. * Get the URL for the previous page.
  119. *
  120. * @return string|null
  121. */
  122. public function previousPageUrl()
  123. {
  124. if ($this->currentPage() > 1) {
  125. return $this->url($this->currentPage() - 1);
  126. }
  127. }
  128. /**
  129. * Create a range of pagination URLs.
  130. *
  131. * @param int $start
  132. * @param int $end
  133. * @return array
  134. */
  135. public function getUrlRange($start, $end)
  136. {
  137. return collect(range($start, $end))->mapWithKeys(function ($page) {
  138. return [$page => $this->url($page)];
  139. })->all();
  140. }
  141. /**
  142. * Get the URL for a given page number.
  143. *
  144. * @param int $page
  145. * @return string
  146. */
  147. public function url($page)
  148. {
  149. if ($page <= 0) {
  150. $page = 1;
  151. }
  152. // If we have any extra query string key / value pairs that need to be added
  153. // onto the URL, we will put them in query string form and then attach it
  154. // to the URL. This allows for extra information like sortings storage.
  155. $parameters = [$this->pageName => $page];
  156. if (count($this->query) > 0) {
  157. $parameters = array_merge($this->query, $parameters);
  158. }
  159. return $this->path()
  160. .(str_contains($this->path(), '?') ? '&' : '?')
  161. .Arr::query($parameters)
  162. .$this->buildFragment();
  163. }
  164. /**
  165. * Get / set the URL fragment to be appended to URLs.
  166. *
  167. * @param string|null $fragment
  168. * @return $this|string|null
  169. */
  170. public function fragment($fragment = null)
  171. {
  172. if (is_null($fragment)) {
  173. return $this->fragment;
  174. }
  175. $this->fragment = $fragment;
  176. return $this;
  177. }
  178. /**
  179. * Add a set of query string values to the paginator.
  180. *
  181. * @param array|string|null $key
  182. * @param string|null $value
  183. * @return $this
  184. */
  185. public function appends($key, $value = null)
  186. {
  187. if (is_null($key)) {
  188. return $this;
  189. }
  190. if (is_array($key)) {
  191. return $this->appendArray($key);
  192. }
  193. return $this->addQuery($key, $value);
  194. }
  195. /**
  196. * Add an array of query string values.
  197. *
  198. * @param array $keys
  199. * @return $this
  200. */
  201. protected function appendArray(array $keys)
  202. {
  203. foreach ($keys as $key => $value) {
  204. $this->addQuery($key, $value);
  205. }
  206. return $this;
  207. }
  208. /**
  209. * Add all current query string values to the paginator.
  210. *
  211. * @return $this
  212. */
  213. public function withQueryString()
  214. {
  215. if (isset(static::$queryStringResolver)) {
  216. return $this->appends(call_user_func(static::$queryStringResolver));
  217. }
  218. return $this;
  219. }
  220. /**
  221. * Add a query string value to the paginator.
  222. *
  223. * @param string $key
  224. * @param string $value
  225. * @return $this
  226. */
  227. protected function addQuery($key, $value)
  228. {
  229. if ($key !== $this->pageName) {
  230. $this->query[$key] = $value;
  231. }
  232. return $this;
  233. }
  234. /**
  235. * Build the full fragment portion of a URL.
  236. *
  237. * @return string
  238. */
  239. protected function buildFragment()
  240. {
  241. return $this->fragment ? '#'.$this->fragment : '';
  242. }
  243. /**
  244. * Load a set of relationships onto the mixed relationship collection.
  245. *
  246. * @param string $relation
  247. * @param array $relations
  248. * @return $this
  249. */
  250. public function loadMorph($relation, $relations)
  251. {
  252. $this->getCollection()->loadMorph($relation, $relations);
  253. return $this;
  254. }
  255. /**
  256. * Load a set of relationship counts onto the mixed relationship collection.
  257. *
  258. * @param string $relation
  259. * @param array $relations
  260. * @return $this
  261. */
  262. public function loadMorphCount($relation, $relations)
  263. {
  264. $this->getCollection()->loadMorphCount($relation, $relations);
  265. return $this;
  266. }
  267. /**
  268. * Get the slice of items being paginated.
  269. *
  270. * @return array
  271. */
  272. public function items()
  273. {
  274. return $this->items->all();
  275. }
  276. /**
  277. * Get the number of the first item in the slice.
  278. *
  279. * @return int|null
  280. */
  281. public function firstItem()
  282. {
  283. return count($this->items) > 0 ? ($this->currentPage - 1) * $this->perPage + 1 : null;
  284. }
  285. /**
  286. * Get the number of the last item in the slice.
  287. *
  288. * @return int|null
  289. */
  290. public function lastItem()
  291. {
  292. return count($this->items) > 0 ? $this->firstItem() + $this->count() - 1 : null;
  293. }
  294. /**
  295. * Transform each item in the slice of items using a callback.
  296. *
  297. * @param callable $callback
  298. * @return $this
  299. */
  300. public function through(callable $callback)
  301. {
  302. $this->items->transform($callback);
  303. return $this;
  304. }
  305. /**
  306. * Get the number of items shown per page.
  307. *
  308. * @return int
  309. */
  310. public function perPage()
  311. {
  312. return $this->perPage;
  313. }
  314. /**
  315. * Determine if there are enough items to split into multiple pages.
  316. *
  317. * @return bool
  318. */
  319. public function hasPages()
  320. {
  321. return $this->currentPage() != 1 || $this->hasMorePages();
  322. }
  323. /**
  324. * Determine if the paginator is on the first page.
  325. *
  326. * @return bool
  327. */
  328. public function onFirstPage()
  329. {
  330. return $this->currentPage() <= 1;
  331. }
  332. /**
  333. * Determine if the paginator is on the last page.
  334. *
  335. * @return bool
  336. */
  337. public function onLastPage()
  338. {
  339. return ! $this->hasMorePages();
  340. }
  341. /**
  342. * Get the current page.
  343. *
  344. * @return int
  345. */
  346. public function currentPage()
  347. {
  348. return $this->currentPage;
  349. }
  350. /**
  351. * Get the query string variable used to store the page.
  352. *
  353. * @return string
  354. */
  355. public function getPageName()
  356. {
  357. return $this->pageName;
  358. }
  359. /**
  360. * Set the query string variable used to store the page.
  361. *
  362. * @param string $name
  363. * @return $this
  364. */
  365. public function setPageName($name)
  366. {
  367. $this->pageName = $name;
  368. return $this;
  369. }
  370. /**
  371. * Set the base path to assign to all URLs.
  372. *
  373. * @param string $path
  374. * @return $this
  375. */
  376. public function withPath($path)
  377. {
  378. return $this->setPath($path);
  379. }
  380. /**
  381. * Set the base path to assign to all URLs.
  382. *
  383. * @param string $path
  384. * @return $this
  385. */
  386. public function setPath($path)
  387. {
  388. $this->path = $path;
  389. return $this;
  390. }
  391. /**
  392. * Set the number of links to display on each side of current page link.
  393. *
  394. * @param int $count
  395. * @return $this
  396. */
  397. public function onEachSide($count)
  398. {
  399. $this->onEachSide = $count;
  400. return $this;
  401. }
  402. /**
  403. * Get the base path for paginator generated URLs.
  404. *
  405. * @return string|null
  406. */
  407. public function path()
  408. {
  409. return $this->path;
  410. }
  411. /**
  412. * Resolve the current request path or return the default value.
  413. *
  414. * @param string $default
  415. * @return string
  416. */
  417. public static function resolveCurrentPath($default = '/')
  418. {
  419. if (isset(static::$currentPathResolver)) {
  420. return call_user_func(static::$currentPathResolver);
  421. }
  422. return $default;
  423. }
  424. /**
  425. * Set the current request path resolver callback.
  426. *
  427. * @param \Closure $resolver
  428. * @return void
  429. */
  430. public static function currentPathResolver(Closure $resolver)
  431. {
  432. static::$currentPathResolver = $resolver;
  433. }
  434. /**
  435. * Resolve the current page or return the default value.
  436. *
  437. * @param string $pageName
  438. * @param int $default
  439. * @return int
  440. */
  441. public static function resolveCurrentPage($pageName = 'page', $default = 1)
  442. {
  443. if (isset(static::$currentPageResolver)) {
  444. return (int) call_user_func(static::$currentPageResolver, $pageName);
  445. }
  446. return $default;
  447. }
  448. /**
  449. * Set the current page resolver callback.
  450. *
  451. * @param \Closure $resolver
  452. * @return void
  453. */
  454. public static function currentPageResolver(Closure $resolver)
  455. {
  456. static::$currentPageResolver = $resolver;
  457. }
  458. /**
  459. * Resolve the query string or return the default value.
  460. *
  461. * @param string|array|null $default
  462. * @return string
  463. */
  464. public static function resolveQueryString($default = null)
  465. {
  466. if (isset(static::$queryStringResolver)) {
  467. return (static::$queryStringResolver)();
  468. }
  469. return $default;
  470. }
  471. /**
  472. * Set with query string resolver callback.
  473. *
  474. * @param \Closure $resolver
  475. * @return void
  476. */
  477. public static function queryStringResolver(Closure $resolver)
  478. {
  479. static::$queryStringResolver = $resolver;
  480. }
  481. /**
  482. * Get an instance of the view factory from the resolver.
  483. *
  484. * @return \Illuminate\Contracts\View\Factory
  485. */
  486. public static function viewFactory()
  487. {
  488. return call_user_func(static::$viewFactoryResolver);
  489. }
  490. /**
  491. * Set the view factory resolver callback.
  492. *
  493. * @param \Closure $resolver
  494. * @return void
  495. */
  496. public static function viewFactoryResolver(Closure $resolver)
  497. {
  498. static::$viewFactoryResolver = $resolver;
  499. }
  500. /**
  501. * Set the default pagination view.
  502. *
  503. * @param string $view
  504. * @return void
  505. */
  506. public static function defaultView($view)
  507. {
  508. static::$defaultView = $view;
  509. }
  510. /**
  511. * Set the default "simple" pagination view.
  512. *
  513. * @param string $view
  514. * @return void
  515. */
  516. public static function defaultSimpleView($view)
  517. {
  518. static::$defaultSimpleView = $view;
  519. }
  520. /**
  521. * Indicate that Tailwind styling should be used for generated links.
  522. *
  523. * @return void
  524. */
  525. public static function useTailwind()
  526. {
  527. static::defaultView('pagination::tailwind');
  528. static::defaultSimpleView('pagination::simple-tailwind');
  529. }
  530. /**
  531. * Indicate that Bootstrap 4 styling should be used for generated links.
  532. *
  533. * @return void
  534. */
  535. public static function useBootstrap()
  536. {
  537. static::useBootstrapFour();
  538. }
  539. /**
  540. * Indicate that Bootstrap 3 styling should be used for generated links.
  541. *
  542. * @return void
  543. */
  544. public static function useBootstrapThree()
  545. {
  546. static::defaultView('pagination::default');
  547. static::defaultSimpleView('pagination::simple-default');
  548. }
  549. /**
  550. * Indicate that Bootstrap 4 styling should be used for generated links.
  551. *
  552. * @return void
  553. */
  554. public static function useBootstrapFour()
  555. {
  556. static::defaultView('pagination::bootstrap-4');
  557. static::defaultSimpleView('pagination::simple-bootstrap-4');
  558. }
  559. /**
  560. * Indicate that Bootstrap 5 styling should be used for generated links.
  561. *
  562. * @return void
  563. */
  564. public static function useBootstrapFive()
  565. {
  566. static::defaultView('pagination::bootstrap-5');
  567. static::defaultSimpleView('pagination::simple-bootstrap-5');
  568. }
  569. /**
  570. * Get an iterator for the items.
  571. *
  572. * @return \ArrayIterator
  573. */
  574. public function getIterator(): Traversable
  575. {
  576. return $this->items->getIterator();
  577. }
  578. /**
  579. * Determine if the list of items is empty.
  580. *
  581. * @return bool
  582. */
  583. public function isEmpty()
  584. {
  585. return $this->items->isEmpty();
  586. }
  587. /**
  588. * Determine if the list of items is not empty.
  589. *
  590. * @return bool
  591. */
  592. public function isNotEmpty()
  593. {
  594. return $this->items->isNotEmpty();
  595. }
  596. /**
  597. * Get the number of items for the current page.
  598. *
  599. * @return int
  600. */
  601. public function count(): int
  602. {
  603. return $this->items->count();
  604. }
  605. /**
  606. * Get the paginator's underlying collection.
  607. *
  608. * @return \Illuminate\Support\Collection
  609. */
  610. public function getCollection()
  611. {
  612. return $this->items;
  613. }
  614. /**
  615. * Set the paginator's underlying collection.
  616. *
  617. * @param \Illuminate\Support\Collection $collection
  618. * @return $this
  619. */
  620. public function setCollection(Collection $collection)
  621. {
  622. $this->items = $collection;
  623. return $this;
  624. }
  625. /**
  626. * Get the paginator options.
  627. *
  628. * @return array
  629. */
  630. public function getOptions()
  631. {
  632. return $this->options;
  633. }
  634. /**
  635. * Determine if the given item exists.
  636. *
  637. * @param mixed $key
  638. * @return bool
  639. */
  640. public function offsetExists($key): bool
  641. {
  642. return $this->items->has($key);
  643. }
  644. /**
  645. * Get the item at the given offset.
  646. *
  647. * @param mixed $key
  648. * @return mixed
  649. */
  650. public function offsetGet($key): mixed
  651. {
  652. return $this->items->get($key);
  653. }
  654. /**
  655. * Set the item at the given offset.
  656. *
  657. * @param mixed $key
  658. * @param mixed $value
  659. * @return void
  660. */
  661. public function offsetSet($key, $value): void
  662. {
  663. $this->items->put($key, $value);
  664. }
  665. /**
  666. * Unset the item at the given key.
  667. *
  668. * @param mixed $key
  669. * @return void
  670. */
  671. public function offsetUnset($key): void
  672. {
  673. $this->items->forget($key);
  674. }
  675. /**
  676. * Render the contents of the paginator to HTML.
  677. *
  678. * @return string
  679. */
  680. public function toHtml()
  681. {
  682. return (string) $this->render();
  683. }
  684. /**
  685. * Make dynamic calls into the collection.
  686. *
  687. * @param string $method
  688. * @param array $parameters
  689. * @return mixed
  690. */
  691. public function __call($method, $parameters)
  692. {
  693. return $this->forwardCallTo($this->getCollection(), $method, $parameters);
  694. }
  695. /**
  696. * Render the contents of the paginator when casting to a string.
  697. *
  698. * @return string
  699. */
  700. public function __toString()
  701. {
  702. return (string) $this->render();
  703. }
  704. }