Merge branch '1.6' of github.com:getgrav/grav into 1.6

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Andy Miller
2019-03-29 11:31:32 -06:00
16 changed files with 417 additions and 111 deletions

View File

@@ -1,11 +1,16 @@
# v1.6.0-rc.5
## mm/dd/2019
1. [](#new)
* Added `FlexObjectInterface::getDefaultValue()` and `FormInterface::getDefaultValue()`
1. [](#improved)
* Added `Content-Type: application/json` body support for PSR-7 `ServerRequest`
* Remove PHP time limit in `ZipArchive`
* DebugBar: Resolve twig templates in deprecated backtraces in order to help locating Twig issues
1. [](#bugfix)
* Fixed `Undefined method closure::fields()` when getting avatar for user, thanks @Romarain [#2422](https://github.com/getgrav/grav/issues/2422)
* Grav 1.6: Added `Flex[Class]::getFlexType()` to all flex classes to have the interface for all Flex classes
* FlexObjects: Remove null values during save
# v1.6.0-rc.4
## 03/20/2019

View File

@@ -25,6 +25,9 @@ class Blueprint extends BlueprintForm
/** @var BlueprintSchema */
protected $blueprintSchema;
/** @var array */
protected $defaults;
public function setScope($scope)
{
$this->scope = $scope;
@@ -56,7 +59,11 @@ class Blueprint extends BlueprintForm
{
$this->initInternals();
return $this->blueprintSchema->getDefaults();
if (null === $this->defaults) {
$this->defaults = $this->blueprintSchema->getDefaults();
}
return $this->defaults;
}
/**
@@ -174,6 +181,7 @@ class Blueprint extends BlueprintForm
$this->blueprintSchema->embed('', $this->items);
$this->blueprintSchema->init();
$this->defaults = null;
}
}

View File

@@ -21,6 +21,9 @@ use DebugBar\DebugBar;
use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
use Grav\Common\Config\Config;
use Grav\Common\Processors\ProcessorInterface;
use Twig\Template;
use Twig\TemplateWrapper;
class Debugger
{
@@ -44,7 +47,7 @@ class Debugger
/** @var array */
protected $timers = [];
/** @var string[] $deprecations */
/** @var array $deprecations */
protected $deprecations = [];
/** @var callable */
@@ -356,57 +359,183 @@ class Debugger
return true;
}
$backtrace = debug_backtrace(false);
// Figure out error scope from the error.
$scope = 'unknown';
if (stripos($errstr, 'grav') !== false) {
$scope = 'grav';
} elseif (strpos($errfile, '/twig/') !== false) {
$scope = 'twig';
} elseif (stripos($errfile, '/yaml/') !== false) {
$scope = 'yaml';
} elseif (strpos($errfile, '/vendor/') !== false) {
$scope = 'vendor';
}
// Clean up backtrace to make it more useful.
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
// Skip current call.
array_shift($backtrace);
// Find yaml file where the error happened.
if ($scope === 'yaml') {
foreach ($backtrace as $current) {
if (isset($current['args'])) {
foreach ($current['args'] as $arg) {
if ($arg instanceof \SplFileInfo) {
$arg = $arg->getPathname();
}
if (\is_string($arg) && preg_match('/.+\.(yaml|md)$/i', $arg)) {
$errfile = $arg;
$errline = 0;
break 2;
}
}
}
}
}
// Filter arguments.
$cut = 0;
$previous = null;
foreach ($backtrace as $i => &$current) {
if (isset($current['args'])) {
$args = [];
foreach ($current['args'] as $arg) {
if (\is_string($arg)) {
$arg = "'" . $arg . "'";
if (mb_strlen($arg) > 100) {
$arg = 'string';
}
} elseif (\is_bool($arg)) {
$arg = $arg ? 'true' : 'false';
} elseif (\is_scalar($arg)) {
$arg = $arg;
} elseif (\is_object($arg)) {
$arg = get_class($arg) . ' $object';
} elseif (\is_array($arg)) {
$arg = '$array';
} else {
$arg = '$object';
}
$args[] = $arg;
}
$current['args'] = $args;
}
$object = $current['object'] ?? null;
unset($current['object']);
$reflection = null;
if ($object instanceof TemplateWrapper) {
$reflection = new \ReflectionObject($object);
$property = $reflection->getProperty('template');
$property->setAccessible(true);
$object = $property->getValue($object);
}
if ($object instanceof Template) {
$file = $current['file'] ?? null;
if (preg_match('`(Template.php|TemplateWrapper.php)$`', $file)) {
$current = null;
continue;
}
$debugInfo = $object->getDebugInfo();
$line = 1;
if (!$reflection) {
foreach ($debugInfo as $codeLine => $templateLine) {
if ($codeLine <= $current['line']) {
$line = $templateLine;
break;
}
}
}
$src = $object->getSourceContext();
//$code = preg_split('/\r\n|\r|\n/', $src->getCode());
//$current['twig']['twig'] = trim($code[$line - 1]);
$current['twig']['file'] = $src->getPath();
$current['twig']['line'] = $line;
$prevFile = $previous['file'] ?? null;
if ($prevFile && $file === $prevFile) {
$prevLine = $previous['line'];
$line = 1;
foreach ($debugInfo as $codeLine => $templateLine) {
if ($codeLine <= $prevLine) {
$line = $templateLine;
break;
}
}
//$previous['twig']['twig'] = trim($code[$line - 1]);
$previous['twig']['file'] = $src->getPath();
$previous['twig']['line'] = $line;
}
$cut = $i;
} elseif ($object instanceof ProcessorInterface) {
$cut = $cut ?: $i;
break;
}
$previous = &$backtrace[$i];
}
unset($current);
if ($cut) {
$backtrace = array_slice($backtrace, 0, $cut + 1);
}
$backtrace = array_values(array_filter($backtrace));
// Skip vendor libraries and the method where error was triggered.
while ($current = array_shift($backtrace)) {
if (isset($current['file']) && strpos($current['file'], 'vendor') !== false) {
foreach ($backtrace as $i => $current) {
if (!isset($current['file'])) {
continue;
}
if (strpos($current['file'], '/vendor/') !== false) {
$cut = $i + 1;
continue;
}
if (isset($current['function']) && ($current['function'] === 'user_error' || $current['function'] === 'trigger_error')) {
$current = array_shift($backtrace);
$cut = $i + 1;
continue;
}
break;
}
// Add back last call.
array_unshift($backtrace, $current);
// Filter arguments.
foreach ($backtrace as &$current) {
if (isset($current['args'])) {
$args = [];
foreach ($current['args'] as $arg) {
if (\is_string($arg)) {
$args[] = "'" . $arg . "'";
} elseif (\is_bool($arg)) {
$args[] = $arg ? 'true' : 'false';
} elseif (\is_scalar($arg)) {
$args[] = $arg;
} elseif (\is_object($arg)) {
$args[] = get_class($arg) . ' $object';
} elseif (\is_array($arg)) {
$args[] = '$array';
} else {
$args[] = '$object';
}
}
$current['args'] = $args;
}
if ($cut) {
$backtrace = array_slice($backtrace, $cut);
}
unset($current);
$backtrace = array_values(array_filter($backtrace));
$this->deprecations[] = [
$current = reset($backtrace);
// If the issue happened inside twig file, change the file and line to match that file.
$file = $current['twig']['file'] ?? '';
if ($file) {
$errfile = $file;
$errline = $current['twig']['line'] ?? 0;
}
$deprecation = [
'scope' => $scope,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'trace' => $backtrace,
'count' => 1
];
$this->deprecations[] = $deprecation;
// Do not pass forward.
return true;
}
@@ -431,38 +560,37 @@ class Debugger
protected function getDepracatedMessage($deprecated)
{
$scope = 'unknown';
if (stripos($deprecated['message'], 'grav') !== false) {
$scope = 'grav';
} elseif (!isset($deprecated['file'])) {
$scope = 'unknown';
} elseif (stripos($deprecated['file'], 'twig') !== false) {
$scope = 'twig';
} elseif (stripos($deprecated['file'], 'yaml') !== false) {
$scope = 'yaml';
} elseif (stripos($deprecated['file'], 'vendor') !== false) {
$scope = 'vendor';
}
$scope = $deprecated['scope'];
$trace = [];
foreach ($deprecated['trace'] as $current) {
$class = $current['class'] ?? '';
$type = $current['type'] ?? '';
$function = $this->getFunction($current);
if (isset($current['file'])) {
$current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
if (isset($deprecated['trace'])) {
foreach ($deprecated['trace'] as $current) {
$class = $current['class'] ?? '';
$type = $current['type'] ?? '';
$function = $this->getFunction($current);
if (isset($current['file'])) {
$current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
}
unset($current['class'], $current['type'], $current['function'], $current['args']);
if (isset($current['twig'])) {
$trace[] = $current['twig'];
} else {
$trace[] = ['call' => $class . $type . $function] + $current;
}
}
unset($current['class'], $current['type'], $current['function'], $current['args']);
$trace[] = ['call' => $class . $type . $function] + $current;
}
$array = [
'message' => $deprecated['message'],
'file' => $deprecated['file'],
'line' => $deprecated['line'],
'trace' => $trace
];
return [
[
'message' => $deprecated['message'],
'trace' => $trace
],
array_filter($array),
$scope
];
}
@@ -473,6 +601,6 @@ class Debugger
return '';
}
return $trace['function'] . '(' . implode(', ', $trace['args']) . ')';
return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
}
}

View File

@@ -161,9 +161,9 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
* @param string|null $separator
* @return mixed
*/
public function value($name, $default = null, $separator = null)
public function getFormValue(string $name, $default = null, string $separator = null)
{
$value = parent::value($name, null, $separator);
$value = parent::getFormValue($name, null, $separator);
if ($name === 'avatar') {
return $this->parseFileProperty($value);

View File

@@ -63,7 +63,7 @@ class Flex implements \Countable
*/
public function addDirectory(FlexDirectory $directory)
{
$this->types[$directory->getType()] = $directory;
$this->types[$directory->getFlexType()] = $directory;
return $this;
}

View File

@@ -87,7 +87,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
parent::__construct($entries);
if ($directory) {
$this->setFlexDirectory($directory)->setKey($directory->getType());
$this->setFlexDirectory($directory)->setKey($directory->getFlexType());
}
}
@@ -129,7 +129,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
*/
public function getFlexType(): string
{
return $this->_flexDirectory->getType();
return $this->_flexDirectory->getFlexType();
}
/**
@@ -245,7 +245,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
* {@inheritdoc}
* @see FlexCollectionInterface::render()
*/
public function render($layout = null, array $context = [])
public function render(string $layout = null, array $context = [])
{
if (null === $layout) {
$layout = 'default';

View File

@@ -91,8 +91,19 @@ class FlexDirectory implements FlexAuthorizeInterface
/**
* @return string
* @deprecated 1.6 Use ->getFlexType() method instead.
*/
public function getType(): string
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED);
return $this->type;
}
/**
* @return string
*/
public function getFlexType(): string
{
return $this->type;
}
@@ -102,7 +113,7 @@ class FlexDirectory implements FlexAuthorizeInterface
*/
public function getTitle(): string
{
return $this->getBlueprintInternal()->get('title', ucfirst($this->getType()));
return $this->getBlueprintInternal()->get('title', ucfirst($this->getFlexType()));
}
/**
@@ -312,13 +323,13 @@ class FlexDirectory implements FlexAuthorizeInterface
if (Utils::isAdminPlugin()) {
$key = substr($key, 0, -1);
}
$cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getType() . $key, $timeout);
$cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getFlexType() . $key, $timeout);
} catch (\Exception $e) {
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];
$debugger->addException($e);
$cache = new MemoryCache('flex-objects-' . $this->getType());
$cache = new MemoryCache('flex-objects-' . $this->getFlexType());
}
// Disable cache key validation.

View File

@@ -45,7 +45,7 @@ class FlexForm implements FlexFormInterface
{
$this->name = $name;
$this->form = $form;
$uniqueId = $object->exists() ? $object->getStorageKey() : "{$object->getType()}:new";
$uniqueId = $object->exists() ? $object->getStorageKey() : "{$object->getFlexType()}:new";
$this->setObject($object);
$this->setId($this->getName());
$this->setUniqueId(md5($uniqueId));
@@ -73,7 +73,7 @@ class FlexForm implements FlexFormInterface
$object = $this->getObject();
$name = $this->name ?: 'object';
return "flex-{$object->getType()}-{$name}";
return "flex-{$object->getFlexType()}-{$name}";
}
/**
@@ -101,6 +101,26 @@ class FlexForm implements FlexFormInterface
return $value ?? $this->getObject()->value($name);
}
public function getDefaultValue(string $name)
{
return $this->object->getDefaultValue($name);
}
/**
* @return array
*/
public function getDefaultValues(): array
{
return $this->object->getDefaultValues();
}
/**
* @return string
*/
public function getFlexType(): string
{
return $this->object->getFlexType();
}
/**
* @return FlexObjectInterface
*/

View File

@@ -104,7 +104,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
*/
public function getFlexType(): string
{
return $this->_flexDirectory->getType();
return $this->_flexDirectory->getFlexType();
}
/**
@@ -171,7 +171,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
{
// Get storage keys for the objects.
$keys = [];
$type = $this->_flexDirectory->getType() . '.obj:';
$type = $this->_flexDirectory->getFlexType() . '.obj:';
foreach ($this->getEntries() as $key => $value) {
$keys[$key] = $value['flex_key'] ?? $type . $value['storage_key'];
@@ -191,7 +191,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
return $this;
}
$type = $keyField === 'flex_key' ? $this->_flexDirectory->getType() . '.obj:' : '';
$type = $keyField === 'flex_key' ? $this->_flexDirectory->getFlexType() . '.obj:' : '';
$entries = [];
foreach ($this->getEntries() as $key => $value) {
if (!isset($value['key'])) {
@@ -221,7 +221,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
* {@inheritdoc}
* @see FlexCollectionInterface::render()
*/
public function render($layout = null, array $context = [])
public function render(string $layout = null, array $context = [])
{
return $this->__call('render', [$layout, $context]);
}

View File

@@ -115,7 +115,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
*/
public function getFlexType(): string
{
return $this->_flexDirectory->getType();
return $this->_flexDirectory->getFlexType();
}
/**
@@ -186,7 +186,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
*/
public function getFlexKey(): string
{
return $this->_storage['flex_key'] ?? $this->_flexDirectory->getType() . '.obj:' . $this->getStorageKey();
return $this->_storage['flex_key'] ?? $this->_flexDirectory->getFlexType() . '.obj:' . $this->getStorageKey();
}
/**
@@ -357,7 +357,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
* {@inheritdoc}
* @see FlexObjectInterface::render()
*/
public function render($layout = null, array $context = [])
public function render(string $layout = null, array $context = [])
{
if (null === $layout) {
$layout = 'default';
@@ -616,9 +616,50 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
/**
* {@inheritdoc}
* @see FlexObjectInterface::value()
* @see FlexObjectInterface::getDefaultValue()
*/
public function value($name, $default = null, $separator = null)
public function getDefaultValue(string $name, string $separator = null)
{
$separator = $separator ?: '.';
$path = explode($separator, $name) ?: [];
$offset = array_shift($path) ?? '';
$current = $this->getDefaultValues();
if (!isset($current[$offset])) {
return null;
}
$current = $current[$offset];
while ($path) {
$offset = array_shift($path);
if ((\is_array($current) || $current instanceof \ArrayAccess) && isset($current[$offset])) {
$current = $current[$offset];
} elseif (\is_object($current) && isset($current->{$offset})) {
$current = $current->{$offset};
} else {
return null;
}
};
return $current;
}
/**
* @return array
*/
public function getDefaultValues(): array
{
return $this->getBlueprint()->getDefaults();
}
/**
* {@inheritdoc}
* @see FlexObjectInterface::getFormValue()
*/
public function getFormValue(string $name, $default = null, string $separator = null)
{
if ($name === 'storage_key') {
return $this->getStorageKey();
@@ -630,6 +671,19 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
return $this->getNestedProperty($name, $default, $separator);
}
/**
* @param string $name
* @param mixed|null $default
* @param string|null $separator
* @return mixed
*
* @deprecated 1.6 Use ->getFormValue() method instead.
*/
public function value($name, $default = null, $separator = null)
{
return $this->getFormValue($name, $default, $separator);
}
/**
* Returns a string representation of this object.
*

View File

@@ -11,11 +11,8 @@ declare(strict_types=1);
namespace Grav\Framework\Flex\Interfaces;
use Grav\Framework\ContentBlock\ContentBlockInterface;
use Grav\Framework\ContentBlock\HtmlBlock;
use Grav\Framework\Flex\FlexDirectory;
use Twig\Error\LoaderError;
use Twig\Error\SyntaxError;
use Grav\Framework\Interfaces\RenderInterface;
/**
* Defines common interface shared with both Flex Objects and Collections.
@@ -23,7 +20,7 @@ use Twig\Error\SyntaxError;
* @used-by \Grav\Framework\Flex\FlexObject
* @since 1.6
*/
interface FlexCommonInterface
interface FlexCommonInterface extends RenderInterface
{
/**
* Get Flex Type of the object / collection.
@@ -64,22 +61,4 @@ interface FlexCommonInterface
* @return string Returns cache checksum.
*/
public function getCacheChecksum(): string;
/**
* Renders the object / collection.
*
* @example {% render object layout 'edit' with { limited: true } %}
* @example {% render collection layout 'list' %}
*
* @param string $layout Layout name.
* @param array $context Context given to the renderer.
*
* @return ContentBlockInterface|HtmlBlock Returns `HtmlBlock` containing the rendered output.
* @throws \Exception
* @throws \Throwable
* @throws LoaderError
* @throws SyntaxError
* @api
*/
public function render($layout = null, array $context = []);
}

View File

@@ -129,7 +129,7 @@ interface FlexObjectInterface extends FlexCommonInterface, NestedObjectInterface
* @throws \RuntimeException if object already exists.
* @api
*/
public function create($key = null);
public function create(string $key = null);
/**
* Save object into the storage.
@@ -173,12 +173,36 @@ interface FlexObjectInterface extends FlexCommonInterface, NestedObjectInterface
public function getForm(string $name = '', array $form = null);
/**
* Form field compatibility.
* Returns default value suitable to be used in a form for the given property.
*
* @param string $name Property name.
* @param mixed $default Default value.
* @param string $separator Optional nested property separator.
* @return mixed Returns value of the field.
* @see FlexObjectInterface::getForm()
*
* @param string $name Property name.
* @param string $separator Optional nested property separator.
*
* @return mixed|null Returns default value of the field, null if there is no default value.
*/
public function value($name, $default = null, $separator = null);
public function getDefaultValue(string $name, string $separator = null);
/**
* Returns default values suitable to be used in a form for the given property.
*
* @see FlexObjectInterface::getForm()
*
* @return array Returns default values.
*/
public function getDefaultValues(): array;
/**
* Returns raw value suitable to be used in a form for the given property.
*
* @see FlexObjectInterface::getForm()
*
* @param string $name Property name.
* @param mixed $default Default value.
* @param string $separator Optional nested property separator.
*
* @return mixed Returns value of the field.
*/
public function getFormValue(string $name, $default = null, string $separator = null);
}

View File

@@ -113,6 +113,42 @@ trait FormTrait
return $this->data[$name] ?? null;
}
public function getDefaultValue(string $name)
{
$path = explode('.', $name) ?: [];
$offset = array_shift($path) ?? '';
$current = $this->getDefaultValues();
if (!isset($current[$offset])) {
return null;
}
$current = $current[$offset];
while ($path) {
$offset = array_shift($path);
if ((\is_array($current) || $current instanceof \ArrayAccess) && isset($current[$offset])) {
$current = $current[$offset];
} elseif (\is_object($current) && isset($current->{$offset})) {
$current = $current->{$offset};
} else {
return null;
}
};
return $current;
}
/**
* @return array
*/
public function getDefaultValues(): array
{
return $this->getBlueprint()->getDefaults();
}
/**
* @param ServerRequestInterface $request
* @return FormInterface|$this

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Framework\Interfaces
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Interfaces;
use Grav\Framework\ContentBlock\ContentBlockInterface;
use Grav\Framework\ContentBlock\HtmlBlock;
/**
* Defines common interface to render any object.
*
* @used-by \Grav\Framework\Flex\FlexObject
* @since 1.6
*/
interface RenderInterface
{
/**
* Renders the object.
*
* @example $block = $object->render('custom', ['variable' => 'value']);
* @example {% render object layout 'custom' with { variable: 'value' } %}
*
* @param string|null $layout Layout to be used.
* @param array|null $context Extra context given to the renderer.
*
* @return ContentBlockInterface|HtmlBlock Returns `HtmlBlock` containing the rendered output.
* @api
*/
public function render(string $layout = null, array $context = []);
}

View File

@@ -95,7 +95,7 @@ trait ArrayPropertyTrait
*/
protected function getElements()
{
return $this->_elements;
return array_filter($this->_elements, function ($val) { return $val !== null; });
}
/**

View File

@@ -183,7 +183,10 @@ trait ObjectPropertyTrait
$elements = [];
foreach ($properties as $offset => $value) {
$elements[$offset] = $this->offsetSerialize($offset, $value);
$serialized = $this->offsetSerialize($offset, $value);
if ($serialized !== null) {
$elements[$offset] = $this->offsetSerialize($offset, $value);
}
}
return $elements;