1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252 |
- <?php
- /**
- * @package Grav\Framework\Flex
- *
- * @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
- * @license MIT License; see LICENSE file for details.
- */
- namespace Grav\Framework\Flex;
- use ArrayAccess;
- use Exception;
- use Grav\Common\Data\Blueprint;
- use Grav\Common\Debugger;
- use Grav\Common\Grav;
- use Grav\Common\Inflector;
- use Grav\Common\Twig\Twig;
- use Grav\Common\User\Interfaces\UserInterface;
- use Grav\Common\Utils;
- use Grav\Framework\Cache\CacheInterface;
- use Grav\Framework\ContentBlock\HtmlBlock;
- use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
- use Grav\Framework\Flex\Interfaces\FlexFormInterface;
- use Grav\Framework\Flex\Traits\FlexAuthorizeTrait;
- use Grav\Framework\Flex\Traits\FlexRelatedDirectoryTrait;
- use Grav\Framework\Object\Access\NestedArrayAccessTrait;
- use Grav\Framework\Object\Access\NestedPropertyTrait;
- use Grav\Framework\Object\Access\OverloadedPropertyTrait;
- use Grav\Framework\Object\Base\ObjectTrait;
- use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
- use Grav\Framework\Object\Interfaces\ObjectInterface;
- use Grav\Framework\Object\Property\LazyPropertyTrait;
- use Psr\SimpleCache\InvalidArgumentException;
- use RocketTheme\Toolbox\Event\Event;
- use RuntimeException;
- use Twig\Error\LoaderError;
- use Twig\Error\SyntaxError;
- use Twig\Template;
- use Twig\TemplateWrapper;
- use function get_class;
- use function in_array;
- use function is_array;
- use function is_object;
- use function is_scalar;
- use function is_string;
- use function json_encode;
- /**
- * Class FlexObject
- * @package Grav\Framework\Flex
- */
- class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
- {
- use ObjectTrait;
- use LazyPropertyTrait {
- LazyPropertyTrait::__construct as private objectConstruct;
- }
- use NestedPropertyTrait;
- use OverloadedPropertyTrait;
- use NestedArrayAccessTrait;
- use FlexAuthorizeTrait;
- use FlexRelatedDirectoryTrait;
- /** @var FlexDirectory */
- private $_flexDirectory;
- /** @var FlexFormInterface[] */
- private $_forms = [];
- /** @var Blueprint[] */
- private $_blueprint = [];
- /** @var array */
- private $_meta;
- /** @var array */
- protected $_original;
- /** @var array */
- protected $_changes;
- /** @var string */
- protected $storage_key;
- /** @var int */
- protected $storage_timestamp;
- /**
- * @return array
- */
- public static function getCachedMethods(): array
- {
- return [
- 'getTypePrefix' => true,
- 'getType' => true,
- 'getFlexType' => true,
- 'getFlexDirectory' => true,
- 'hasFlexFeature' => true,
- 'getFlexFeatures' => true,
- 'getCacheKey' => true,
- 'getCacheChecksum' => false,
- 'getTimestamp' => true,
- 'value' => true,
- 'exists' => true,
- 'hasProperty' => true,
- 'getProperty' => true,
- // FlexAclTrait
- 'isAuthorized' => 'session',
- ];
- }
- /**
- * @param array $elements
- * @param array $storage
- * @param FlexDirectory $directory
- * @param bool $validate
- * @return static
- */
- public static function createFromStorage(array $elements, array $storage, FlexDirectory $directory, bool $validate = false)
- {
- $instance = new static($elements, $storage['key'], $directory, $validate);
- $instance->setMetaData($storage);
- return $instance;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::__construct()
- */
- public function __construct(array $elements, $key, FlexDirectory $directory, bool $validate = false)
- {
- if (get_class($this) === __CLASS__) {
- user_error('Using ' . __CLASS__ . ' directly is deprecated since Grav 1.7, use \Grav\Common\Flex\Types\Generic\GenericObject or your own class instead', E_USER_DEPRECATED);
- }
- $this->_flexDirectory = $directory;
- if (isset($elements['__META'])) {
- $this->setMetaData($elements['__META']);
- unset($elements['__META']);
- }
- if ($validate) {
- $blueprint = $this->getFlexDirectory()->getBlueprint();
- $blueprint->validate($elements, ['xss_check' => false]);
- $elements = $blueprint->filter($elements, true, true);
- }
- $this->filterElements($elements);
- $this->objectConstruct($elements, $key);
- }
- /**
- * {@inheritdoc}
- * @see FlexCommonInterface::hasFlexFeature()
- */
- public function hasFlexFeature(string $name): bool
- {
- return in_array($name, $this->getFlexFeatures(), true);
- }
- /**
- * {@inheritdoc}
- * @see FlexCommonInterface::hasFlexFeature()
- */
- public function getFlexFeatures(): array
- {
- $implements = class_implements($this);
- $list = [];
- foreach ($implements as $interface) {
- if ($pos = strrpos($interface, '\\')) {
- $interface = substr($interface, $pos+1);
- }
- $list[] = Inflector::hyphenize(str_replace('Interface', '', $interface));
- }
- return $list;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getFlexType()
- */
- public function getFlexType(): string
- {
- return $this->_flexDirectory->getFlexType();
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getFlexDirectory()
- */
- public function getFlexDirectory(): FlexDirectory
- {
- return $this->_flexDirectory;
- }
- /**
- * Refresh object from the storage.
- *
- * @param bool $keepMissing
- * @return bool True if the object was refreshed
- */
- public function refresh(bool $keepMissing = false): bool
- {
- $key = $this->getStorageKey();
- if ('' === $key) {
- return false;
- }
- $storage = $this->getFlexDirectory()->getStorage();
- $meta = $storage->getMetaData([$key])[$key] ?? null;
- $newChecksum = $meta['checksum'] ?? $meta['storage_timestamp'] ?? null;
- $curChecksum = $this->_meta['checksum'] ?? $this->_meta['storage_timestamp'] ?? null;
- // Check if object is up to date with the storage.
- if (null === $newChecksum || $newChecksum === $curChecksum) {
- return false;
- }
- // Get current elements (if requested).
- $current = $keepMissing ? $this->getElements() : [];
- // Get elements from the filesystem.
- $elements = $storage->readRows([$key => null])[$key] ?? null;
- if (null !== $elements) {
- $meta = $elements['__META'] ?? $meta;
- unset($elements['__META']);
- $this->filterElements($elements);
- $newKey = $meta['key'] ?? $this->getKey();
- if ($meta) {
- $this->setMetaData($meta);
- }
- $this->objectConstruct($elements, $newKey);
- if ($current) {
- // Inject back elements which are missing in the filesystem.
- $data = $this->getBlueprint()->flattenData($current);
- foreach ($data as $property => $value) {
- if (strpos($property, '.') === false) {
- $this->defProperty($property, $value);
- } else {
- $this->defNestedProperty($property, $value);
- }
- }
- }
- /** @var Debugger $debugger */
- $debugger = Grav::instance()['debugger'];
- $debugger->addMessage("Refreshed {$this->getFlexType()} object {$this->getKey()}", 'debug');
- }
- return true;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getTimestamp()
- */
- public function getTimestamp(): int
- {
- return $this->_meta['storage_timestamp'] ?? 0;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getCacheKey()
- */
- public function getCacheKey(): string
- {
- return $this->hasKey() ? $this->getTypePrefix() . $this->getFlexType() . '.' . $this->getKey() : '';
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getCacheChecksum()
- */
- public function getCacheChecksum(): string
- {
- return (string)($this->_meta['checksum'] ?? $this->getTimestamp());
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::search()
- */
- public function search(string $search, $properties = null, array $options = null): float
- {
- $properties = (array)($properties ?? $this->getFlexDirectory()->getConfig('data.search.fields'));
- if (!$properties) {
- $fields = $this->getFlexDirectory()->getConfig('admin.views.list.fields') ?? $this->getFlexDirectory()->getConfig('admin.list.fields', []);
- foreach ($fields as $property => $value) {
- if (!empty($value['link'])) {
- $properties[] = $property;
- }
- }
- }
- $options = $options ?? (array)$this->getFlexDirectory()->getConfig('data.search.options');
- $weight = 0;
- foreach ($properties as $property) {
- if (strpos($property, '.')) {
- $weight += $this->searchNestedProperty($property, $search, $options);
- } else {
- $weight += $this->searchProperty($property, $search, $options);
- }
- }
- return $weight > 0 ? min($weight, 1) : 0;
- }
- /**
- * {@inheritdoc}
- * @see ObjectInterface::getFlexKey()
- */
- public function getKey()
- {
- return (string)$this->_key;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getFlexKey()
- */
- public function getFlexKey(): string
- {
- $key = $this->_meta['flex_key'] ?? null;
- if (!$key && $key = $this->getStorageKey()) {
- $key = $this->_flexDirectory->getFlexType() . '.obj:' . $key;
- }
- return (string)$key;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getStorageKey()
- */
- public function getStorageKey(): string
- {
- return (string)($this->storage_key ?? $this->_meta['storage_key'] ?? null);
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getMetaData()
- */
- public function getMetaData(): array
- {
- return $this->_meta ?? [];
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::exists()
- */
- public function exists(): bool
- {
- $key = $this->getStorageKey();
- return $key && $this->getFlexDirectory()->getStorage()->hasKey($key);
- }
- /**
- * @param string $property
- * @param string $search
- * @param array|null $options
- * @return float
- */
- public function searchProperty(string $property, string $search, array $options = null): float
- {
- $options = $options ?? (array)$this->getFlexDirectory()->getConfig('data.search.options');
- $value = $this->getProperty($property);
- return $this->searchValue($property, $value, $search, $options);
- }
- /**
- * @param string $property
- * @param string $search
- * @param array|null $options
- * @return float
- */
- public function searchNestedProperty(string $property, string $search, array $options = null): float
- {
- $options = $options ?? (array)$this->getFlexDirectory()->getConfig('data.search.options');
- if ($property === 'key') {
- $value = $this->getKey();
- } else {
- $value = $this->getNestedProperty($property);
- }
- return $this->searchValue($property, $value, $search, $options);
- }
- /**
- * @param string $name
- * @param mixed $value
- * @param string $search
- * @param array|null $options
- * @return float
- */
- protected function searchValue(string $name, $value, string $search, array $options = null): float
- {
- $options = $options ?? [];
- // Ignore empty search strings.
- $search = trim($search);
- if ($search === '') {
- return 0;
- }
- // Search only non-empty string values.
- if (!is_string($value) || $value === '') {
- return 0;
- }
- $caseSensitive = $options['case_sensitive'] ?? false;
- $tested = false;
- if (($tested |= !empty($options['same_as']))) {
- if ($caseSensitive) {
- if ($value === $search) {
- return (float)$options['same_as'];
- }
- } elseif (mb_strtolower($value) === mb_strtolower($search)) {
- return (float)$options['same_as'];
- }
- }
- if (($tested |= !empty($options['starts_with'])) && Utils::startsWith($value, $search, $caseSensitive)) {
- return (float)$options['starts_with'];
- }
- if (($tested |= !empty($options['ends_with'])) && Utils::endsWith($value, $search, $caseSensitive)) {
- return (float)$options['ends_with'];
- }
- if ((!$tested || !empty($options['contains'])) && Utils::contains($value, $search, $caseSensitive)) {
- return (float)($options['contains'] ?? 1);
- }
- return 0;
- }
- /**
- * Get original data before update
- *
- * @return array
- */
- public function getOriginalData(): array
- {
- return $this->_original ?? [];
- }
- /**
- * Get any changes based on data sent to update
- *
- * @return array
- */
- public function getChanges(): array
- {
- return $this->_changes ?? [];
- }
- /**
- * @return string
- */
- protected function getTypePrefix(): string
- {
- return 'o.';
- }
- /**
- * Alias of getBlueprint()
- *
- * @return Blueprint
- * @deprecated 1.6 Admin compatibility
- */
- public function blueprints()
- {
- return $this->getBlueprint();
- }
- /**
- * @param string|null $namespace
- * @return CacheInterface
- */
- public function getCache(string $namespace = null)
- {
- return $this->_flexDirectory->getCache($namespace);
- }
- /**
- * @param string|null $key
- * @return $this
- */
- public function setStorageKey($key = null)
- {
- $this->storage_key = $key ?? '';
- return $this;
- }
- /**
- * @param int $timestamp
- * @return $this
- */
- public function setTimestamp($timestamp = null)
- {
- $this->storage_timestamp = $timestamp ?? time();
- return $this;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::render()
- */
- public function render(string $layout = null, array $context = [])
- {
- if (!$layout) {
- $config = $this->getTemplateConfig();
- $layout = $config['object']['defaults']['layout'] ?? 'default';
- }
- $type = $this->getFlexType();
- $grav = Grav::instance();
- /** @var Debugger $debugger */
- $debugger = $grav['debugger'];
- $debugger->startTimer('flex-object-' . ($debugKey = uniqid($type, false)), 'Render Object ' . $type . ' (' . $layout . ')');
- $key = $this->getCacheKey();
- // Disable caching if context isn't all scalars.
- if ($key) {
- foreach ($context as $value) {
- if (!is_scalar($value)) {
- $key = '';
- break;
- }
- }
- }
- if ($key) {
- // Create a new key which includes layout and context.
- $key = md5($key . '.' . $layout . json_encode($context));
- $cache = $this->getCache('render');
- } else {
- $cache = null;
- }
- try {
- $data = $cache ? $cache->get($key) : null;
- $block = $data ? HtmlBlock::fromArray($data) : null;
- } catch (InvalidArgumentException $e) {
- $debugger->addException($e);
- $block = null;
- } catch (\InvalidArgumentException $e) {
- $debugger->addException($e);
- $block = null;
- }
- $checksum = $this->getCacheChecksum();
- if ($block && $checksum !== $block->getChecksum()) {
- $block = null;
- }
- if (!$block) {
- $block = HtmlBlock::create($key ?: null);
- $block->setChecksum($checksum);
- if (!$cache) {
- $block->disableCache();
- }
- $event = new Event([
- 'type' => 'flex',
- 'directory' => $this->getFlexDirectory(),
- 'object' => $this,
- 'layout' => &$layout,
- 'context' => &$context
- ]);
- $this->triggerEvent('onRender', $event);
- $output = $this->getTemplate($layout)->render(
- [
- 'grav' => $grav,
- 'config' => $grav['config'],
- 'block' => $block,
- 'directory' => $this->getFlexDirectory(),
- 'object' => $this,
- 'layout' => $layout
- ] + $context
- );
- if ($debugger->enabled()) {
- $name = $this->getKey() . ' (' . $type . ')';
- $output = "\n<!–– START {$name} object ––>\n{$output}\n<!–– END {$name} object ––>\n";
- }
- $block->setContent($output);
- try {
- $cache && $block->isCached() && $cache->set($key, $block->toArray());
- } catch (InvalidArgumentException $e) {
- $debugger->addException($e);
- }
- }
- $debugger->stopTimer('flex-object-' . $debugKey);
- return $block;
- }
- /**
- * @return array
- */
- public function jsonSerialize()
- {
- return $this->getElements();
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::prepareStorage()
- */
- public function prepareStorage(): array
- {
- return ['__META' => $this->getMetaData()] + $this->getElements();
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::update()
- */
- public function update(array $data, array $files = [])
- {
- if ($data) {
- $blueprint = $this->getBlueprint();
- // Process updated data through the object filters.
- $this->filterElements($data);
- // Get currently stored data.
- $elements = $this->getElements();
- // Merge existing object to the test data to be validated.
- $test = $blueprint->mergeData($elements, $data);
- // Validate and filter elements and throw an error if any issues were found.
- $blueprint->validate($test + ['storage_key' => $this->getStorageKey(), 'timestamp' => $this->getTimestamp()], ['xss_check' => false]);
- $data = $blueprint->filter($data, true, true);
- // Finally update the object.
- foreach ($blueprint->flattenData($data) as $key => $value) {
- if ($value === null) {
- $this->unsetNestedProperty($key);
- } else {
- $this->setNestedProperty($key, $value);
- }
- }
- // Store the changes
- $this->_original = $this->getElements();
- $this->_changes = Utils::arrayDiffMultidimensional($this->_original, $elements);
- }
- if ($files && method_exists($this, 'setUpdatedMedia')) {
- $this->setUpdatedMedia($files);
- }
- return $this;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::create()
- */
- public function create(string $key = null)
- {
- if ($key) {
- $this->setStorageKey($key);
- }
- if ($this->exists()) {
- throw new RuntimeException('Cannot create new object (Already exists)');
- }
- return $this->save();
- }
- /**
- * @param string|null $key
- * @return FlexObject|FlexObjectInterface
- */
- public function createCopy(string $key = null)
- {
- $this->markAsCopy();
- return $this->create($key);
- }
- /**
- * @param UserInterface|null $user
- */
- public function check(UserInterface $user = null): void
- {
- // If user has been provided, check if the user has permissions to save this object.
- if ($user && !$this->isAuthorized('save', null, $user)) {
- throw new \RuntimeException('Forbidden', 403);
- }
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::save()
- */
- public function save()
- {
- $this->triggerEvent('onBeforeSave');
- $storage = $this->getFlexDirectory()->getStorage();
- $storageKey = $this->getStorageKey() ?: '@@' . spl_object_hash($this);
- $result = $storage->replaceRows([$storageKey => $this->prepareStorage()]);
- if (method_exists($this, 'clearMediaCache')) {
- $this->clearMediaCache();
- }
- $value = reset($result);
- $meta = $value['__META'] ?? null;
- if ($meta) {
- /** @var FlexIndex $indexClass */
- $indexClass = $this->getFlexDirectory()->getIndexClass();
- $indexClass::updateObjectMeta($meta, $value, $storage);
- $this->_meta = $meta;
- }
- if ($value) {
- $storageKey = $meta['storage_key'] ?? (string)key($result);
- if ($storageKey !== '') {
- $this->setStorageKey($storageKey);
- }
- $newKey = $meta['key'] ?? ($this->hasKey() ? $this->getKey() : null);
- $this->setKey($newKey ?? $storageKey);
- }
- // FIXME: For some reason locator caching isn't cleared for the file, investigate!
- $locator = Grav::instance()['locator'];
- $locator->clearCache();
- if (method_exists($this, 'saveUpdatedMedia')) {
- $this->saveUpdatedMedia();
- }
- try {
- $this->getFlexDirectory()->reloadIndex();
- if (method_exists($this, 'clearMediaCache')) {
- $this->clearMediaCache();
- }
- } catch (Exception $e) {
- /** @var Debugger $debugger */
- $debugger = Grav::instance()['debugger'];
- $debugger->addException($e);
- // Caching failed, but we can ignore that for now.
- }
- $this->triggerEvent('onAfterSave');
- return $this;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::delete()
- */
- public function delete()
- {
- if (!$this->exists()) {
- return $this;
- }
- $this->triggerEvent('onBeforeDelete');
- $this->getFlexDirectory()->getStorage()->deleteRows([$this->getStorageKey() => $this->prepareStorage()]);
- try {
- $this->getFlexDirectory()->reloadIndex();
- if (method_exists($this, 'clearMediaCache')) {
- $this->clearMediaCache();
- }
- } catch (Exception $e) {
- /** @var Debugger $debugger */
- $debugger = Grav::instance()['debugger'];
- $debugger->addException($e);
- // Caching failed, but we can ignore that for now.
- }
- $this->triggerEvent('onAfterDelete');
- return $this;
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getBlueprint()
- */
- public function getBlueprint(string $name = '')
- {
- if (!isset($this->_blueprint[$name])) {
- $blueprint = $this->doGetBlueprint($name);
- $blueprint->setScope('object');
- $blueprint->setObject($this);
- $this->_blueprint[$name] = $blueprint->init();
- }
- return $this->_blueprint[$name];
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getForm()
- */
- public function getForm(string $name = '', array $options = null)
- {
- $hash = $name . '-' . md5(json_encode($options, JSON_THROW_ON_ERROR));
- if (!isset($this->_forms[$hash])) {
- $this->_forms[$hash] = $this->createFormObject($name, $options);
- }
- return $this->_forms[$hash];
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getDefaultValue()
- */
- public function getDefaultValue(string $name, string $separator = null)
- {
- $separator = $separator ?: '.';
- $path = explode($separator, $name) ?: [];
- $offset = array_shift($path) ?? '';
- $current = $this->getDefaultValues();
- if (!isset($current[$offset])) {
- return null;
- }
- $current = $current[$offset];
- while ($path) {
- $offset = array_shift($path);
- if ((is_array($current) || $current instanceof ArrayAccess) && isset($current[$offset])) {
- $current = $current[$offset];
- } elseif (is_object($current) && isset($current->{$offset})) {
- $current = $current->{$offset};
- } else {
- return null;
- }
- };
- return $current;
- }
- /**
- * @return array
- */
- public function getDefaultValues(): array
- {
- return $this->getBlueprint()->getDefaults();
- }
- /**
- * {@inheritdoc}
- * @see FlexObjectInterface::getFormValue()
- */
- public function getFormValue(string $name, $default = null, string $separator = null)
- {
- if ($name === 'storage_key') {
- return $this->getStorageKey();
- }
- if ($name === 'storage_timestamp') {
- return $this->getTimestamp();
- }
- return $this->getNestedProperty($name, $default, $separator);
- }
- /**
- * @param FlexDirectory $directory
- */
- public function setFlexDirectory(FlexDirectory $directory): void
- {
- $this->_flexDirectory = $directory;
- }
- /**
- * Returns a string representation of this object.
- *
- * @return string
- */
- public function __toString()
- {
- return $this->getFlexKey();
- }
- /**
- * @return array
- */
- public function __debugInfo()
- {
- return [
- 'type:private' => $this->getFlexType(),
- 'storage_key:protected' => $this->getStorageKey(),
- 'storage_timestamp:protected' => $this->getTimestamp(),
- 'key:private' => $this->getKey(),
- 'elements:private' => $this->getElements(),
- 'storage:private' => $this->getMetaData()
- ];
- }
- /**
- * Clone object.
- */
- public function __clone()
- {
- // Allows future compatibility as parent::__clone() works.
- }
- protected function markAsCopy(): void
- {
- $meta = $this->getMetaData();
- $meta['copy'] = true;
- $this->_meta = $meta;
- }
- /**
- * @param string $name
- * @return Blueprint
- */
- protected function doGetBlueprint(string $name = ''): Blueprint
- {
- return $this->_flexDirectory->getBlueprint($name ? '.' . $name : $name);
- }
- /**
- * @param array $meta
- */
- protected function setMetaData(array $meta): void
- {
- $this->_meta = $meta;
- }
- /**
- * @return array
- */
- protected function doSerialize(): array
- {
- return [
- 'type' => $this->getFlexType(),
- 'key' => $this->getKey(),
- 'elements' => $this->getElements(),
- 'storage' => $this->getMetaData()
- ];
- }
- /**
- * @param array $serialized
- * @param FlexDirectory|null $directory
- * @return void
- */
- protected function doUnserialize(array $serialized, FlexDirectory $directory = null): void
- {
- $type = $serialized['type'] ?? 'unknown';
- if (!isset($serialized['key'], $serialized['type'], $serialized['elements'])) {
- throw new \InvalidArgumentException("Cannot unserialize '{$type}': Bad data");
- }
- if (null === $directory) {
- $directory = $this->getFlexContainer()->getDirectory($type);
- if (!$directory) {
- throw new \InvalidArgumentException("Cannot unserialize Flex type '{$type}': Directory not found");
- }
- }
- $this->setFlexDirectory($directory);
- $this->setMetaData($serialized['storage']);
- $this->setKey($serialized['key']);
- $this->setElements($serialized['elements']);
- }
- /**
- * @return array
- */
- protected function getTemplateConfig()
- {
- $config = $this->getFlexDirectory()->getConfig('site.templates', []);
- $defaults = array_replace($config['defaults'] ?? [], $config['object']['defaults'] ?? []);
- $config['object']['defaults'] = $defaults;
- return $config;
- }
- /**
- * @param string $layout
- * @return array
- */
- protected function getTemplatePaths(string $layout): array
- {
- $config = $this->getTemplateConfig();
- $type = $this->getFlexType();
- $defaults = $config['object']['defaults'] ?? [];
- $ext = $defaults['ext'] ?? '.html.twig';
- $types = array_unique(array_merge([$type], (array)($defaults['type'] ?? null)));
- $paths = $config['object']['paths'] ?? [
- 'flex/{TYPE}/object/{LAYOUT}{EXT}',
- 'flex-objects/layouts/{TYPE}/object/{LAYOUT}{EXT}'
- ];
- $table = ['TYPE' => '%1$s', 'LAYOUT' => '%2$s', 'EXT' => '%3$s'];
- $lookups = [];
- foreach ($paths as $path) {
- $path = Utils::simpleTemplate($path, $table);
- foreach ($types as $type) {
- $lookups[] = sprintf($path, $type, $layout, $ext);
- }
- }
- return array_unique($lookups);
- }
- /**
- * Filter data coming to constructor or $this->update() request.
- *
- * NOTE: The incoming data can be an arbitrary array so do not assume anything from its content.
- *
- * @param array $elements
- */
- protected function filterElements(array &$elements): void
- {
- if (isset($elements['storage_key'])) {
- $elements['storage_key'] = trim($elements['storage_key']);
- }
- if (isset($elements['storage_timestamp'])) {
- $elements['storage_timestamp'] = (int)$elements['storage_timestamp'];
- }
- unset($elements['_post_entries_save']);
- }
- /**
- * This methods allows you to override form objects in child classes.
- *
- * @param string $name Form name
- * @param array|null $options Form optiosn
- * @return FlexFormInterface
- */
- protected function createFormObject(string $name, array $options = null)
- {
- return new FlexForm($name, $this, $options);
- }
- /**
- * @param string $action
- * @return string
- */
- protected function getAuthorizeAction(string $action): string
- {
- // Handle special action save, which can mean either update or create.
- if ($action === 'save') {
- $action = $this->exists() ? 'update' : 'create';
- }
- return $action;
- }
- /**
- * Method to reset blueprints if the type changes.
- *
- * @return void
- * @since 1.7.18
- */
- protected function resetBlueprints(): void
- {
- $this->_blueprint = [];
- }
- // DEPRECATED METHODS
- /**
- * @param bool $prefix
- * @return string
- * @deprecated 1.6 Use `->getFlexType()` instead.
- */
- public function getType($prefix = false)
- {
- user_error(__METHOD__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED);
- $type = $prefix ? $this->getTypePrefix() : '';
- return $type . $this->getFlexType();
- }
- /**
- * @param string $name
- * @param mixed|null $default
- * @param string|null $separator
- * @return mixed
- *
- * @deprecated 1.6 Use ->getFormValue() method instead.
- */
- public function value($name, $default = null, $separator = null)
- {
- user_error(__METHOD__ . '() is deprecated since Grav 1.6, use ->getFormValue() method instead', E_USER_DEPRECATED);
- return $this->getFormValue($name, $default, $separator);
- }
- /**
- * @param string $name
- * @param object|null $event
- * @return $this
- * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\FlexObjectTrait
- */
- public function triggerEvent(string $name, $event = null)
- {
- user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\FlexObjectTrait', E_USER_DEPRECATED);
- if (null === $event) {
- $event = new Event([
- 'type' => 'flex',
- 'directory' => $this->getFlexDirectory(),
- 'object' => $this
- ]);
- }
- if (strpos($name, 'onFlexObject') !== 0 && strpos($name, 'on') === 0) {
- $name = 'onFlexObject' . substr($name, 2);
- }
- $grav = Grav::instance();
- if ($event instanceof Event) {
- $grav->fireEvent($name, $event);
- } else {
- $grav->dispatchEvent($event);
- }
- return $this;
- }
- /**
- * @param array $storage
- * @deprecated 1.7 Use `->setMetaData()` instead.
- */
- protected function setStorage(array $storage): void
- {
- user_error(__METHOD__ . '() is deprecated since Grav 1.7, use ->setMetaData() method instead', E_USER_DEPRECATED);
- $this->setMetaData($storage);
- }
- /**
- * @return array
- * @deprecated 1.7 Use `->getMetaData()` instead.
- */
- protected function getStorage(): array
- {
- user_error(__METHOD__ . '() is deprecated since Grav 1.7, use ->getMetaData() method instead', E_USER_DEPRECATED);
- return $this->getMetaData();
- }
- /**
- * @param string $layout
- * @return Template|TemplateWrapper
- * @throws LoaderError
- * @throws SyntaxError
- * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\GravTrait
- */
- protected function getTemplate($layout)
- {
- user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\GravTrait', E_USER_DEPRECATED);
- $grav = Grav::instance();
- /** @var Twig $twig */
- $twig = $grav['twig'];
- try {
- return $twig->twig()->resolveTemplate($this->getTemplatePaths($layout));
- } catch (LoaderError $e) {
- /** @var Debugger $debugger */
- $debugger = Grav::instance()['debugger'];
- $debugger->addException($e);
- return $twig->twig()->resolveTemplate(['flex/404.html.twig']);
- }
- }
- /**
- * @return Flex
- * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\GravTrait
- */
- protected function getFlexContainer(): Flex
- {
- user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\GravTrait', E_USER_DEPRECATED);
- /** @var Flex $flex */
- $flex = Grav::instance()['flex'];
- return $flex;
- }
- /**
- * @return UserInterface|null
- * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\GravTrait
- */
- protected function getActiveUser(): ?UserInterface
- {
- user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\GravTrait', E_USER_DEPRECATED);
- /** @var UserInterface|null $user */
- $user = Grav::instance()['user'] ?? null;
- return $user;
- }
- /**
- * @return string
- * @deprecated 1.7 Moved to \Grav\Common\Flex\Traits\GravTrait
- */
- protected function getAuthorizeScope(): string
- {
- user_error(__METHOD__ . '() is deprecated since Grav 1.7, moved to \Grav\Common\Flex\Traits\GravTrait', E_USER_DEPRECATED);
- return isset(Grav::instance()['admin']) ? 'admin' : 'site';
- }
- }
|