Collection.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. <?php
  2. /**
  3. * @package Grav.Common.Page
  4. *
  5. * @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\Page;
  9. use Grav\Common\Grav;
  10. use Grav\Common\Iterator;
  11. use Grav\Common\Utils;
  12. class Collection extends Iterator
  13. {
  14. /**
  15. * @var Pages
  16. */
  17. protected $pages;
  18. /**
  19. * @var array
  20. */
  21. protected $params;
  22. /**
  23. * Collection constructor.
  24. *
  25. * @param array $items
  26. * @param array $params
  27. * @param Pages|null $pages
  28. */
  29. public function __construct($items = [], array $params = [], Pages $pages = null)
  30. {
  31. parent::__construct($items);
  32. $this->params = $params;
  33. $this->pages = $pages ? $pages : Grav::instance()->offsetGet('pages');
  34. }
  35. /**
  36. * Get the collection params
  37. *
  38. * @return array
  39. */
  40. public function params()
  41. {
  42. return $this->params;
  43. }
  44. /**
  45. * Add a single page to a collection
  46. *
  47. * @param Page $page
  48. *
  49. * @return $this
  50. */
  51. public function addPage(Page $page)
  52. {
  53. $this->items[$page->path()] = ['slug' => $page->slug()];
  54. return $this;
  55. }
  56. /**
  57. * Add a page with path and slug
  58. *
  59. * @param $path
  60. * @param $slug
  61. * @return $this
  62. */
  63. public function add($path, $slug)
  64. {
  65. $this->items[$path] = ['slug' => $slug];
  66. return $this;
  67. }
  68. /**
  69. *
  70. * Create a copy of this collection
  71. *
  72. * @return static
  73. */
  74. public function copy()
  75. {
  76. return new static($this->items, $this->params, $this->pages);
  77. }
  78. /**
  79. *
  80. * Merge another collection with the current collection
  81. *
  82. * @param Collection $collection
  83. * @return $this
  84. */
  85. public function merge(Collection $collection)
  86. {
  87. foreach($collection as $page) {
  88. $this->addPage($page);
  89. }
  90. return $this;
  91. }
  92. /**
  93. * Intersect another collection with the current collection
  94. *
  95. * @param Collection $collection
  96. * @return $this
  97. */
  98. public function intersect(Collection $collection)
  99. {
  100. $array1 = $this->items;
  101. $array2 = $collection->toArray();
  102. $this->items = array_uintersect($array1, $array2, function($val1, $val2) {
  103. return strcmp($val1['slug'], $val2['slug']);
  104. });
  105. return $this;
  106. }
  107. /**
  108. * Set parameters to the Collection
  109. *
  110. * @param array $params
  111. *
  112. * @return $this
  113. */
  114. public function setParams(array $params)
  115. {
  116. $this->params = array_merge($this->params, $params);
  117. return $this;
  118. }
  119. /**
  120. * Returns current page.
  121. *
  122. * @return Page
  123. */
  124. public function current()
  125. {
  126. $current = parent::key();
  127. return $this->pages->get($current);
  128. }
  129. /**
  130. * Returns current slug.
  131. *
  132. * @return mixed
  133. */
  134. public function key()
  135. {
  136. $current = parent::current();
  137. return $current['slug'];
  138. }
  139. /**
  140. * Returns the value at specified offset.
  141. *
  142. * @param mixed $offset The offset to retrieve.
  143. *
  144. * @return mixed Can return all value types.
  145. */
  146. public function offsetGet($offset)
  147. {
  148. return !empty($this->items[$offset]) ? $this->pages->get($offset) : null;
  149. }
  150. /**
  151. * Split collection into array of smaller collections.
  152. *
  153. * @param $size
  154. * @return array|Collection[]
  155. */
  156. public function batch($size)
  157. {
  158. $chunks = array_chunk($this->items, $size, true);
  159. $list = [];
  160. foreach ($chunks as $chunk) {
  161. $list[] = new static($chunk, $this->params, $this->pages);
  162. }
  163. return $list;
  164. }
  165. /**
  166. * Remove item from the list.
  167. *
  168. * @param Page|string|null $key
  169. *
  170. * @return $this
  171. * @throws \InvalidArgumentException
  172. */
  173. public function remove($key = null)
  174. {
  175. if ($key instanceof Page) {
  176. $key = $key->path();
  177. } elseif (is_null($key)) {
  178. $key = key($this->items);
  179. }
  180. if (!is_string($key)) {
  181. throw new \InvalidArgumentException('Invalid argument $key.');
  182. }
  183. parent::remove($key);
  184. return $this;
  185. }
  186. /**
  187. * Reorder collection.
  188. *
  189. * @param string $by
  190. * @param string $dir
  191. * @param array $manual
  192. * @param string $sort_flags
  193. *
  194. * @return $this
  195. */
  196. public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
  197. {
  198. $this->items = $this->pages->sortCollection($this, $by, $dir, $manual, $sort_flags);
  199. return $this;
  200. }
  201. /**
  202. * Check to see if this item is the first in the collection.
  203. *
  204. * @param string $path
  205. *
  206. * @return boolean True if item is first.
  207. */
  208. public function isFirst($path)
  209. {
  210. if ($this->items && $path == array_keys($this->items)[0]) {
  211. return true;
  212. } else {
  213. return false;
  214. }
  215. }
  216. /**
  217. * Check to see if this item is the last in the collection.
  218. *
  219. * @param string $path
  220. *
  221. * @return boolean True if item is last.
  222. */
  223. public function isLast($path)
  224. {
  225. if ($this->items && $path == array_keys($this->items)[count($this->items) - 1]) {
  226. return true;
  227. } else {
  228. return false;
  229. }
  230. }
  231. /**
  232. * Gets the previous sibling based on current position.
  233. *
  234. * @param string $path
  235. *
  236. * @return Page The previous item.
  237. */
  238. public function prevSibling($path)
  239. {
  240. return $this->adjacentSibling($path, -1);
  241. }
  242. /**
  243. * Gets the next sibling based on current position.
  244. *
  245. * @param string $path
  246. *
  247. * @return Page The next item.
  248. */
  249. public function nextSibling($path)
  250. {
  251. return $this->adjacentSibling($path, 1);
  252. }
  253. /**
  254. * Returns the adjacent sibling based on a direction.
  255. *
  256. * @param string $path
  257. * @param integer $direction either -1 or +1
  258. *
  259. * @return Page The sibling item.
  260. */
  261. public function adjacentSibling($path, $direction = 1)
  262. {
  263. $values = array_keys($this->items);
  264. $keys = array_flip($values);
  265. if (array_key_exists($path, $keys)) {
  266. $index = $keys[$path] - $direction;
  267. return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
  268. }
  269. return $this;
  270. }
  271. /**
  272. * Returns the item in the current position.
  273. *
  274. * @param string $path the path the item
  275. *
  276. * @return Integer the index of the current page.
  277. */
  278. public function currentPosition($path)
  279. {
  280. return array_search($path, array_keys($this->items));
  281. }
  282. /**
  283. * Returns the items between a set of date ranges of either the page date field (default) or
  284. * an arbitrary datetime page field where end date is optional
  285. * Dates can be passed in as text that strtotime() can process
  286. * http://php.net/manual/en/function.strtotime.php
  287. *
  288. * @param $startDate
  289. * @param bool $endDate
  290. * @param $field
  291. *
  292. * @return $this
  293. * @throws \Exception
  294. */
  295. public function dateRange($startDate, $endDate = false, $field = false)
  296. {
  297. $start = Utils::date2timestamp($startDate);
  298. $end = $endDate ? Utils::date2timestamp($endDate) : false;
  299. $date_range = [];
  300. foreach ($this->items as $path => $slug) {
  301. $page = $this->pages->get($path);
  302. if ($page !== null) {
  303. $date = $field ? strtotime($page->value($field)) : $page->date();
  304. if ($date >= $start && (!$end || $date <= $end)) {
  305. $date_range[$path] = $slug;
  306. }
  307. }
  308. }
  309. $this->items = $date_range;
  310. return $this;
  311. }
  312. /**
  313. * Creates new collection with only visible pages
  314. *
  315. * @return Collection The collection with only visible pages
  316. */
  317. public function visible()
  318. {
  319. $visible = [];
  320. foreach ($this->items as $path => $slug) {
  321. $page = $this->pages->get($path);
  322. if ($page !== null && $page->visible()) {
  323. $visible[$path] = $slug;
  324. }
  325. }
  326. $this->items = $visible;
  327. return $this;
  328. }
  329. /**
  330. * Creates new collection with only non-visible pages
  331. *
  332. * @return Collection The collection with only non-visible pages
  333. */
  334. public function nonVisible()
  335. {
  336. $visible = [];
  337. foreach ($this->items as $path => $slug) {
  338. $page = $this->pages->get($path);
  339. if ($page !== null && !$page->visible()) {
  340. $visible[$path] = $slug;
  341. }
  342. }
  343. $this->items = $visible;
  344. return $this;
  345. }
  346. /**
  347. * Creates new collection with only modular pages
  348. *
  349. * @return Collection The collection with only modular pages
  350. */
  351. public function modular()
  352. {
  353. $modular = [];
  354. foreach ($this->items as $path => $slug) {
  355. $page = $this->pages->get($path);
  356. if ($page !== null && $page->modular()) {
  357. $modular[$path] = $slug;
  358. }
  359. }
  360. $this->items = $modular;
  361. return $this;
  362. }
  363. /**
  364. * Creates new collection with only non-modular pages
  365. *
  366. * @return Collection The collection with only non-modular pages
  367. */
  368. public function nonModular()
  369. {
  370. $modular = [];
  371. foreach ($this->items as $path => $slug) {
  372. $page = $this->pages->get($path);
  373. if ($page !== null && !$page->modular()) {
  374. $modular[$path] = $slug;
  375. }
  376. }
  377. $this->items = $modular;
  378. return $this;
  379. }
  380. /**
  381. * Creates new collection with only published pages
  382. *
  383. * @return Collection The collection with only published pages
  384. */
  385. public function published()
  386. {
  387. $published = [];
  388. foreach ($this->items as $path => $slug) {
  389. $page = $this->pages->get($path);
  390. if ($page !== null && $page->published()) {
  391. $published[$path] = $slug;
  392. }
  393. }
  394. $this->items = $published;
  395. return $this;
  396. }
  397. /**
  398. * Creates new collection with only non-published pages
  399. *
  400. * @return Collection The collection with only non-published pages
  401. */
  402. public function nonPublished()
  403. {
  404. $published = [];
  405. foreach ($this->items as $path => $slug) {
  406. $page = $this->pages->get($path);
  407. if ($page !== null && !$page->published()) {
  408. $published[$path] = $slug;
  409. }
  410. }
  411. $this->items = $published;
  412. return $this;
  413. }
  414. /**
  415. * Creates new collection with only routable pages
  416. *
  417. * @return Collection The collection with only routable pages
  418. */
  419. public function routable()
  420. {
  421. $routable = [];
  422. foreach ($this->items as $path => $slug) {
  423. $page = $this->pages->get($path);
  424. if ($page !== null && $page->routable()) {
  425. $routable[$path] = $slug;
  426. }
  427. }
  428. $this->items = $routable;
  429. return $this;
  430. }
  431. /**
  432. * Creates new collection with only non-routable pages
  433. *
  434. * @return Collection The collection with only non-routable pages
  435. */
  436. public function nonRoutable()
  437. {
  438. $routable = [];
  439. foreach ($this->items as $path => $slug) {
  440. $page = $this->pages->get($path);
  441. if ($page !== null && !$page->routable()) {
  442. $routable[$path] = $slug;
  443. }
  444. }
  445. $this->items = $routable;
  446. return $this;
  447. }
  448. /**
  449. * Creates new collection with only pages of the specified type
  450. *
  451. * @param $type
  452. *
  453. * @return Collection The collection
  454. */
  455. public function ofType($type)
  456. {
  457. $items = [];
  458. foreach ($this->items as $path => $slug) {
  459. $page = $this->pages->get($path);
  460. if ($page !== null && $page->template() == $type) {
  461. $items[$path] = $slug;
  462. }
  463. }
  464. $this->items = $items;
  465. return $this;
  466. }
  467. /**
  468. * Creates new collection with only pages of one of the specified types
  469. *
  470. * @param $types
  471. *
  472. * @return Collection The collection
  473. */
  474. public function ofOneOfTheseTypes($types)
  475. {
  476. $items = [];
  477. foreach ($this->items as $path => $slug) {
  478. $page = $this->pages->get($path);
  479. if ($page !== null && in_array($page->template(), $types)) {
  480. $items[$path] = $slug;
  481. }
  482. }
  483. $this->items = $items;
  484. return $this;
  485. }
  486. /**
  487. * Creates new collection with only pages of one of the specified access levels
  488. *
  489. * @param $accessLevels
  490. *
  491. * @return Collection The collection
  492. */
  493. public function ofOneOfTheseAccessLevels($accessLevels)
  494. {
  495. $items = [];
  496. foreach ($this->items as $path => $slug) {
  497. $page = $this->pages->get($path);
  498. if ($page !== null && isset($page->header()->access)) {
  499. if (is_array($page->header()->access)) {
  500. //Multiple values for access
  501. $valid = false;
  502. foreach ($page->header()->access as $index => $accessLevel) {
  503. if (is_array($accessLevel)) {
  504. foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
  505. if (in_array($innerAccessLevel, $accessLevels)) {
  506. $valid = true;
  507. }
  508. }
  509. } else {
  510. if (in_array($index, $accessLevels)) {
  511. $valid = true;
  512. }
  513. }
  514. }
  515. if ($valid) {
  516. $items[$path] = $slug;
  517. }
  518. } else {
  519. //Single value for access
  520. if (in_array($page->header()->access, $accessLevels)) {
  521. $items[$path] = $slug;
  522. }
  523. }
  524. }
  525. }
  526. $this->items = $items;
  527. return $this;
  528. }
  529. /**
  530. * Get the extended version of this Collection with each page keyed by route
  531. *
  532. * @return array
  533. * @throws \Exception
  534. */
  535. public function toExtendedArray()
  536. {
  537. $items = [];
  538. foreach ($this->items as $path => $slug) {
  539. $page = $this->pages->get($path);
  540. if ($page !== null) {
  541. $items[$page->route()] = $page->toArray();
  542. }
  543. }
  544. return $items;
  545. }
  546. }