From 7a61b09a8536202f1efd61dd5121a91bbeca9d6a Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 24 Sep 2018 11:25:51 +0300 Subject: [PATCH 1/5] Added proper support for hiding form fields in blueprints by using dynamic property like `security@: admin.foobar` to any field --- CHANGELOG.md | 1 + system/src/Grav/Common/Data/Blueprint.php | 42 ++++++++++++++++ .../src/Grav/Common/Data/BlueprintSchema.php | 48 +++++++++++++++---- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a619b6395..789fcd4cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * Added `Grav\Framework\Collection\AbstractIndexCollection` class * Added `Grav\Framework\Object\ObjectIndex` class * Added `Grav\Framework\Flex` classes + * Added proper support for hiding form fields in blueprints by using dynamic property like `security@: admin.foobar` to any field 1. [](#improved) * Doctrine filecache is now namespaced with prefix to support purging diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php index 740b339d5..b48581b6f 100644 --- a/system/src/Grav/Common/Data/Blueprint.php +++ b/system/src/Grav/Common/Data/Blueprint.php @@ -10,6 +10,7 @@ namespace Grav\Common\Data; use Grav\Common\File\CompiledYamlFile; use Grav\Common\Grav; +use Grav\Common\User\User; use RocketTheme\Toolbox\Blueprints\BlueprintForm; use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; @@ -252,4 +253,45 @@ class Blueprint extends BlueprintForm $field[$property] = $config; } } + + /** + * @param array $field + * @param string $property + * @param array $call + */ + protected function dynamicSecurity(array &$field, $property, array &$call) + { + if ($property) { + return; + } + + $grav = Grav::instance(); + $actions = (array)$call['params']; + + /** @var User $user */ + if (isset($grav['user'])) { + $user = Grav::instance()['user'] ?? null; + foreach ($actions as $action) { + if (!$user->authorize($action)) { + $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); + } + } + } } diff --git a/system/src/Grav/Common/Data/BlueprintSchema.php b/system/src/Grav/Common/Data/BlueprintSchema.php index 410309fe7..67a8021de 100644 --- a/system/src/Grav/Common/Data/BlueprintSchema.php +++ b/system/src/Grav/Common/Data/BlueprintSchema.php @@ -49,20 +49,20 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface /** * Filter data by using blueprints. * - * @param array $data + * @param array $data Incoming data, for example from a form. + * @param bool $missingValuesAsNull Include missing values as nulls. * @return array */ - public function filter(array $data) + public function filter(array $data, $missingValuesAsNull = false) { - return $this->filterArray($data, $this->nested); + return $this->filterArray($data, $this->nested, $missingValuesAsNull); } /** * @param array $data * @param array $rules - * @returns array + * @return array * @throws \RuntimeException - * @internal */ protected function validateArray(array $data, array $rules) { @@ -74,6 +74,11 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface if ($rule) { // Item has been defined in blueprints. + if (!empty($rule['validate']['ignore'])) { + // Skip validation in the ignored field. + continue; + } + $messages += Validation::validate($field, $rule); } elseif (\is_array($field) && \is_array($val)) { // Array has been defined in blueprints. @@ -90,22 +95,42 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface /** * @param array $data * @param array $rules + * @param bool $missingValuesAsNull * @return array - * @internal */ - protected function filterArray(array $data, array $rules) + protected function filterArray(array $data, array $rules, $missingValuesAsNull) { $results = []; + + if ($missingValuesAsNull) { + // First pass is to fill up all the fields with null. This is done to lock the ordering of the fields. + foreach ($rules as $key => $rule) { + if ($key && !isset($results[$key])) { + $val = $rules[$key] ?? $rules['*'] ?? null; + $rule = \is_string($val) ? $this->items[$val] : null; + + if (empty($rule['validate']['ignore'])) { + $results[$key] = null; + } + } + } + } + foreach ($data as $key => $field) { $val = $rules[$key] ?? $rules['*'] ?? null; $rule = \is_string($val) ? $this->items[$val] : null; if ($rule) { // Item has been defined in blueprints. + if (!empty($rule['validate']['ignore'])) { + // Skip any data in the ignored field. + continue; + } + $field = Validation::filter($field, $rule); } elseif (\is_array($field) && \is_array($val)) { // Array has been defined in blueprints. - $field = $this->filterArray($field, $val); + $field = $this->filterArray($field, $val, $missingValuesAsNull); } elseif (isset($rules['validation']) && $rules['validation'] === 'strict') { $field = null; } @@ -133,6 +158,13 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface } $field = $this->items[$field]; + + // Skip ignored field, it will not be required. + if (!empty($field['validate']['ignore'])) { + continue; + } + + // Check if required. if (isset($field['validate']['required']) && $field['validate']['required'] === true) { From 346b8683ac4b88471edeb176123dd9e8966ae699 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 24 Sep 2018 15:18:56 +0300 Subject: [PATCH 2/5] Fix extra authorize, forms fields stored into the Flex objects --- system/src/Grav/Framework/Flex/FlexObject.php | 10 +++++----- .../Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php index dd6db49a8..c67ca41db 100644 --- a/system/src/Grav/Framework/Flex/FlexObject.php +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -45,12 +45,12 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface /** @var FlexDirectory */ private $_flexDirectory; + /** @var FlexForm[] */ + private $_forms = []; /** @var string */ private $storageKey; /** @var int */ private $timestamp = 0; - /** @var FlexForm[] */ - private $forms = []; /** * @return array @@ -165,11 +165,11 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface */ public function getForm($name = 'default') { - if (!isset($this->forms[$name])) { - $this->forms[$name] = new FlexForm($name, $this); + if (!isset($this->_forms[$name])) { + $this->_forms[$name] = new FlexForm($name, $this); } - return $this->forms[$name]; + return $this->_forms[$name]; } /** diff --git a/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php index 2966f2bc8..728f8f188 100644 --- a/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php +++ b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php @@ -19,7 +19,7 @@ use Grav\Common\User\User; */ trait FlexAuthorizeTrait { - private $authorize = '%s.flex-object.%s'; + private $_authorize = '%s.flex-object.%s'; public function authorize(string $action, string $scope = null) : bool { @@ -34,11 +34,11 @@ trait FlexAuthorizeTrait $action = $this->exists() ? 'update' : 'create'; } - return $user->authorize(sprintf($this->authorize, $scope, $action)) || $user->authorize('admin.super'); + return $user->authorize(sprintf($this->_authorize, $scope, $action)) || $user->authorize('admin.super'); } protected function setAuthorizeRule(string $authorize) : void { - $this->authorize = $authorize; + $this->_authorize = $authorize; } } From ff1d5bc965cfcdbdc00748dc25eb1f31f7412e8f Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 24 Sep 2018 15:23:23 +0300 Subject: [PATCH 3/5] Fix extra storageKey, timestamps fields stored into the Flex objects --- system/src/Grav/Framework/Flex/FlexObject.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php index c67ca41db..23e8cf6e0 100644 --- a/system/src/Grav/Framework/Flex/FlexObject.php +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -48,9 +48,9 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface /** @var FlexForm[] */ private $_forms = []; /** @var string */ - private $storageKey; + private $_storageKey; /** @var int */ - private $timestamp = 0; + private $_timestamp = 0; /** * @return array @@ -212,7 +212,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface */ public function getStorageKey() { - return $this->storageKey; + return $this->_storageKey; } /** @@ -221,7 +221,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface */ public function setStorageKey($key = null) { - $this->storageKey = $key; + $this->_storageKey = $key; return $this; } @@ -231,7 +231,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface */ public function getTimestamp() : int { - return $this->timestamp; + return $this->_timestamp; } /** @@ -240,7 +240,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface */ public function setTimestamp($timestamp = null) { - $this->timestamp = $timestamp ?? time(); + $this->_timestamp = $timestamp ?? time(); return $this; } @@ -485,8 +485,8 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface throw new \InvalidArgumentException("Cannot unserialize '{$type}': Not found"); } $this->_flexDirectory = $directory; - $this->storageKey = $serialized['storage_key']; - $this->timestamp = $serialized['storage_timestamp']; + $this->_storageKey = $serialized['storage_key']; + $this->_timestamp = $serialized['storage_timestamp']; $this->setKey($serialized['key']); $this->setElements($serialized['elements']); @@ -571,10 +571,10 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface protected function filterElements(array &$elements) { if (!empty($elements['storage_key'])) { - $this->storageKey = trim($elements['storage_key']); + $this->_storageKey = trim($elements['storage_key']); } if (!empty($elements['storage_timestamp'])) { - $this->timestamp = (int)$elements['storage_timestamp']; + $this->_timestamp = (int)$elements['storage_timestamp']; } unset ($elements['storage_key'], $elements['storage_timestamp']); From 8c64434069e23f18f6e68b6529530b9422150833 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 24 Sep 2018 15:33:49 +0300 Subject: [PATCH 4/5] Fixed updating empty array (no data, should be ignored) --- system/src/Grav/Framework/Flex/FlexObject.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php index 23e8cf6e0..a9fd6f248 100644 --- a/system/src/Grav/Framework/Flex/FlexObject.php +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -125,9 +125,11 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface $blueprint->validate($data + ['storage_key' => $this->getStorageKey()]); $data = $blueprint->filter($data); - $this->filterElements($data); - $this->setElements($data); + + if ($data) { + $this->setElements($data); + } return $this; } From 007b17a7ba3717e6bf89eaee595c2f0fe3f1e023 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Tue, 25 Sep 2018 11:16:31 +0300 Subject: [PATCH 5/5] Fixed partial save for Flex Objects --- system/src/Grav/Framework/Flex/FlexObject.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php index a9fd6f248..944e8cb72 100644 --- a/system/src/Grav/Framework/Flex/FlexObject.php +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -116,15 +116,18 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface */ public function update(array $data, $isFullUpdate = false) { + // Validate and filter the incoming data. $blueprint = $this->getFlexDirectory()->getBlueprint(); + $blueprint->validate($data + ['storage_key' => $this->getStorageKey(), 'timestamp' => $this->getTimestamp()]); + $data = $blueprint->filter($data); if (!$isFullUpdate) { + // Partial update: merge data to the existing object. $elements = $this->getElements(); $data = $blueprint->mergeData($elements, $data); } - $blueprint->validate($data + ['storage_key' => $this->getStorageKey()]); - $data = $blueprint->filter($data); + // Filter object data. $this->filterElements($data); if ($data) {