diff --git a/CHANGELOG.md b/CHANGELOG.md index 258c9c3ee..e98e25ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# v1.6.7 +## 04/22/2019 + +1. [](#new) + * Added a new `bin/grav yamllinter` CLI command to find YAML Linting issues [#2468](https://github.com/getgrav/grav/issues/2468#issuecomment-485151681) +1. [](#improved) + * Improve `FormTrait` backwards compatibility with existing forms + * Added a new `Utils::getSubnet()` function for IPv4/IPv6 parsing [#2465](https://github.com/getgrav/grav/pull/2465) +1. [](#bugfix) + * Remove disabled fields from the form schema + * Fix issue when excluding `inlineJs` and `inlineCss` from Assets pipeline [#2468](https://github.com/getgrav/grav/issues/2468) + * Fix for manually set position on external URLs [#2470](https://github.com/getgrav/grav/issues/2470) + # v1.6.6 ## 04/17/2019 diff --git a/bin/grav b/bin/grav index 2a81d4c51..7120218c0 100755 --- a/bin/grav +++ b/bin/grav @@ -3,6 +3,7 @@ use Grav\Common\Composer; use Grav\Common\Grav; +use League\CLImate\CLImate; use Symfony\Component\Console\Application; \define('GRAV_CLI', true); @@ -24,7 +25,22 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) { exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req)); } -Grav::instance(array('loader' => $autoload)); +$climate = new League\CLImate\CLImate; +$climate->arguments->add([ + 'environment' => [ + 'prefix' => 'e', + 'longPrefix' => 'env', + 'description' => 'Configuration Environment', + 'defaultValue' => 'localhost' + ] +]); +$climate->arguments->parse(); + +// Set up environment based on params. +$environment = $climate->arguments->get('environment'); + +$grav = Grav::instance(array('loader' => $autoload)); +$grav->setup($environment); if (!ini_get('date.timezone')) { date_default_timezone_set('UTC'); @@ -46,5 +62,6 @@ $app->addCommands(array( new \Grav\Console\Cli\SchedulerCommand(), new \Grav\Console\Cli\SecurityCommand(), new \Grav\Console\Cli\LogViewerCommand(), + new \Grav\Console\Cli\YamlLinterCommand(), )); $app->run(); diff --git a/system/defines.php b/system/defines.php index d9152c547..35917b0f5 100644 --- a/system/defines.php +++ b/system/defines.php @@ -8,7 +8,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.6.6'); +define('GRAV_VERSION', '1.6.7'); define('GRAV_TESTING', false); define('DS', '/'); diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php index af54b26bb..74ba296d0 100644 --- a/system/src/Grav/Common/Assets.php +++ b/system/src/Grav/Common/Assets.php @@ -172,7 +172,8 @@ class Assets extends PropertyObject // If pipeline disabled, set to position if provided, else after if (isset($options['pipeline'])) { if ($options['pipeline'] === false) { - $excludes = strtolower($type . '_pipeline_before_excludes'); + $exclude_type = ($type === $this::JS_TYPE || $type === $this::INLINE_JS_TYPE) ? $this::JS_TYPE : $this::CSS_TYPE; + $excludes = strtolower($exclude_type . '_pipeline_before_excludes'); if ($this->{$excludes}) { $default = 'after'; } else { @@ -271,7 +272,7 @@ class Assets extends PropertyObject $type = $asset->getType(); - if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false) { + if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline' ) { if ($this->{$type . '_pipeline_before_excludes'}) { $asset->setPosition('after'); } else { @@ -281,6 +282,7 @@ class Assets extends PropertyObject } } + if ($asset[$key] === $value) return true; return false; }); diff --git a/system/src/Grav/Common/Data/BlueprintSchema.php b/system/src/Grav/Common/Data/BlueprintSchema.php index 0c954fa33..898450d28 100644 --- a/system/src/Grav/Common/Data/BlueprintSchema.php +++ b/system/src/Grav/Common/Data/BlueprintSchema.php @@ -142,7 +142,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface if ($rule) { // Item has been defined in blueprints. - if (!empty($rule['validate']['ignore'])) { + if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) { // Skip validation in the ignored field. continue; } @@ -178,7 +178,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface $val = $rules[$key] ?? $rules['*'] ?? null; $rule = \is_string($val) ? $this->items[$val] : null; - if (empty($rule['validate']['ignore'])) { + if (empty($rule['disabled']) && empty($rule['validate']['ignore'])) { continue; } } @@ -191,7 +191,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface if ($rule) { // Item has been defined in blueprints. - if (!empty($rule['validate']['ignore'])) { + if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) { // Skip any data in the ignored field. unset($results[$key]); continue; @@ -240,6 +240,8 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface if ( // Not an input field !$field + // Field has been disabled + || !empty($field['disabled']) // Field validation is set to be ignored || !empty($field['validate']['ignore']) // Field is toggleable and the toggle is turned off @@ -273,7 +275,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface $field = $this->items[$field]; // Skip ignored field, it will not be required. - if (!empty($field['validate']['ignore'])) { + if (!empty($field['disabled']) || !empty($field['validate']['ignore'])) { continue; } diff --git a/system/src/Grav/Common/Helpers/YamlLinter.php b/system/src/Grav/Common/Helpers/YamlLinter.php new file mode 100644 index 000000000..43bde820b --- /dev/null +++ b/system/src/Grav/Common/Helpers/YamlLinter.php @@ -0,0 +1,76 @@ +isStream($path)) { + $directory = $locator->getRecursiveIterator($path, $flags); + } else { + $directory = new \RecursiveDirectoryIterator($path, $flags); + } + $recursive = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST); + $iterator = new \RegexIterator($recursive, '/^.+\.'.$extensions.'$/i'); + + /** @var \RecursiveDirectoryIterator $file */ + foreach ($iterator as $filepath => $file) { + try { + Yaml::parse(static::extractYaml($filepath)); + } catch (\Exception $e) { + $lint_errors[str_replace(GRAV_ROOT, '', $filepath)] = $e->getMessage(); + } + } + + return $lint_errors; + } + + protected static function extractYaml($path) + { + $extension = pathinfo($path, PATHINFO_EXTENSION); + if ($extension === 'md') { + $file = MarkdownFile::instance($path); + $contents = $file->frontmatter(); + } else { + $contents = file_get_contents($path); + } + return $contents; + } + +} diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 42f1fbe69..c254009e5 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -1469,4 +1469,44 @@ abstract class Utils return $string; } + + /** + * Find the subnet of an ip with CIDR prefix size + * + * @param string $ip + * @param int $prefix + * + * @return string + * @throws \InvalidArgumentException if provided an invalid IP + */ + public static function getSubnet($ip, $prefix = 64) + { + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + throw new \InvalidArgumentException('Invalid IP: ' . $ip); + } + + // Packed representation of IP + $ip = inet_pton($ip); + + // Maximum netmask length = same as packed address + $len = 8*strlen($ip); + if ($prefix > $len) $prefix = $len; + + $mask = str_repeat('f', $prefix>>2); + + switch($prefix & 3) + { + case 3: $mask .= 'e'; break; + case 2: $mask .= 'c'; break; + case 1: $mask .= '8'; break; + } + $mask = str_pad($mask, $len>>2, '0'); + + // Packed representation of netmask + $mask = pack('H*', $mask); + // Bitwise - Take all bits that are both 1 to generate subnet + $subnet = inet_ntop($ip & $mask); + + return $subnet; + } } diff --git a/system/src/Grav/Console/Cli/YamlLinterCommand.php b/system/src/Grav/Console/Cli/YamlLinterCommand.php new file mode 100644 index 000000000..18cdb493f --- /dev/null +++ b/system/src/Grav/Console/Cli/YamlLinterCommand.php @@ -0,0 +1,73 @@ +setName('yamllinter') + ->addOption( + 'env', + 'e', + InputOption::VALUE_OPTIONAL, + 'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com' + ) + ->setDescription('Checks various files for YAML errors') + ->setHelp("Checks various files for YAML errors"); + } + + protected function serve() + { + $grav = Grav::instance(); + $grav->setup(); + + $io = new SymfonyStyle($this->input, $this->output); + + $io->title('Yaml Linter'); + + $io->section('User Configuration'); + $errors = YamlLinter::lintConfig(); + + if (empty($errors)) { + $io->success('No YAML Linting issues with configuration'); + } else { + $this->displayErrors($errors, $io); + } + + + $io->section('Pages Frontmatter'); + $errors = YamlLinter::lintPages(); + + if (empty($errors)) { + $io->success('No YAML Linting issues with pages'); + } else { + $this->displayErrors($errors, $io); + } + + } + + protected function displayErrors($errors, $io) + { + $io->error("YAML Linting issues found..."); + foreach ($errors as $path => $error) { + $io->writeln("$path - $error"); + } + } +} diff --git a/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php index 2d0fe40bd..ea502dc4c 100644 --- a/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php +++ b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php @@ -27,11 +27,7 @@ class MemoryCache extends AbstractCache public function doGet($key, $miss) { - if (!array_key_exists($key, $this->cache)) { - return $miss; - } - - return $this->cache[$key]; + return $this->cache[$key] ?? $miss; } public function doSet($key, $value, $ttl) diff --git a/system/src/Grav/Framework/Flex/FlexForm.php b/system/src/Grav/Framework/Flex/FlexForm.php index e3b9b42fa..7faad5227 100644 --- a/system/src/Grav/Framework/Flex/FlexForm.php +++ b/system/src/Grav/Framework/Flex/FlexForm.php @@ -54,7 +54,7 @@ class FlexForm implements FlexFormInterface $this->setObject($object); $this->setId($this->getName()); $this->setUniqueId(md5($uniqueId)); - $this->errors = []; + $this->messages = []; $this->submitted = false; $flash = $this->getFlash(); diff --git a/system/src/Grav/Framework/Flex/FlexIndex.php b/system/src/Grav/Framework/Flex/FlexIndex.php index e4c27e974..5ad95a477 100644 --- a/system/src/Grav/Framework/Flex/FlexIndex.php +++ b/system/src/Grav/Framework/Flex/FlexIndex.php @@ -386,10 +386,6 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde $cached = $result; } - if ($cached === null) { - throw new \RuntimeException('Flex: Internal error'); - } - $cache->set($key, $cached); } catch (InvalidArgumentException $e) { $debugger->addException($e); diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php index 356bd0425..0520aaf6a 100644 --- a/system/src/Grav/Framework/Flex/FlexObject.php +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -730,8 +730,8 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface $grav = Grav::instance(); /** @var Flex $flex */ - $flex = $grav['flex_directory']; - $directory = $flex->getDirectory($type); + $flex = $grav['flex_objects'] ?? null; + $directory = $flex ? $flex->getDirectory($type) : null; if (!$directory) { throw new \InvalidArgumentException("Cannot unserialize '{$type}': Not found"); } diff --git a/system/src/Grav/Framework/Form/Interfaces/FormInterface.php b/system/src/Grav/Framework/Form/Interfaces/FormInterface.php index 7bfd85fdc..d218e7282 100644 --- a/system/src/Grav/Framework/Form/Interfaces/FormInterface.php +++ b/system/src/Grav/Framework/Form/Interfaces/FormInterface.php @@ -140,6 +140,11 @@ interface FormInterface extends RenderInterface, \Serializable */ public function isValid(): bool; + /** + * @return string + */ + public function getError(): ?string; + /** * @return array */ diff --git a/system/src/Grav/Framework/Form/Traits/FormTrait.php b/system/src/Grav/Framework/Form/Traits/FormTrait.php index 41e22dd24..bcd63bdd4 100644 --- a/system/src/Grav/Framework/Form/Traits/FormTrait.php +++ b/system/src/Grav/Framework/Form/Traits/FormTrait.php @@ -30,6 +30,13 @@ use Twig\TemplateWrapper; */ trait FormTrait { + /** @var string */ + public $status = 'success'; + /** @var string */ + public $message; + /** @var string[] */ + public $messages = []; + /** @var string */ private $name; /** @var string */ @@ -38,8 +45,6 @@ trait FormTrait private $uniqueid; /** @var bool */ private $submitted; - /** @var string[] */ - private $errors; /** @var Data|object|null */ private $data; /** @var array|UploadedFileInterface[] */ @@ -164,12 +169,24 @@ trait FormTrait */ public function handleRequest(ServerRequestInterface $request): FormInterface { + // Set current form to be active. + $grav = Grav::instance(); + $forms = $grav['forms'] ?? null; + if ($forms) { + $forms->setActiveForm($this); + + /** @var Twig $twig */ + $twig = $grav['twig']; + $twig->twig_vars['form'] = $this; + + } + try { [$data, $files] = $this->parseRequest($request); $this->submit($data, $files); } catch (\Exception $e) { - $this->errors[] = $e->getMessage(); + $this->setError($e->getMessage()); } return $this; @@ -191,12 +208,17 @@ trait FormTrait public function isValid(): bool { - return !$this->errors; + return $this->status === 'success'; + } + + public function getError(): ?string + { + return !$this->isValid() ? $this->message : null; } public function getErrors(): array { - return $this->errors; + return !$this->isValid() ? $this->messages : []; } public function isSubmitted(): bool @@ -206,7 +228,7 @@ trait FormTrait public function validate(): bool { - if ($this->errors) { + if (!$this->isValid()) { return false; } @@ -214,19 +236,14 @@ trait FormTrait $this->validateData($this->data); $this->validateUploads($this->getFiles()); } catch (ValidationException $e) { - $list = []; - foreach ($e->getMessages() as $field => $errors) { - $list[] = $errors; - } - $list = array_merge(...$list); - $this->errors = $list; + $this->setErrors($e->getMessages()); } catch (\Exception $e) { - $this->errors[] = $e->getMessage(); + $this->setError($e->getMessage()); } $this->filterData($this->data); - return empty($this->errors); + return $this->isValid(); } /** @@ -252,7 +269,7 @@ trait FormTrait $this->submitted = true; } catch (\Exception $e) { - $this->errors[] = $e->getMessage(); + $this->setError($e->getMessage()); } return $this; @@ -265,7 +282,9 @@ trait FormTrait $this->data = null; $this->files = []; - $this->errors = []; + $this->status = 'success'; + $this->message = null; + $this->messages = []; $this->submitted = false; $this->flash = null; } @@ -310,7 +329,6 @@ trait FormTrait } /** - * Get form flash object. * * @return FormFlash @@ -376,16 +394,6 @@ trait FormTrait $this->flash = null; } - /** - * Set all errors. - * - * @param array $errors - */ - protected function setErrors(array $errors): void - { - $this->errors = array_merge($this->errors, $errors); - } - /** * Set a single error. * @@ -393,7 +401,19 @@ trait FormTrait */ protected function setError(string $error): void { - $this->errors[] = $error; + $this->status = 'error'; + $this->message = $error; + } + + /** + * Set all errors. + * + * @param array $errors + */ + protected function setErrors(array $errors): void + { + $this->status = 'error'; + $this->messages = $errors; } /** @@ -563,7 +583,7 @@ trait FormTrait $value = json_decode($value, true); if ($value === null && json_last_error() !== JSON_ERROR_NONE) { unset($data[$key]); - $this->errors[] = "Badly encoded JSON data (for {$key}) was sent to the form"; + $this->setError("Badly encoded JSON data (for {$key}) was sent to the form"); } } } @@ -583,7 +603,9 @@ trait FormTrait 'id' => $this->id, 'uniqueid' => $this->uniqueid, 'submitted' => $this->submitted, - 'errors' => $this->errors, + 'status' => $this->status, + 'message' => $this->message, + 'messages' => $this->messages, 'data' => $data, 'files' => $this->files, ]; @@ -598,7 +620,9 @@ trait FormTrait $this->id = $data['id']; $this->uniqueid = $data['uniqueid']; $this->submitted = $data['submitted'] ?? false; - $this->errors = $data['errors'] ?? []; + $this->status = $data['status'] ?? 'success'; + $this->message = $data['message'] ?? null; + $this->messages = $data['messages'] ?? []; $this->data = isset($data['data']) ? new Data($data['data'], $this->getBlueprint()) : null; $this->files = $data['files'] ?? []; }