FlexObject.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. <?php
  2. /**
  3. * @package Grav\Framework\Flex
  4. *
  5. * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Framework\Flex;
  9. use Grav\Common\Data\Blueprint;
  10. use Grav\Common\Debugger;
  11. use Grav\Common\Grav;
  12. use Grav\Common\Twig\Twig;
  13. use Grav\Common\Utils;
  14. use Grav\Framework\Cache\CacheInterface;
  15. use Grav\Framework\ContentBlock\HtmlBlock;
  16. use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
  17. use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
  18. use Grav\Framework\Flex\Interfaces\FlexFormInterface;
  19. use Grav\Framework\Flex\Traits\FlexAuthorizeTrait;
  20. use Grav\Framework\Object\Access\NestedArrayAccessTrait;
  21. use Grav\Framework\Object\Access\NestedPropertyTrait;
  22. use Grav\Framework\Object\Access\OverloadedPropertyTrait;
  23. use Grav\Framework\Object\Base\ObjectTrait;
  24. use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
  25. use Grav\Framework\Object\Interfaces\ObjectInterface;
  26. use Grav\Framework\Object\Property\LazyPropertyTrait;
  27. use Psr\SimpleCache\InvalidArgumentException;
  28. use RocketTheme\Toolbox\Event\Event;
  29. use Twig\Error\LoaderError;
  30. use Twig\Error\SyntaxError;
  31. use Twig\Template;
  32. use Twig\TemplateWrapper;
  33. /**
  34. * Class FlexObject
  35. * @package Grav\Framework\Flex
  36. */
  37. class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
  38. {
  39. use ObjectTrait;
  40. use LazyPropertyTrait {
  41. LazyPropertyTrait::__construct as private objectConstruct;
  42. }
  43. use NestedPropertyTrait;
  44. use OverloadedPropertyTrait;
  45. use NestedArrayAccessTrait;
  46. use FlexAuthorizeTrait;
  47. /** @var FlexDirectory */
  48. private $_flexDirectory;
  49. /** @var FlexFormInterface[] */
  50. private $_forms = [];
  51. /** @var array */
  52. private $_storage;
  53. /** @var array */
  54. protected $_changes;
  55. /**
  56. * @return array
  57. */
  58. public static function getCachedMethods(): array
  59. {
  60. return [
  61. 'getTypePrefix' => true,
  62. 'getType' => true,
  63. 'getFlexType' => true,
  64. 'getFlexDirectory' => true,
  65. 'getCacheKey' => true,
  66. 'getCacheChecksum' => true,
  67. 'getTimestamp' => true,
  68. 'value' => true,
  69. 'exists' => true,
  70. 'hasProperty' => true,
  71. 'getProperty' => true,
  72. // FlexAclTrait
  73. 'isAuthorized' => 'session',
  74. ];
  75. }
  76. public static function createFromStorage(array $elements, array $storage, FlexDirectory $directory, bool $validate = false)
  77. {
  78. $instance = new static($elements, $storage['key'], $directory, $validate);
  79. $instance->setStorage($storage);
  80. return $instance;
  81. }
  82. /**
  83. * {@inheritdoc}
  84. * @see FlexObjectInterface::__construct()
  85. */
  86. public function __construct(array $elements, $key, FlexDirectory $directory, bool $validate = false)
  87. {
  88. $this->_flexDirectory = $directory;
  89. if ($validate) {
  90. $blueprint = $this->getFlexDirectory()->getBlueprint();
  91. $blueprint->validate($elements);
  92. $elements = $blueprint->filter($elements);
  93. }
  94. $this->filterElements($elements);
  95. $this->objectConstruct($elements, $key);
  96. }
  97. /**
  98. * {@inheritdoc}
  99. * @see FlexObjectInterface::getFlexType()
  100. */
  101. public function getFlexType(): string
  102. {
  103. return $this->_flexDirectory->getFlexType();
  104. }
  105. /**
  106. * {@inheritdoc}
  107. * @see FlexObjectInterface::getFlexDirectory()
  108. */
  109. public function getFlexDirectory(): FlexDirectory
  110. {
  111. return $this->_flexDirectory;
  112. }
  113. /**
  114. * {@inheritdoc}
  115. * @see FlexObjectInterface::getTimestamp()
  116. */
  117. public function getTimestamp(): int
  118. {
  119. return $this->_storage['storage_timestamp'] ?? 0;
  120. }
  121. /**
  122. * {@inheritdoc}
  123. * @see FlexObjectInterface::getCacheKey()
  124. */
  125. public function getCacheKey(): string
  126. {
  127. return $this->getTypePrefix() . $this->getFlexType() . '.' . $this->getStorageKey();
  128. }
  129. /**
  130. * {@inheritdoc}
  131. * @see FlexObjectInterface::getCacheChecksum()
  132. */
  133. public function getCacheChecksum(): string
  134. {
  135. return (string)$this->getTimestamp();
  136. }
  137. /**
  138. * {@inheritdoc}
  139. * @see FlexObjectInterface::search()
  140. */
  141. public function search(string $search, $properties = null, array $options = null): float
  142. {
  143. $options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
  144. $properties = $properties ?? $this->getFlexDirectory()->getConfig('data.search.fields', []);
  145. if (!$properties) {
  146. foreach ($this->getFlexDirectory()->getConfig('admin.list.fields', []) as $property => $value) {
  147. if (!empty($value['link'])) {
  148. $properties[] = $property;
  149. }
  150. }
  151. }
  152. $weight = 0;
  153. foreach ((array)$properties as $property) {
  154. $weight += $this->searchNestedProperty($property, $search, $options);
  155. }
  156. return $weight > 0 ? min($weight, 1) : 0;
  157. }
  158. /**
  159. * {@inheritdoc}
  160. * @see ObjectInterface::getFlexKey()
  161. */
  162. public function getKey()
  163. {
  164. return $this->_key ?: $this->getFlexType() . '@@' . spl_object_hash($this);
  165. }
  166. /**
  167. * {@inheritdoc}
  168. * @see FlexObjectInterface::getFlexKey()
  169. */
  170. public function getFlexKey(): string
  171. {
  172. return $this->_storage['flex_key'] ?? $this->_flexDirectory->getFlexType() . '.obj:' . $this->getStorageKey();
  173. }
  174. /**
  175. * {@inheritdoc}
  176. * @see FlexObjectInterface::getStorageKey()
  177. */
  178. public function getStorageKey(): string
  179. {
  180. return $this->_storage['storage_key'] ?? $this->getTypePrefix() . $this->getFlexType() . '@@' . spl_object_hash($this);
  181. }
  182. /**
  183. * {@inheritdoc}
  184. * @see FlexObjectInterface::getMetaData()
  185. */
  186. public function getMetaData(): array
  187. {
  188. return $this->getStorage();
  189. }
  190. /**
  191. * {@inheritdoc}
  192. * @see FlexObjectInterface::exists()
  193. */
  194. public function exists(): bool
  195. {
  196. $key = $this->getStorageKey();
  197. return $key && $this->getFlexDirectory()->getStorage()->hasKey($key);
  198. }
  199. /**
  200. * @param string $property
  201. * @param string $search
  202. * @param array|null $options
  203. * @return float
  204. */
  205. public function searchProperty(string $property, string $search, array $options = null): float
  206. {
  207. $options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
  208. $value = $this->getProperty($property);
  209. return $this->searchValue($property, $value, $search, $options);
  210. }
  211. /**
  212. * @param string $property
  213. * @param string $search
  214. * @param array|null $options
  215. * @return float
  216. */
  217. public function searchNestedProperty(string $property, string $search, array $options = null): float
  218. {
  219. $options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
  220. $value = $this->getNestedProperty($property);
  221. return $this->searchValue($property, $value, $search, $options);
  222. }
  223. /**
  224. * @param string $name
  225. * @param mixed $value
  226. * @param string $search
  227. * @param array|null $options
  228. * @return float
  229. */
  230. protected function searchValue(string $name, $value, string $search, array $options = null): float
  231. {
  232. $search = trim($search);
  233. if ($search === '') {
  234. return 0;
  235. }
  236. if (!\is_string($value) || $value === '') {
  237. return 0;
  238. }
  239. $tested = false;
  240. if (($tested |= !empty($options['starts_with'])) && Utils::startsWith($value, $search, $options['case_sensitive'] ?? false)) {
  241. return (float)$options['starts_with'];
  242. }
  243. if (($tested |= !empty($options['ends_with'])) && Utils::endsWith($value, $search, $options['case_sensitive'] ?? false)) {
  244. return (float)$options['ends_with'];
  245. }
  246. if ((!$tested || !empty($options['contains'])) && Utils::contains($value, $search, $options['case_sensitive'] ?? false)) {
  247. return (float)($options['contains'] ?? 1);
  248. }
  249. return 0;
  250. }
  251. /**
  252. * Get any changes based on data sent to update
  253. *
  254. * @return array
  255. */
  256. public function getChanges(): array
  257. {
  258. return $this->_changes ?? [];
  259. }
  260. /**
  261. * @return string
  262. */
  263. protected function getTypePrefix(): string
  264. {
  265. return 'o.';
  266. }
  267. /**
  268. * @param bool $prefix
  269. * @return string
  270. * @deprecated 1.6 Use `->getFlexType()` instead.
  271. */
  272. public function getType($prefix = false)
  273. {
  274. user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED);
  275. $type = $prefix ? $this->getTypePrefix() : '';
  276. return $type . $this->getFlexType();
  277. }
  278. /**
  279. * Alias of getBlueprint()
  280. *
  281. * @return Blueprint
  282. * @deprecated 1.6 Admin compatibility
  283. */
  284. public function blueprints()
  285. {
  286. return $this->getBlueprint();
  287. }
  288. /**
  289. * @param string|null $namespace
  290. * @return CacheInterface
  291. */
  292. public function getCache(string $namespace = null)
  293. {
  294. return $this->_flexDirectory->getCache($namespace);
  295. }
  296. /**
  297. * @param string|null $key
  298. * @return $this
  299. */
  300. public function setStorageKey($key = null)
  301. {
  302. $this->_storage['storage_key'] = $key;
  303. return $this;
  304. }
  305. /**
  306. * @param int $timestamp
  307. * @return $this
  308. */
  309. public function setTimestamp($timestamp = null)
  310. {
  311. $this->_storage['storage_timestamp'] = $timestamp ?? time();
  312. return $this;
  313. }
  314. /**
  315. * {@inheritdoc}
  316. * @see FlexObjectInterface::render()
  317. */
  318. public function render(string $layout = null, array $context = [])
  319. {
  320. if (null === $layout) {
  321. $layout = 'default';
  322. }
  323. $type = $this->getFlexType();
  324. $grav = Grav::instance();
  325. /** @var Debugger $debugger */
  326. $debugger = $grav['debugger'];
  327. $debugger->startTimer('flex-object-' . ($debugKey = uniqid($type, false)), 'Render Object ' . $type . ' (' . $layout . ')');
  328. $cache = $key = null;
  329. foreach ($context as $value) {
  330. if (!\is_scalar($value)) {
  331. $key = false;
  332. }
  333. }
  334. if ($key !== false) {
  335. $key = md5($this->getCacheKey() . '.' . $layout . json_encode($context));
  336. $cache = $this->getCache('render');
  337. }
  338. try {
  339. $data = $cache && $key ? $cache->get($key) : null;
  340. $block = $data ? HtmlBlock::fromArray($data) : null;
  341. } catch (InvalidArgumentException $e) {
  342. $debugger->addException($e);
  343. $block = null;
  344. } catch (\InvalidArgumentException $e) {
  345. $debugger->addException($e);
  346. $block = null;
  347. }
  348. $checksum = $this->getCacheChecksum();
  349. if ($block && $checksum !== $block->getChecksum()) {
  350. $block = null;
  351. }
  352. if (!$block) {
  353. $block = HtmlBlock::create($key ?: null);
  354. $block->setChecksum($checksum);
  355. if ($key === false) {
  356. $block->disableCache();
  357. }
  358. $grav->fireEvent('onFlexObjectRender', new Event([
  359. 'object' => $this,
  360. 'layout' => &$layout,
  361. 'context' => &$context
  362. ]));
  363. $output = $this->getTemplate($layout)->render(
  364. ['grav' => $grav, 'config' => $grav['config'], 'block' => $block, 'object' => $this, 'layout' => $layout] + $context
  365. );
  366. if ($debugger->enabled()) {
  367. $name = $this->getKey() . ' (' . $type . ')';
  368. $output = "\n<!–– START {$name} object ––>\n{$output}\n<!–– END {$name} object ––>\n";
  369. }
  370. $block->setContent($output);
  371. try {
  372. $cache && $key && $block->isCached() && $cache->set($key, $block->toArray());
  373. } catch (InvalidArgumentException $e) {
  374. $debugger->addException($e);
  375. }
  376. }
  377. $debugger->stopTimer('flex-object-' . $debugKey);
  378. return $block;
  379. }
  380. /**
  381. * @return array
  382. */
  383. public function jsonSerialize()
  384. {
  385. return $this->getElements();
  386. }
  387. /**
  388. * {@inheritdoc}
  389. * @see FlexObjectInterface::prepareStorage()
  390. */
  391. public function prepareStorage(): array
  392. {
  393. return $this->getElements();
  394. }
  395. /**
  396. * @param string $name
  397. * @return $this
  398. */
  399. public function triggerEvent($name)
  400. {
  401. return $this;
  402. }
  403. /**
  404. * {@inheritdoc}
  405. * @see FlexObjectInterface::update()
  406. */
  407. public function update(array $data, array $files = [])
  408. {
  409. if ($data) {
  410. $blueprint = $this->getBlueprint();
  411. // Process updated data through the object filters.
  412. $this->filterElements($data);
  413. // Get currently stored data.
  414. $elements = $this->getElements();
  415. // Merge existing object to the test data to be validated.
  416. $test = $blueprint->mergeData($elements, $data);
  417. // Validate and filter elements and throw an error if any issues were found.
  418. $blueprint->validate($test + ['storage_key' => $this->getStorageKey(), 'timestamp' => $this->getTimestamp()]);
  419. $data = $blueprint->filter($data, false, true);
  420. // Finally update the object.
  421. foreach ($blueprint->flattenData($data) as $key => $value) {
  422. if ($value === null) {
  423. $this->unsetNestedProperty($key);
  424. } else {
  425. $this->setNestedProperty($key, $value);
  426. }
  427. }
  428. // Store the changes
  429. $this->_changes = Utils::arrayDiffMultidimensional($this->getElements(), $elements);
  430. }
  431. if ($files && method_exists($this, 'setUpdatedMedia')) {
  432. $this->setUpdatedMedia($files);
  433. }
  434. return $this;
  435. }
  436. /**
  437. * {@inheritdoc}
  438. * @see FlexObjectInterface::create()
  439. */
  440. public function create(string $key = null)
  441. {
  442. if ($key) {
  443. $this->setStorageKey($key);
  444. }
  445. if ($this->exists()) {
  446. throw new \RuntimeException('Cannot create new object (Already exists)');
  447. }
  448. return $this->save();
  449. }
  450. /**
  451. * {@inheritdoc}
  452. * @see FlexObjectInterface::save()
  453. */
  454. public function save()
  455. {
  456. $this->triggerEvent('onBeforeSave');
  457. $result = $this->getFlexDirectory()->getStorage()->replaceRows([$this->getStorageKey() => $this->prepareStorage()]);
  458. $value = reset($result);
  459. $storageKey = (string)key($result);
  460. if ($value && $storageKey) {
  461. $this->setStorageKey($storageKey);
  462. if (!$this->hasKey()) {
  463. $this->setKey($storageKey);
  464. }
  465. }
  466. // FIXME: For some reason locator caching isn't cleared for the file, investigate!
  467. $locator = Grav::instance()['locator'];
  468. $locator->clearCache();
  469. // Make sure that the object exists before continuing (just in case).
  470. if (!$this->exists()) {
  471. throw new \RuntimeException('Saving failed: Object does not exist!');
  472. }
  473. if (method_exists($this, 'saveUpdatedMedia')) {
  474. $this->saveUpdatedMedia();
  475. }
  476. try {
  477. $this->getFlexDirectory()->clearCache();
  478. if (method_exists($this, 'clearMediaCache')) {
  479. $this->clearMediaCache();
  480. }
  481. } catch (\Exception $e) {
  482. /** @var Debugger $debugger */
  483. $debugger = Grav::instance()['debugger'];
  484. $debugger->addException($e);
  485. // Caching failed, but we can ignore that for now.
  486. }
  487. $this->triggerEvent('onAfterSave');
  488. return $this;
  489. }
  490. /**
  491. * {@inheritdoc}
  492. * @see FlexObjectInterface::delete()
  493. */
  494. public function delete()
  495. {
  496. $this->triggerEvent('onBeforeDelete');
  497. $this->getFlexDirectory()->getStorage()->deleteRows([$this->getStorageKey() => $this->prepareStorage()]);
  498. try {
  499. $this->getFlexDirectory()->clearCache();
  500. if (method_exists($this, 'clearMediaCache')) {
  501. $this->clearMediaCache();
  502. }
  503. } catch (\Exception $e) {
  504. /** @var Debugger $debugger */
  505. $debugger = Grav::instance()['debugger'];
  506. $debugger->addException($e);
  507. // Caching failed, but we can ignore that for now.
  508. }
  509. $this->triggerEvent('onAfterDelete');
  510. return $this;
  511. }
  512. /**
  513. * {@inheritdoc}
  514. * @see FlexObjectInterface::getBlueprint()
  515. */
  516. public function getBlueprint(string $name = '')
  517. {
  518. return $this->_flexDirectory->getBlueprint($name ? '.' . $name : $name);
  519. }
  520. /**
  521. * {@inheritdoc}
  522. * @see FlexObjectInterface::getForm()
  523. */
  524. public function getForm(string $name = '', array $form = null)
  525. {
  526. if (!isset($this->_forms[$name])) {
  527. $this->_forms[$name] = $this->createFormObject($name, $form);
  528. }
  529. return $this->_forms[$name];
  530. }
  531. /**
  532. * {@inheritdoc}
  533. * @see FlexObjectInterface::getDefaultValue()
  534. */
  535. public function getDefaultValue(string $name, string $separator = null)
  536. {
  537. $separator = $separator ?: '.';
  538. $path = explode($separator, $name) ?: [];
  539. $offset = array_shift($path) ?? '';
  540. $current = $this->getDefaultValues();
  541. if (!isset($current[$offset])) {
  542. return null;
  543. }
  544. $current = $current[$offset];
  545. while ($path) {
  546. $offset = array_shift($path);
  547. if ((\is_array($current) || $current instanceof \ArrayAccess) && isset($current[$offset])) {
  548. $current = $current[$offset];
  549. } elseif (\is_object($current) && isset($current->{$offset})) {
  550. $current = $current->{$offset};
  551. } else {
  552. return null;
  553. }
  554. };
  555. return $current;
  556. }
  557. /**
  558. * @return array
  559. */
  560. public function getDefaultValues(): array
  561. {
  562. return $this->getBlueprint()->getDefaults();
  563. }
  564. /**
  565. * {@inheritdoc}
  566. * @see FlexObjectInterface::getFormValue()
  567. */
  568. public function getFormValue(string $name, $default = null, string $separator = null)
  569. {
  570. if ($name === 'storage_key') {
  571. return $this->getStorageKey();
  572. }
  573. if ($name === 'storage_timestamp') {
  574. return $this->getTimestamp();
  575. }
  576. return $this->getNestedProperty($name, $default, $separator);
  577. }
  578. /**
  579. * @param string $name
  580. * @param mixed|null $default
  581. * @param string|null $separator
  582. * @return mixed
  583. *
  584. * @deprecated 1.6 Use ->getFormValue() method instead.
  585. */
  586. public function value($name, $default = null, $separator = null)
  587. {
  588. return $this->getFormValue($name, $default, $separator);
  589. }
  590. /**
  591. * Returns a string representation of this object.
  592. *
  593. * @return string
  594. */
  595. public function __toString()
  596. {
  597. return $this->getFlexKey();
  598. }
  599. public function __debugInfo()
  600. {
  601. return [
  602. 'type:private' => $this->getFlexType(),
  603. 'key:private' => $this->getKey(),
  604. 'elements:private' => $this->getElements(),
  605. 'storage:private' => $this->getStorage()
  606. ];
  607. }
  608. /**
  609. * @return array
  610. */
  611. protected function doSerialize(): array
  612. {
  613. return [
  614. 'type' => $this->getFlexType(),
  615. 'key' => $this->getKey(),
  616. 'elements' => $this->getElements(),
  617. 'storage' => $this->getStorage()
  618. ];
  619. }
  620. /**
  621. * @param array $serialized
  622. */
  623. protected function doUnserialize(array $serialized): void
  624. {
  625. $type = $serialized['type'] ?? 'unknown';
  626. if (!isset($serialized['key'], $serialized['type'], $serialized['elements'])) {
  627. throw new \InvalidArgumentException("Cannot unserialize '{$type}': Bad data");
  628. }
  629. $grav = Grav::instance();
  630. /** @var Flex|null $flex */
  631. $flex = $grav['flex_objects'] ?? null;
  632. $directory = $flex ? $flex->getDirectory($type) : null;
  633. if (!$directory) {
  634. throw new \InvalidArgumentException("Cannot unserialize '{$type}': Not found");
  635. }
  636. $this->setFlexDirectory($directory);
  637. $this->setStorage($serialized['storage']);
  638. $this->setKey($serialized['key']);
  639. $this->setElements($serialized['elements']);
  640. }
  641. /**
  642. * @param FlexDirectory $directory
  643. */
  644. public function setFlexDirectory(FlexDirectory $directory): void
  645. {
  646. $this->_flexDirectory = $directory;
  647. }
  648. /**
  649. * @param array $storage
  650. */
  651. protected function setStorage(array $storage) : void
  652. {
  653. $this->_storage = $storage;
  654. }
  655. /**
  656. * @return array
  657. */
  658. protected function getStorage() : array
  659. {
  660. return $this->_storage ?? [];
  661. }
  662. /**
  663. * @param string $type
  664. * @param string $property
  665. * @return FlexCollectionInterface
  666. */
  667. protected function getCollectionByProperty($type, $property)
  668. {
  669. $directory = $this->getRelatedDirectory($type);
  670. $collection = $directory->getCollection();
  671. $list = $this->getNestedProperty($property) ?: [];
  672. /** @var FlexCollection $collection */
  673. $collection = $collection->filter(function ($object) use ($list) { return \in_array($object->id, $list, true); });
  674. return $collection;
  675. }
  676. /**
  677. * @param string $type
  678. * @return FlexDirectory
  679. * @throws \RuntimeException
  680. */
  681. protected function getRelatedDirectory($type): FlexDirectory
  682. {
  683. /** @var Flex $flex */
  684. $flex = Grav::instance()['flex_objects'];
  685. $directory = $flex->getDirectory($type);
  686. if (!$directory) {
  687. throw new \RuntimeException(ucfirst($type). ' directory does not exist!');
  688. }
  689. return $directory;
  690. }
  691. /**
  692. * @param string $layout
  693. * @return Template|TemplateWrapper
  694. * @throws LoaderError
  695. * @throws SyntaxError
  696. */
  697. protected function getTemplate($layout)
  698. {
  699. $grav = Grav::instance();
  700. /** @var Twig $twig */
  701. $twig = $grav['twig'];
  702. try {
  703. return $twig->twig()->resolveTemplate(
  704. [
  705. "flex-objects/layouts/{$this->getFlexType()}/object/{$layout}.html.twig",
  706. "flex-objects/layouts/_default/object/{$layout}.html.twig"
  707. ]
  708. );
  709. } catch (LoaderError $e) {
  710. /** @var Debugger $debugger */
  711. $debugger = Grav::instance()['debugger'];
  712. $debugger->addException($e);
  713. return $twig->twig()->resolveTemplate(['flex-objects/layouts/404.html.twig']);
  714. }
  715. }
  716. /**
  717. * Filter data coming to constructor or $this->update() request.
  718. *
  719. * NOTE: The incoming data can be an arbitrary array so do not assume anything from its content.
  720. *
  721. * @param array $elements
  722. */
  723. protected function filterElements(array &$elements): void
  724. {
  725. if (!empty($elements['storage_key'])) {
  726. $this->_storage['storage_key'] = trim($elements['storage_key']);
  727. }
  728. if (!empty($elements['storage_timestamp'])) {
  729. $this->_storage['storage_timestamp'] = (int)$elements['storage_timestamp'];
  730. }
  731. unset ($elements['storage_key'], $elements['storage_timestamp'], $elements['_post_entries_save']);
  732. }
  733. /**
  734. * This methods allows you to override form objects in child classes.
  735. *
  736. * @param string $name Form name
  737. * @param array|null $form Form fields
  738. * @return FlexFormInterface
  739. */
  740. protected function createFormObject(string $name, array $form = null)
  741. {
  742. return new FlexForm($name, $this, $form);
  743. }
  744. }