123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- <?php
- /**
- * @package Grav\Common\Data
- *
- * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
- * @license MIT License; see LICENSE file for details.
- */
- namespace Grav\Common\Data;
- use Grav\Common\File\CompiledYamlFile;
- use Grav\Common\Grav;
- use Grav\Common\User\Interfaces\UserInterface;
- use RocketTheme\Toolbox\Blueprints\BlueprintForm;
- use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
- class Blueprint extends BlueprintForm
- {
- /** @var string */
- protected $context = 'blueprints://';
- protected $scope;
- /** @var BlueprintSchema */
- protected $blueprintSchema;
- /** @var array */
- protected $defaults;
- protected $handlers = [];
- public function __clone()
- {
- if ($this->blueprintSchema) {
- $this->blueprintSchema = clone $this->blueprintSchema;
- }
- }
- public function setScope($scope)
- {
- $this->scope = $scope;
- }
- /**
- * Set default values for field types.
- *
- * @param array $types
- * @return $this
- */
- public function setTypes(array $types)
- {
- $this->initInternals();
- $this->blueprintSchema->setTypes($types);
- return $this;
- }
- /**
- * Get nested structure containing default values defined in the blueprints.
- *
- * Fields without default value are ignored in the list.
- *
- * @return array
- */
- public function getDefaults()
- {
- $this->initInternals();
- if (null === $this->defaults) {
- $this->defaults = $this->blueprintSchema->getDefaults();
- }
- return $this->defaults;
- }
- /**
- * Initialize blueprints with its dynamic fields.
- *
- * @return $this
- */
- public function init()
- {
- foreach ($this->dynamic as $key => $data) {
- // Locate field.
- $path = explode('/', $key);
- $current = &$this->items;
- foreach ($path as $field) {
- if (\is_object($current)) {
- // Handle objects.
- if (!isset($current->{$field})) {
- $current->{$field} = [];
- }
- $current = &$current->{$field};
- } else {
- // Handle arrays and scalars.
- if (!\is_array($current)) {
- $current = [$field => []];
- } elseif (!isset($current[$field])) {
- $current[$field] = [];
- }
- $current = &$current[$field];
- }
- }
- // Set dynamic property.
- foreach ($data as $property => $call) {
- $action = $call['action'];
- $method = 'dynamic' . ucfirst($action);
- if (isset($this->handlers[$action])) {
- $callable = $this->handlers[$action];
- $callable($current, $property, $call);
- } elseif (method_exists($this, $method)) {
- $this->{$method}($current, $property, $call);
- }
- }
- }
- return $this;
- }
- /**
- * Merge two arrays by using blueprints.
- *
- * @param array $data1
- * @param array $data2
- * @param string $name Optional
- * @param string $separator Optional
- * @return array
- */
- public function mergeData(array $data1, array $data2, $name = null, $separator = '.')
- {
- $this->initInternals();
- return $this->blueprintSchema->mergeData($data1, $data2, $name, $separator);
- }
- /**
- * Process data coming from a form.
- *
- * @param array $data
- * @param array $toggles
- * @return array
- */
- public function processForm(array $data, array $toggles = [])
- {
- $this->initInternals();
- return $this->blueprintSchema->processForm($data, $toggles);
- }
- /**
- * Return data fields that do not exist in blueprints.
- *
- * @param array $data
- * @param string $prefix
- * @return array
- */
- public function extra(array $data, $prefix = '')
- {
- $this->initInternals();
- return $this->blueprintSchema->extra($data, $prefix);
- }
- /**
- * Validate data against blueprints.
- *
- * @param array $data
- * @throws \RuntimeException
- */
- public function validate(array $data)
- {
- $this->initInternals();
- $this->blueprintSchema->validate($data);
- }
- /**
- * Filter data by using blueprints.
- *
- * @param array $data
- * @param bool $missingValuesAsNull
- * @param bool $keepEmptyValues
- * @return array
- */
- public function filter(array $data, bool $missingValuesAsNull = false, bool $keepEmptyValues = false)
- {
- $this->initInternals();
- return $this->blueprintSchema->filter($data, $missingValuesAsNull, $keepEmptyValues);
- }
- /**
- * Flatten data by using blueprints.
- *
- * @param array $data
- * @return array
- */
- public function flattenData(array $data)
- {
- $this->initInternals();
- return $this->blueprintSchema->flattenData($data);
- }
- /**
- * Return blueprint data schema.
- *
- * @return BlueprintSchema
- */
- public function schema()
- {
- $this->initInternals();
- return $this->blueprintSchema;
- }
- public function addDynamicHandler(string $name, callable $callable): void
- {
- $this->handlers[$name] = $callable;
- }
- /**
- * Initialize validator.
- */
- protected function initInternals()
- {
- if (null === $this->blueprintSchema) {
- $types = Grav::instance()['plugins']->formFieldTypes;
- $this->blueprintSchema = new BlueprintSchema;
- if ($types) {
- $this->blueprintSchema->setTypes($types);
- }
- $this->blueprintSchema->embed('', $this->items);
- $this->blueprintSchema->init();
- $this->defaults = null;
- }
- }
- /**
- * @param string $filename
- * @return string
- */
- protected function loadFile($filename)
- {
- $file = CompiledYamlFile::instance($filename);
- $content = $file->content();
- $file->free();
- return $content;
- }
- /**
- * @param string|array $path
- * @param string $context
- * @return array
- */
- protected function getFiles($path, $context = null)
- {
- /** @var UniformResourceLocator $locator */
- $locator = Grav::instance()['locator'];
- if (\is_string($path) && !$locator->isStream($path)) {
- // Find path overrides.
- $paths = (array) ($this->overrides[$path] ?? null);
- // Add path pointing to default context.
- if ($context === null) {
- $context = $this->context;
- }
- if ($context && $context[\strlen($context)-1] !== '/') {
- $context .= '/';
- }
- $path = $context . $path;
- if (!preg_match('/\.yaml$/', $path)) {
- $path .= '.yaml';
- }
- $paths[] = $path;
- } else {
- $paths = (array) $path;
- }
- $files = [];
- foreach ($paths as $lookup) {
- if (\is_string($lookup) && strpos($lookup, '://')) {
- $files = array_merge($files, $locator->findResources($lookup));
- } else {
- $files[] = $lookup;
- }
- }
- return array_values(array_unique($files));
- }
- /**
- * @param array $field
- * @param string $property
- * @param array $call
- */
- protected function dynamicData(array &$field, $property, array &$call)
- {
- $params = $call['params'];
- if (\is_array($params)) {
- $function = array_shift($params);
- } else {
- $function = $params;
- $params = [];
- }
- [$o, $f] = explode('::', $function, 2);
- $data = null;
- if (!$f) {
- if (\function_exists($o)) {
- $data = \call_user_func_array($o, $params);
- }
- } else {
- if (method_exists($o, $f)) {
- $data = \call_user_func_array([$o, $f], $params);
- }
- }
- // If function returns a value,
- if (null !== $data) {
- if (\is_array($data) && isset($field[$property]) && \is_array($field[$property])) {
- // Combine field and @data-field together.
- $field[$property] += $data;
- } else {
- // Or create/replace field with @data-field.
- $field[$property] = $data;
- }
- }
- }
- /**
- * @param array $field
- * @param string $property
- * @param array $call
- */
- protected function dynamicConfig(array &$field, $property, array &$call)
- {
- $value = $call['params'];
- $default = $field[$property] ?? null;
- $config = Grav::instance()['config']->get($value, $default);
- if (null !== $config) {
- $field[$property] = $config;
- }
- }
- /**
- * @param array $field
- * @param string $property
- * @param array $call
- */
- protected function dynamicSecurity(array &$field, $property, array &$call)
- {
- if ($property || !empty($field['validate']['ignore'])) {
- return;
- }
- $grav = Grav::instance();
- $actions = (array)$call['params'];
- /** @var UserInterface|null $user */
- $user = $grav['user'] ?? null;
- foreach ($actions as $action) {
- if (!$user || !$user->authorize($action)) {
- $this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
- return;
- }
- }
- }
- /**
- * @param array $field
- * @param string $property
- * @param array $call
- */
- protected function dynamicScope(array &$field, $property, array &$call)
- {
- if ($property && $property !== 'ignore') {
- return;
- }
- $scopes = (array)$call['params'];
- $matches = \in_array($this->scope, $scopes, true);
- if ($this->scope && $property !== 'ignore') {
- $matches = !$matches;
- }
- if ($matches) {
- $this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
- return;
- }
- }
- protected function addPropertyRecursive(array &$field, $property, $value)
- {
- if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
- $field[$property] = array_merge_recursive($field[$property], $value);
- } else {
- $field[$property] = $value;
- }
- if (!empty($field['fields'])) {
- foreach ($field['fields'] as $key => &$child) {
- $this->addPropertyRecursive($child, $property, $value);
- }
- }
- }
- }
|