Merge branch 'release/1.6.7'

This commit is contained in:
Andy Miller
2019-04-22 15:20:18 -06:00
14 changed files with 295 additions and 51 deletions

View File

@@ -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

View File

@@ -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();

View File

@@ -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', '/');

View File

@@ -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;
});

View File

@@ -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;
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* @package Grav\Common\Helpers
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Helpers;
use Grav\Common\Grav;
use RocketTheme\Toolbox\File\MarkdownFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Symfony\Component\Yaml\Yaml;
class YamlLinter
{
public static function lint()
{
$errors = static::lintConfig();
$errors = $errors + static::lintPages();
return $errors;
}
public static function lintPages()
{
return static::recurseFolder('page://');
}
public static function lintConfig()
{
return static::recurseFolder('config://');
}
public static function recurseFolder($path, $extensions = 'md|yaml')
{
$lint_errors = [];
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
if ($locator->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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* @package Grav\Console\Cli
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Cli;
use Grav\Common\Grav;
use Grav\Common\Helpers\LogViewer;
use Grav\Common\Helpers\YamlLinter;
use Grav\Common\Utils;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Style\SymfonyStyle;
class YamlLinterCommand extends ConsoleCommand
{
protected function configure()
{
$this
->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("<yellow>$path</yellow> - $error");
}
}
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -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);

View File

@@ -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");
}

View File

@@ -140,6 +140,11 @@ interface FormInterface extends RenderInterface, \Serializable
*/
public function isValid(): bool;
/**
* @return string
*/
public function getError(): ?string;
/**
* @return array
*/

View File

@@ -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'] ?? [];
}