first commit

This commit is contained in:
2019-03-28 17:57:56 +01:00
commit b0e25fd66f
561 changed files with 56803 additions and 0 deletions

View File

@@ -0,0 +1,254 @@
<?php
/**
* @package Grav.Common.Data
*
* @copyright Copyright (C) 2015 - 2018 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 RocketTheme\Toolbox\Blueprints\BlueprintForm;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Blueprint extends BlueprintForm
{
/**
* @var string
*/
protected $context = 'blueprints://';
/**
* @var BlueprintSchema
*/
protected $blueprintSchema;
/**
* 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();
return $this->blueprintSchema->getDefaults();
}
/**
* 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);
}
/**
* 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
* @return array
*/
public function filter(array $data)
{
$this->initInternals();
return $this->blueprintSchema->filter($data);
}
/**
* Return blueprint data schema.
*
* @return BlueprintSchema
*/
public function schema()
{
$this->initInternals();
return $this->blueprintSchema;
}
/**
* Initialize validator.
*/
protected function initInternals()
{
if (!isset($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();
}
}
/**
* @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 = isset($this->overrides[$path]) ? (array) $this->overrides[$path] : [];
// 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 = [];
}
list($o, $f) = preg_split('/::/', $function, 2);
if (!$f) {
if (function_exists($o)) {
$data = call_user_func_array($o, $params);
}
} else {
if (method_exists($o, $f)) {
$data = call_user_func_array(array($o, $f), $params);
}
}
// If function returns a value,
if (isset($data)) {
if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) {
// 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 = isset($field[$property]) ? $field[$property] : null;
$config = Grav::instance()['config']->get($value, $default);
if (!is_null($config)) {
$field[$property] = $config;
}
}
}

View File

@@ -0,0 +1,171 @@
<?php
/**
* @package Grav.Common.Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Data;
use Grav\Common\Grav;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
use RocketTheme\Toolbox\Blueprints\BlueprintSchema as BlueprintSchemaBase;
class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{
use Export;
protected $ignoreFormKeys = [
'title' => true,
'help' => true,
'placeholder' => true,
'placeholder_key' => true,
'placeholder_value' => true,
'fields' => true
];
/**
* Validate data against blueprints.
*
* @param array $data
* @throws \RuntimeException
*/
public function validate(array $data)
{
try {
$messages = $this->validateArray($data, $this->nested);
} catch (\RuntimeException $e) {
throw (new ValidationException($e->getMessage(), $e->getCode(), $e))->setMessages();
}
if (!empty($messages)) {
throw (new ValidationException())->setMessages($messages);
}
}
/**
* Filter data by using blueprints.
*
* @param array $data
* @return array
*/
public function filter(array $data)
{
return $this->filterArray($data, $this->nested);
}
/**
* @param array $data
* @param array $rules
* @returns array
* @throws \RuntimeException
* @internal
*/
protected function validateArray(array $data, array $rules)
{
$messages = $this->checkRequired($data, $rules);
foreach ($data as $key => $field) {
$val = isset($rules[$key]) ? $rules[$key] : (isset($rules['*']) ? $rules['*'] : null);
$rule = is_string($val) ? $this->items[$val] : null;
if ($rule) {
// Item has been defined in blueprints.
$messages += Validation::validate($field, $rule);
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$messages += $this->validateArray($field, $val);
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
// Undefined/extra item.
throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));
}
}
return $messages;
}
/**
* @param array $data
* @param array $rules
* @return array
* @internal
*/
protected function filterArray(array $data, array $rules)
{
$results = array();
foreach ($data as $key => $field) {
$val = isset($rules[$key]) ? $rules[$key] : (isset($rules['*']) ? $rules['*'] : null);
$rule = is_string($val) ? $this->items[$val] : null;
if ($rule) {
// Item has been defined in blueprints.
$field = Validation::filter($field, $rule);
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$field = $this->filterArray($field, $val);
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
$field = null;
}
if (isset($field) && (!is_array($field) || !empty($field))) {
$results[$key] = $field;
}
}
return $results;
}
/**
* @param array $data
* @param array $fields
* @return array
*/
protected function checkRequired(array $data, array $fields)
{
$messages = [];
foreach ($fields as $name => $field) {
if (!is_string($field)) {
continue;
}
$field = $this->items[$field];
if (isset($field['validate']['required'])
&& $field['validate']['required'] === true) {
if (isset($data[$name])) {
continue;
}
if ($field['type'] === 'file' && isset($data['data']['name'][$name])) { //handle case of file input fields required
continue;
}
$value = isset($field['label']) ? $field['label'] : $field['name'];
$language = Grav::instance()['language'];
$message = sprintf($language->translate('FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $language->translate($value));
$messages[$field['name']][] = $message;
}
}
return $messages;
}
/**
* @param array $field
* @param string $property
* @param array $call
*/
protected function dynamicConfig(array &$field, $property, array &$call)
{
$value = $call['params'];
$default = isset($field[$property]) ? $field[$property] : null;
$config = Grav::instance()['config']->get($value, $default);
if (null !== $config) {
$field[$property] = $config;
}
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* @package Grav.Common.Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Data;
use Grav\Common\Grav;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
class Blueprints
{
protected $search;
protected $types;
protected $instances = [];
/**
* @param string|array $search Search path.
*/
public function __construct($search = 'blueprints://')
{
$this->search = $search;
}
/**
* Get blueprint.
*
* @param string $type Blueprint type.
* @return Blueprint
* @throws \RuntimeException
*/
public function get($type)
{
if (!isset($this->instances[$type])) {
$this->instances[$type] = $this->loadFile($type);
}
return $this->instances[$type];
}
/**
* Get all available blueprint types.
*
* @return array List of type=>name
*/
public function types()
{
if ($this->types === null) {
$this->types = array();
$grav = Grav::instance();
/** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
// Get stream / directory iterator.
if ($locator->isStream($this->search)) {
$iterator = $locator->getIterator($this->search);
} else {
$iterator = new \DirectoryIterator($this->search);
}
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
if (!$file->isFile() || '.' . $file->getExtension() !== YAML_EXT) {
continue;
}
$name = $file->getBasename(YAML_EXT);
$this->types[$name] = ucfirst(str_replace('_', ' ', $name));
}
}
return $this->types;
}
/**
* Load blueprint file.
*
* @param string $name Name of the blueprint.
* @return Blueprint
*/
protected function loadFile($name)
{
$blueprint = new Blueprint($name);
if (is_array($this->search) || is_object($this->search)) {
// Page types.
$blueprint->setOverrides($this->search);
$blueprint->setContext('blueprints://pages');
} else {
$blueprint->setContext($this->search);
}
return $blueprint->load()->init();
}
}

View File

@@ -0,0 +1,287 @@
<?php
/**
* @package Grav.Common.Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Data;
use RocketTheme\Toolbox\ArrayTraits\Countable;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\FileInterface;
class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
{
use NestedArrayAccessWithGetters, Countable, Export;
protected $gettersVariable = 'items';
protected $items;
/**
* @var Blueprints
*/
protected $blueprints;
/**
* @var File
*/
protected $storage;
/**
* @param array $items
* @param Blueprint|callable $blueprints
*/
public function __construct(array $items = array(), $blueprints = null)
{
$this->items = $items;
$this->blueprints = $blueprints;
}
/**
* Get value by using dot notation for nested arrays/objects.
*
* @example $value = $data->value('this.is.my.nested.variable');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @return mixed Value.
*/
public function value($name, $default = null, $separator = '.')
{
return $this->get($name, $default, $separator);
}
/**
* Join nested values together by using blueprints.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return $this
* @throws \RuntimeException
*/
public function join($name, $value, $separator = '.')
{
$old = $this->get($name, null, $separator);
if ($old !== null) {
if (!is_array($old)) {
throw new \RuntimeException('Value ' . $old);
}
if (is_object($value)) {
$value = (array) $value;
} elseif (!is_array($value)) {
throw new \RuntimeException('Value ' . $value);
}
$value = $this->blueprints()->mergeData($old, $value, $name, $separator);
}
$this->set($name, $value, $separator);
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()
{
return $this->blueprints()->getDefaults();
}
/**
* Set default values by using blueprints.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return $this
*/
public function joinDefaults($name, $value, $separator = '.')
{
if (is_object($value)) {
$value = (array) $value;
}
$old = $this->get($name, null, $separator);
if ($old !== null) {
$value = $this->blueprints()->mergeData($value, $old, $name, $separator);
}
$this->set($name, $value, $separator);
return $this;
}
/**
* Get value from the configuration and join it with given data.
*
* @param string $name Dot separated path to the requested value.
* @param array $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return array
* @throws \RuntimeException
*/
public function getJoined($name, $value, $separator = '.')
{
if (is_object($value)) {
$value = (array) $value;
} elseif (!is_array($value)) {
throw new \RuntimeException('Value ' . $value);
}
$old = $this->get($name, null, $separator);
if ($old === null) {
// No value set; no need to join data.
return $value;
}
if (!is_array($old)) {
throw new \RuntimeException('Value ' . $old);
}
// Return joined data.
return $this->blueprints()->mergeData($old, $value, $name, $separator);
}
/**
* Merge two configurations together.
*
* @param array $data
* @return $this
*/
public function merge(array $data)
{
$this->items = $this->blueprints()->mergeData($this->items, $data);
return $this;
}
/**
* Set default values to the configuration if variables were not set.
*
* @param array $data
* @return $this
*/
public function setDefaults(array $data)
{
$this->items = $this->blueprints()->mergeData($data, $this->items);
return $this;
}
/**
* Validate by blueprints.
*
* @return $this
* @throws \Exception
*/
public function validate()
{
$this->blueprints()->validate($this->items);
return $this;
}
/**
* @return $this
* Filter all items by using blueprints.
*/
public function filter()
{
$this->items = $this->blueprints()->filter($this->items);
return $this;
}
/**
* Get extra items which haven't been defined in blueprints.
*
* @return array
*/
public function extra()
{
return $this->blueprints()->extra($this->items);
}
/**
* Return blueprints.
*
* @return Blueprint
*/
public function blueprints()
{
if (!$this->blueprints){
$this->blueprints = new Blueprint;
} elseif (is_callable($this->blueprints)) {
// Lazy load blueprints.
$blueprints = $this->blueprints;
$this->blueprints = $blueprints();
}
return $this->blueprints;
}
/**
* Save data if storage has been defined.
* @throws \RuntimeException
*/
public function save()
{
$file = $this->file();
if ($file) {
$file->save($this->items);
}
}
/**
* Returns whether the data already exists in the storage.
*
* NOTE: This method does not check if the data is current.
*
* @return bool
*/
public function exists()
{
$file = $this->file();
return $file && $file->exists();
}
/**
* Return unmodified data as raw string.
*
* NOTE: This function only returns data which has been saved to the storage.
*
* @return string
*/
public function raw()
{
$file = $this->file();
return $file ? $file->raw() : '';
}
/**
* Set or get the data storage.
*
* @param FileInterface $storage Optionally enter a new storage.
* @return FileInterface
*/
public function file(FileInterface $storage = null)
{
if ($storage) {
$this->storage = $storage;
}
return $this->storage;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* @package Grav.Common.Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Data;
use RocketTheme\Toolbox\File\FileInterface;
interface DataInterface
{
/**
* Get value by using dot notation for nested arrays/objects.
*
* @example $value = $data->value('this.is.my.nested.variable');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @return mixed Value.
*/
public function value($name, $default = null, $separator = '.');
/**
* Merge external data.
*
* @param array $data
* @return mixed
*/
public function merge(array $data);
/**
* Return blueprints.
*/
public function blueprints();
/**
* Validate by blueprints.
*
* @throws \Exception
*/
public function validate();
/**
* Filter all items by using blueprints.
*/
public function filter();
/**
* Get extra items which haven't been defined in blueprints.
*/
public function extra();
/**
* Save data into the file.
*/
public function save();
/**
* Set or get the data storage.
*
* @param FileInterface $storage Optionally enter a new storage.
* @return FileInterface
*/
public function file(FileInterface $storage = null);
}

View File

@@ -0,0 +1,765 @@
<?php
/**
* @package Grav.Common.Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Data;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Grav\Common\Yaml;
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYaml;
class Validation
{
/**
* Validate value against a blueprint field definition.
*
* @param $value
* @param array $field
* @return array
*/
public static function validate($value, array $field)
{
$messages = [];
$validate = isset($field['validate']) ? (array) $field['validate'] : [];
// Validate type with fallback type text.
$type = (string) isset($validate['type']) ? $validate['type'] : $field['type'];
$method = 'type'.strtr($type, '-', '_');
// If value isn't required, we will stop validation if empty value is given.
if ((empty($validate['required']) || (isset($validate['required']) && $validate['required'] !== true)) && ($value === null || $value === '' || (($field['type'] === 'checkbox' || $field['type'] === 'switch') && $value == false))) {
return $messages;
}
if (!isset($field['type'])) {
$field['type'] = 'text';
}
// Get language class.
$language = Grav::instance()['language'];
$name = ucfirst(isset($field['label']) ? $field['label'] : $field['name']);
$message = (string) isset($field['validate']['message'])
? $language->translate($field['validate']['message'])
: $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
// If this is a YAML field validate/filter as such
if ($type != 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
$method = 'typeYaml';
}
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $validate, $field);
} else {
$success = true;
}
if (!$success) {
$messages[$field['name']][] = $message;
}
// Check individual rules.
foreach ($validate as $rule => $params) {
$method = 'validate' . ucfirst(strtr($rule, '-', '_'));
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $params);
if (!$success) {
$messages[$field['name']][] = $message;
}
}
}
return $messages;
}
/**
* Filter value against a blueprint field definition.
*
* @param mixed $value
* @param array $field
* @return mixed Filtered value.
*/
public static function filter($value, array $field)
{
$validate = isset($field['validate']) ? (array) $field['validate'] : [];
// If value isn't required, we will return null if empty value is given.
if (empty($validate['required']) && ($value === null || $value === '')) {
return null;
}
if (!isset($field['type'])) {
$field['type'] = 'text';
}
// Validate type with fallback type text.
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
$method = 'filter' . ucfirst(strtr($type, '-', '_'));
// If this is a YAML field validate/filter as such
if ($type !== 'yaml' && isset($field['yaml']) && $field['yaml'] === true) {
$method = 'filterYaml';
}
if (!method_exists(__CLASS__, $method)) {
$method = 'filterText';
}
return self::$method($value, $validate, $field);
}
/**
* HTML5 input: text
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeText($value, array $params, array $field)
{
if (!is_string($value) && !is_numeric($value)) {
return false;
}
$value = (string)$value;
if (isset($params['min']) && strlen($value) < $params['min']) {
return false;
}
if (isset($params['max']) && strlen($value) > $params['max']) {
return false;
}
$min = isset($params['min']) ? $params['min'] : 0;
if (isset($params['step']) && (strlen($value) - $min) % $params['step'] == 0) {
return false;
}
if ((!isset($params['multiline']) || !$params['multiline']) && preg_match('/\R/um', $value)) {
return false;
}
return true;
}
protected static function filterText($value, array $params, array $field)
{
return (string) $value;
}
protected static function filterCommaList($value, array $params, array $field)
{
return is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
}
public static function typeCommaList($value, array $params, array $field)
{
return is_array($value) ? true : self::typeText($value, $params, $field);
}
protected static function filterLower($value, array $params)
{
return strtolower($value);
}
protected static function filterUpper($value, array $params)
{
return strtoupper($value);
}
/**
* HTML5 input: textarea
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeTextarea($value, array $params, array $field)
{
if (!isset($params['multiline'])) {
$params['multiline'] = true;
}
return self::typeText($value, $params, $field);
}
/**
* HTML5 input: password
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typePassword($value, array $params, array $field)
{
return self::typeText($value, $params, $field);
}
/**
* HTML5 input: hidden
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeHidden($value, array $params, array $field)
{
return self::typeText($value, $params, $field);
}
/**
* Custom input: checkbox list
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeCheckboxes($value, array $params, array $field)
{
// Set multiple: true so checkboxes can easily use min/max counts to control number of options required
$field['multiple'] = true;
return self::typeArray((array) $value, $params, $field);
}
protected static function filterCheckboxes($value, array $params, array $field)
{
return self::filterArray($value, $params, $field);
}
/**
* HTML5 input: checkbox
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeCheckbox($value, array $params, array $field)
{
$value = (string) $value;
if (!isset($field['value'])) {
$field['value'] = 1;
}
if (isset($value) && $value != $field['value']) {
return false;
}
return true;
}
/**
* HTML5 input: radio
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeRadio($value, array $params, array $field)
{
return self::typeArray((array) $value, $params, $field);
}
/**
* Custom input: toggle
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeToggle($value, array $params, array $field)
{
return self::typeArray((array) $value, $params, $field);
}
/**
* Custom input: file
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeFile($value, array $params, array $field)
{
return self::typeArray((array) $value, $params, $field);
}
protected static function filterFile($value, array $params, array $field)
{
return (array) $value;
}
/**
* HTML5 input: select
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeSelect($value, array $params, array $field)
{
return self::typeArray((array) $value, $params, $field);
}
/**
* HTML5 input: number
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeNumber($value, array $params, array $field)
{
if (!is_numeric($value)) {
return false;
}
if (isset($params['min']) && $value < $params['min']) {
return false;
}
if (isset($params['max']) && $value > $params['max']) {
return false;
}
$min = isset($params['min']) ? $params['min'] : 0;
if (isset($params['step']) && fmod($value - $min, $params['step']) == 0) {
return false;
}
return true;
}
protected static function filterNumber($value, array $params, array $field)
{
return (string)(int)$value !== (string)(float)$value ? (float) $value : (int) $value;
}
protected static function filterDateTime($value, array $params, array $field)
{
$format = Grav::instance()['config']->get('system.pages.dateformat.default');
if ($format) {
$converted = new \DateTime($value);
return $converted->format($format);
}
return $value;
}
/**
* HTML5 input: range
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeRange($value, array $params, array $field)
{
return self::typeNumber($value, $params, $field);
}
protected static function filterRange($value, array $params, array $field)
{
return self::filterNumber($value, $params, $field);
}
/**
* HTML5 input: color
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeColor($value, array $params, array $field)
{
return preg_match('/^\#[0-9a-fA-F]{3}[0-9a-fA-F]{3}?$/u', $value);
}
/**
* HTML5 input: email
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeEmail($value, array $params, array $field)
{
$values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
foreach ($values as $value) {
if (!(self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_EMAIL))) {
return false;
}
}
return true;
}
/**
* HTML5 input: url
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeUrl($value, array $params, array $field)
{
return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_URL);
}
/**
* HTML5 input: datetime
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeDatetime($value, array $params, array $field)
{
if ($value instanceof \DateTime) {
return true;
} elseif (!is_string($value)) {
return false;
} elseif (!isset($params['format'])) {
return false !== strtotime($value);
}
$dateFromFormat = \DateTime::createFromFormat($params['format'], $value);
return $dateFromFormat && $value === date($params['format'], $dateFromFormat->getTimestamp());
}
/**
* HTML5 input: datetime-local
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeDatetimeLocal($value, array $params, array $field)
{
return self::typeDatetime($value, $params, $field);
}
/**
* HTML5 input: date
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeDate($value, array $params, array $field)
{
$params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'Y-m-d';
}
return self::typeDatetime($value, $params, $field);
}
/**
* HTML5 input: time
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeTime($value, array $params, array $field)
{
$params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'H:i';
}
return self::typeDatetime($value, $params, $field);
}
/**
* HTML5 input: month
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeMonth($value, array $params, array $field)
{
$params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'Y-m';
}
return self::typeDatetime($value, $params, $field);
}
/**
* HTML5 input: week
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeWeek($value, array $params, array $field)
{
if (!isset($params['format']) && !preg_match('/^\d{4}-W\d{2}$/u', $value)) {
return false;
}
return self::typeDatetime($value, $params, $field);
}
/**
* Custom input: array
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeArray($value, array $params, array $field)
{
if (!is_array($value)) {
return false;
}
if (isset($field['multiple'])) {
if (isset($params['min']) && count($value) < $params['min']) {
return false;
}
if (isset($params['max']) && count($value) > $params['max']) {
return false;
}
$min = isset($params['min']) ? $params['min'] : 0;
if (isset($params['step']) && (count($value) - $min) % $params['step'] == 0) {
return false;
}
}
$options = isset($field['options']) ? array_keys($field['options']) : array();
$values = isset($field['use']) && $field['use'] == 'keys' ? array_keys($value) : $value;
if ($options && array_diff($values, $options)) {
return false;
}
return true;
}
protected static function filterArray($value, $params, $field)
{
$values = (array) $value;
$options = isset($field['options']) ? array_keys($field['options']) : array();
$multi = isset($field['multiple']) ? $field['multiple'] : false;
if (count($values) == 1 && isset($values[0]) && $values[0] == '') {
return null;
}
if ($options) {
$useKey = isset($field['use']) && $field['use'] == 'keys';
foreach ($values as $key => $value) {
$values[$key] = $useKey ? (bool) $value : $value;
}
}
if ($multi) {
foreach ($values as $key => $value) {
if (is_array($value)) {
$value = implode(',', $value);
$values[$key] = array_map('trim', explode(',', $value));
} else {
$values[$key] = trim($value);
}
}
}
if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) {
foreach ($values as $key => $value) {
foreach ($value as $inner_key => $inner_value) {
if ($inner_value == '') {
unset($value[$inner_key]);
}
}
$values[$key] = $value;
}
}
return $values;
}
public static function typeList($value, array $params, array $field)
{
if (!is_array($value)) {
return false;
}
if (isset($field['fields'])) {
foreach ($value as $key => $item) {
foreach ($field['fields'] as $subKey => $subField) {
$subKey = trim($subKey, '.');
$subValue = isset($item[$subKey]) ? $item[$subKey] : null;
self::validate($subValue, $subField);
}
}
}
return true;
}
protected static function filterList($value, array $params, array $field)
{
return (array) $value;
}
public static function filterYaml($value, $params)
{
if (!is_string($value)) {
return $value;
}
return (array) Yaml::parse($value);
}
/**
* Custom input: ignore (will not validate)
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeIgnore($value, array $params, array $field)
{
return true;
}
public static function filterIgnore($value, array $params, array $field)
{
return $value;
}
// HTML5 attributes (min, max and range are handled inside the types)
public static function validateRequired($value, $params)
{
if (is_scalar($value)) {
return (bool) $params !== true || $value !== '';
} else {
return (bool) $params !== true || !empty($value);
}
}
public static function validatePattern($value, $params)
{
return (bool) preg_match("`^{$params}$`u", $value);
}
// Internal types
public static function validateAlpha($value, $params)
{
return ctype_alpha($value);
}
public static function validateAlnum($value, $params)
{
return ctype_alnum($value);
}
public static function typeBool($value, $params)
{
return is_bool($value) || $value == 1 || $value == 0;
}
public static function validateBool($value, $params)
{
return is_bool($value) || $value == 1 || $value == 0;
}
protected static function filterBool($value, $params)
{
return (bool) $value;
}
public static function validateDigit($value, $params)
{
return ctype_digit($value);
}
public static function validateFloat($value, $params)
{
return is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
}
protected static function filterFloat($value, $params)
{
return (float) $value;
}
public static function validateHex($value, $params)
{
return ctype_xdigit($value);
}
public static function validateInt($value, $params)
{
return is_numeric($value) && (int) $value == $value;
}
protected static function filterInt($value, $params)
{
return (int) $value;
}
public static function validateArray($value, $params)
{
return is_array($value)
|| ($value instanceof \ArrayAccess
&& $value instanceof \Traversable
&& $value instanceof \Countable);
}
public static function filterItem_List($value, $params)
{
return array_values(array_filter($value, function($v) { return !empty($v); } ));
}
public static function validateJson($value, $params)
{
return (bool) (@json_decode($value));
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* @package Grav.Common.Data
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Data;
use Grav\Common\Grav;
class ValidationException extends \RuntimeException
{
protected $messages = [];
public function setMessages(array $messages = []) {
$this->messages = $messages;
$language = Grav::instance()['language'];
$this->message = $language->translate('FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message;
foreach ($messages as $variable => &$list) {
$list = array_unique($list);
foreach ($list as $message) {
$this->message .= "<br/>$message";
}
}
return $this;
}
public function getMessages()
{
return $this->messages;
}
}