This commit is contained in:
2021-05-12 12:05:01 +02:00
parent ec6c71798f
commit ffb5dbdaae
104 changed files with 6733 additions and 3490 deletions
@@ -90,7 +90,7 @@ trait AssetUtilsTrait
}
$relative_dir = dirname($relative_path);
$link = ROOT_DIR . $relative_path;
$link = GRAV_ROOT . '/' . $relative_path;
}
// TODO: looks like this is not being used.
@@ -252,7 +252,7 @@ trait TestingAssetsTrait
*/
public function addDir($directory, $pattern = self::DEFAULT_REGEX)
{
$root_dir = rtrim(ROOT_DIR, '/');
$root_dir = GRAV_ROOT;
// Check if $directory is a stream.
if (strpos($directory, '://')) {
+2 -1
View File
@@ -238,6 +238,7 @@ class Validation
$value = trim($value);
}
$value = preg_replace("/\r\n|\r/um", "\n", $value);
$len = mb_strlen($value);
$min = (int)($params['min'] ?? 0);
@@ -280,7 +281,7 @@ class Validation
$value = trim($value);
}
return $value;
return preg_replace("/\r\n|\r/um", "\n", $value);
}
/**
@@ -746,6 +746,16 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
return $bool ? $this->select($list) : $this->unselect($list);
}
/**
* @param string|null $languageCode
* @param bool|null $fallback
* @return PageIndex
*/
public function withTranslated(string $languageCode = null, bool $fallback = null)
{
return $this->getIndex()->withTranslated($languageCode, $fallback);
}
/**
* Filter pages by given filters.
*
@@ -18,6 +18,7 @@ use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Flex\Traits\FlexGravTrait;
use Grav\Common\Flex\Traits\FlexIndexTrait;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Header;
use Grav\Common\Page\Interfaces\PageCollectionInterface;
use Grav\Common\Page\Interfaces\PageInterface;
@@ -164,6 +165,31 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $root;
}
/**
* @param string|null $languageCode
* @param bool|null $fallback
* @return PageIndex
*/
public function withTranslated(string $languageCode = null, bool $fallback = null)
{
if (null === $languageCode) {
return $this;
}
$entries = $this->translateEntries($this->getEntries(), $languageCode, $fallback);
$params = ['language' => $languageCode, 'language_fallback' => $fallback] + $this->getParams();
return $this->createFrom($entries)->setParams($params);
}
/**
* @return string|null
*/
public function getLanguage(): ?string
{
return $this->_params['language'] ?? null;
}
/**
* Get the collection params
*
@@ -174,6 +200,17 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $this->_params ?? [];
}
/**
* Get the collection param
*
* @param string $name
* @return mixed
*/
public function getParam(string $name)
{
return $this->_params[$name] ?? null;
}
/**
* Set parameters to the Collection
*
@@ -187,6 +224,20 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $this;
}
/**
* Set a parameter to the Collection
*
* @param string $name
* @param mixed $value
* @return $this
*/
public function setParam(string $name, $value)
{
$this->_params[$name] = $value;
return $this;
}
/**
* Get the collection params
*
@@ -197,6 +248,15 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $this->getParams();
}
/**
* {@inheritdoc}
* @see FlexCollectionInterface::getCacheKey()
*/
public function getCacheKey(): string
{
return $this->getTypePrefix() . $this->getFlexType() . '.' . sha1(json_encode($this->getKeys()) . $this->getKeyField() . $this->getLanguage());
}
/**
* Filter pages by given filters.
*
@@ -345,6 +405,96 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
return $index;
}
/**
* @param array $entries
* @param string $lang
* @param bool|null $fallback
* @return array
*/
protected function translateEntries(array $entries, string $lang, bool $fallback = null): array
{
$languages = $this->getFallbackLanguages($lang, $fallback);
foreach ($entries as $key => &$entry) {
// Find out which version of the page we should load.
$translations = $this->getLanguageTemplates((string)$key);
if (!$translations) {
// No translations found, is this a folder?
continue;
}
// Find a translation.
$template = null;
foreach ($languages as $code) {
if (isset($translations[$code])) {
$template = $translations[$code];
break;
}
}
// We couldn't find a translation, remove entry from the list.
if (!isset($code, $template)) {
unset($entries['key']);
continue;
}
// Get the main key without template and langauge.
[$main_key,] = explode('|', $entry['storage_key'] . '|', 2);
// Update storage key and language.
$entry['storage_key'] = $main_key . '|' . $template . '.' . $code;
$entry['lang'] = $code;
}
unset($entry);
return $entries;
}
/**
* @return array
*/
protected function getLanguageTemplates(string $key): array
{
$meta = $this->getMetaData($key);
$template = $meta['template'] ?? 'folder';
$translations = $meta['markdown'] ?? [];
$list = [];
foreach ($translations as $code => $search) {
if (isset($search[$template])) {
// Use main template if possible.
$list[$code] = $template;
} elseif (!empty($search)) {
// Fall back to first matching template.
$list[$code] = key($search);
}
}
return $list;
}
/**
* @param string|null $languageCode
* @param bool|null $fallback
* @return array
*/
protected function getFallbackLanguages(string $languageCode = null, bool $fallback = null): array
{
$fallback = $fallback ?? true;
if (!$fallback && null !== $languageCode) {
return [$languageCode];
}
$grav = Grav::instance();
/** @var Language $language */
$language = $grav['language'];
$languageCode = $languageCode ?? '';
if ($languageCode === '' && $fallback) {
return $language->getFallbackLanguages(null, true);
}
return $fallback ? $language->getFallbackLanguages($languageCode, true) : [$languageCode];
}
/**
* @param array $options
* @return array
@@ -474,17 +474,28 @@ class PageStorage extends FolderStorage
}
/**
* Check if page folder should be deleted.
*
* Deleting page can be done either by deleting everything or just a single language.
* If key contains the language, delete only it, unless it is the last language.
*
* @param string $key
* @return bool
*/
protected function canDeleteFolder(string $key): bool
{
// Return true if there's no language in the key.
$keys = $this->extractKeysFromStorageKey($key);
if ($keys['lang']) {
return false;
if (!$keys['lang']) {
return true;
}
return true;
// Get the main key and reload meta.
$key = $this->buildStorageKey($keys);
$meta = $this->getObjectMeta($key, true);
// Return true if there aren't any markdown files left.
return empty($meta['markdown'] ?? []);
}
/**
@@ -22,7 +22,6 @@ use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaUploadInterface;
use Grav\Common\Page\Media;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Medium\MediumFactory;
use Grav\Common\User\Access;
use Grav\Common\User\Authentication;
@@ -78,16 +77,12 @@ class UserObject extends FlexObject implements UserInterface, Countable
/** @var array|null */
protected $_uploads_original;
/** @var FileInterface|null */
protected $_storage;
/** @var UserGroupIndex */
protected $_groups;
/** @var Access */
protected $_access;
/** @var array|null */
protected $access;
+1 -1
View File
@@ -527,7 +527,7 @@ class GPM extends Iterator
$plugins = $this->getRepositoryPlugins();
if (null === $themes || null === $plugins) {
if (!is_writable(ROOT_DIR . '/cache/gpm')) {
if (!is_writable(GRAV_ROOT . '/cache/gpm')) {
throw new RuntimeException('The cache/gpm folder is not writable. Please check the folder permissions.');
}
+4 -1
View File
@@ -135,7 +135,10 @@ class Installer
}
if (!$options['sophisticated']) {
if ($options['theme']) {
$isTheme = $options['theme'] ?? false;
// Make sure that themes are always being copied, even if option was not set!
$isTheme = $isTheme || preg_match('|/themes/[^/]+|ui', $install_path);
if ($isTheme) {
self::copyInstall($extracted, $install_path);
} else {
self::moveInstall($extracted, $install_path);
@@ -71,15 +71,6 @@ trait MediaUploadTrait
*/
public function checkUploadedFile(UploadedFileInterface $uploadedFile, string $filename = null, array $settings = null): string
{
// Add the defaults to the settings.
$settings = $this->getUploadSettings($settings);
// Destination is always needed (but it can be set in defaults).
$self = $settings['self'] ?? false;
if (!isset($settings['destination']) && $self === false) {
throw new RuntimeException($this->translate('PLUGIN_ADMIN.DESTINATION_NOT_SPECIFIED'), 400);
}
// Check if there is an upload error.
switch ($uploadedFile->getError()) {
case UPLOAD_ERR_OK:
@@ -101,10 +92,38 @@ trait MediaUploadTrait
throw new RuntimeException($this->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 400);
}
$metadata = [
'filename' => $uploadedFile->getClientFilename(),
'mime' => $uploadedFile->getClientMediaType(),
'size' => $uploadedFile->getSize(),
];
return $this->checkFileMetadata($metadata, $filename, $settings);
}
/**
* Checks that file metadata meets the requirements. Returns new filename.
*
* @param array $metadata
* @param array|null $settings
* @return string|null
* @throws RuntimeException
*/
public function checkFileMetadata(array $metadata, string $filename = null, array $settings = null): string
{
// Add the defaults to the settings.
$settings = $this->getUploadSettings($settings);
// Destination is always needed (but it can be set in defaults).
$self = $settings['self'] ?? false;
if (!isset($settings['destination']) && $self === false) {
throw new RuntimeException($this->translate('PLUGIN_ADMIN.DESTINATION_NOT_SPECIFIED'), 400);
}
if (null === $filename) {
// If no filename is given, use the filename from the uploaded file (path is not allowed).
$folder = '';
$filename = $uploadedFile->getClientFilename() ?? '';
$filename = $metadata['filename'] ?? '';
} else {
// If caller sets the filename, we will accept any custom path.
$folder = dirname($filename);
@@ -128,7 +147,7 @@ trait MediaUploadTrait
$filename = date('YmdHis') . '-' . $filename;
}
}
$filepath = $folder !== '' ? $folder . $filename : $filename;
$filepath = $folder . $filename;
// Check if the filename is allowed.
if (!Utils::checkFilename($filename)) {
@@ -148,14 +167,14 @@ trait MediaUploadTrait
$filesize = $settings['filesize'];
if ($filesize) {
$max_filesize = $filesize * 1048576;
if ($uploadedFile->getSize() > $max_filesize) {
if ($metadata['size'] > $max_filesize) {
// TODO: use own language string
throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT'), 400);
}
} elseif (null === $filesize) {
// Check size against the Grav upload limit.
$grav_limit = Utils::getUploadLimit();
if ($grav_limit > 0 && $uploadedFile->getSize() > $grav_limit) {
if ($grav_limit > 0 && $metadata['size'] > $grav_limit) {
throw new RuntimeException($this->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT'), 400);
}
}
@@ -165,6 +184,11 @@ trait MediaUploadTrait
$errors = [];
// Do not trust mime type sent by the browser.
$mime = Utils::getMimeByFilename($filename);
$mimeTest = $metadata['mime'] ?? $mime;
if ($mime !== $mimeTest) {
throw new RuntimeException('The mime type does not match to file extension', 400);
}
foreach ((array)$settings['accept'] as $type) {
// Force acceptance of any file when star notation
if ($type === '*') {
@@ -95,8 +95,8 @@ class Excerpts
*/
public function processLinkExcerpt(array $excerpt, string $type = 'link'): array
{
$grav = Grav::instance();
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
$url_parts = $this->parseUrl($url);
// If there is a query, then parse it and build action calls.
@@ -114,7 +114,7 @@ class Excerpts
);
// Valid attributes supported.
$valid_attributes = Grav::instance()['config']->get('system.pages.markdown.valid_link_attributes');
$valid_attributes = $grav['config']->get('system.pages.markdown.valid_link_attributes') ?? [];
$skip = [];
// Unless told to not process, go through actions.
@@ -155,9 +155,11 @@ class Excerpts
// If scheme isn't http(s)..
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
// Handle custom streams.
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
$grav = Grav::instance();
$url_parts['path'] = $grav['base_url_relative'] . '/' . $this->resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
/** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
if ($locator->isStream($url)) {
$path = $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
$url_parts['path'] = $grav['base_url_relative'] . '/' . $path;
unset($url_parts['stream'], $url_parts['scheme']);
}
@@ -338,20 +340,4 @@ class Excerpts
return $url_parts;
}
/**
* @param string $url
* @return string
*/
protected function resolveStream(string $url)
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if ($locator->isStream($url)) {
return $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
}
return $url;
}
}
+1 -1
View File
@@ -2095,7 +2095,7 @@ class Page implements PageInterface
*/
public function filePathClean()
{
return str_replace(ROOT_DIR, '', $this->filePath());
return str_replace(GRAV_ROOT . DS, '', $this->filePath());
}
/**
+1 -1
View File
@@ -311,7 +311,7 @@ class Plugins extends Iterator
}
} else {
$grav['log']->addWarning(
sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $name)
sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clearcache`", $name)
);
return null;
}
@@ -101,7 +101,14 @@ class InitializeProcessor extends ProcessorBase
// Load pages.
$this->initializePages($config);
// Initialize URI.
// Load accounts (decides class to be used).
// TODO: remove in 2.0.
$this->container['accounts'];
// Initialize session.
$this->initializeSession($config);
// Initialize URI (uses session, see issue #3269).
$this->initializeUri($config);
// Grav may return redirect response right away.
@@ -115,13 +122,6 @@ class InitializeProcessor extends ProcessorBase
}
}
// Load accounts (decides class to be used).
// TODO: remove in 2.0.
$this->container['accounts'];
// Initialize session.
$this->initializeSession($config);
$this->stopTimer('_init');
// Wrap call to next handler so that debugger can profile it.
+1 -1
View File
@@ -210,7 +210,7 @@ class Security
'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])([\s\/]on|\sxmlns)[a-z].*=>?#iUu',
// Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols
'invalid_protocols' => '#(' . implode('|', array_map('preg_quote', $invalid_protocols, ['#'])) . '):.*?#iUu',
'invalid_protocols' => '#(' . implode('|', array_map('preg_quote', $invalid_protocols, ['#'])) . '):\S.*?#iUu',
// Match -moz-bindings
'moz_binding' => '#-moz-binding[a-z\x00-\x20]*:#u',
@@ -10,10 +10,18 @@
namespace Grav\Common\Service;
use Grav\Common\Uri;
use JsonException;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use function explode;
use function fopen;
use function function_exists;
use function in_array;
use function is_array;
use function strtolower;
use function trim;
/**
* Class RequestServiceProvider
@@ -36,7 +44,56 @@ class RequestServiceProvider implements ServiceProviderInterface
$psr17Factory // StreamFactory
);
return $creator->fromGlobals();
$server = $_SERVER;
if (false === isset($server['REQUEST_METHOD'])) {
$server['REQUEST_METHOD'] = 'GET';
}
$method = $server['REQUEST_METHOD'];
$headers = function_exists('getallheaders') ? getallheaders() : $creator::getHeadersFromServer($_SERVER);
$post = null;
if (in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'])) {
foreach ($headers as $headerName => $headerValue) {
if ('content-type' !== strtolower($headerName)) {
continue;
}
$contentType = strtolower(trim(explode(';', $headerValue, 2)[0]));
switch ($contentType) {
case 'application/x-www-form-urlencoded':
case 'multipart/form-data':
$post = $_POST;
break 2;
case 'application/json':
case 'application/vnd.api+json':
try {
$json = file_get_contents('php://input');
$post = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($post)) {
$post = null;
}
} catch (JsonException $e) {
$post = null;
}
break 2;
}
}
}
// Remove _url from ngnix routes.
$get = $_GET;
unset($get['_url']);
if (isset($server['QUERY_STRING'])) {
$query = $server['QUERY_STRING'];
if (strpos($query, '_url=') !== false) {
parse_str($query, $query);
unset($query['_url']);
$server['QUERY_STRING'] = http_build_query($query);
}
}
return $creator->fromArrays($server, $headers, $_COOKIE, $get, $post, $_FILES, fopen('php://input', 'rb') ?: null);
};
$container['route'] = $container->factory(function () {
@@ -0,0 +1,351 @@
<?php
/**
* @package Grav\Common\Twig
*
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\Extension;
use Grav\Common\Grav;
use Grav\Common\Utils;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
/**
* Class FilesystemExtension
* @package Grav\Common\Twig\Extension
*/
class FilesystemExtension extends AbstractExtension
{
/** @var UniformResourceLocator */
private $locator;
public function __construct()
{
$this->locator = Grav::instance()['locator'];
}
/**
* @return TwigFunction[]
*/
public function getFilters()
{
return $this->getFunctions();
}
/**
* Return a list of all functions.
*
* @return TwigFunction[]
*/
public function getFunctions()
{
return [
new TwigFunction('file_exists', [$this, 'file_exists']),
new TwigFunction('fileatime', [$this, 'fileatime']),
new TwigFunction('filectime', [$this, 'filectime']),
new TwigFunction('filemtime', [$this, 'filemtime']),
new TwigFunction('filesize', [$this, 'filesize']),
new TwigFunction('filetype', [$this, 'filetype']),
new TwigFunction('is_dir', [$this, 'is_dir']),
new TwigFunction('is_file', [$this, 'is_file']),
new TwigFunction('is_link', [$this, 'is_link']),
new TwigFunction('is_readable', [$this, 'is_readable']),
new TwigFunction('is_writable', [$this, 'is_writable']),
new TwigFunction('is_writeable', [$this, 'is_writable']),
new TwigFunction('lstat', [$this, 'lstat']),
new TwigFunction('getimagesize', [$this, 'getimagesize']),
new TwigFunction('exif_read_data', [$this, 'exif_read_data']),
new TwigFunction('read_exif_data', [$this, 'exif_read_data']),
new TwigFunction('exif_imagetype', [$this, 'exif_imagetype']),
new TwigFunction('hash_file', [$this, 'hash_file']),
new TwigFunction('hash_hmac_file', [$this, 'hash_hmac_file']),
new TwigFunction('md5_file', [$this, 'md5_file']),
new TwigFunction('sha1_file', [$this, 'sha1_file']),
new TwigFunction('get_meta_tags', [$this, 'get_meta_tags']),
];
}
/**
* @param string $filename
* @return bool
*/
public function file_exists($filename): bool
{
if (!$this->checkFilename($filename)) {
return false;
}
return file_exists($filename);
}
/**
* @param string $filename
* @return int|false
*/
public function fileatime($filename)
{
if (!$this->checkFilename($filename)) {
return false;
}
return fileatime($filename);
}
/**
* @param string $filename
* @return int|false
*/
public function filectime($filename)
{
if (!$this->checkFilename($filename)) {
return false;
}
return filectime($filename);
}
/**
* @param string $filename
* @return int|false
*/
public function filemtime($filename)
{
if (!$this->checkFilename($filename)) {
return false;
}
return filemtime($filename);
}
/**
* @param string $filename
* @return int|false
*/
public function filesize($filename)
{
if (!$this->checkFilename($filename)) {
return false;
}
return filesize($filename);
}
/**
* @param string $filename
* @return string|false
*/
public function filetype($filename)
{
if (!$this->checkFilename($filename)) {
return false;
}
return filetype($filename);
}
/**
* @param string $filename
* @return bool
*/
public function is_dir($filename): bool
{
if (!$this->checkFilename($filename)) {
return false;
}
return is_dir($filename);
}
/**
* @param string $filename
* @return bool
*/
public function is_file($filename): bool
{
if (!$this->checkFilename($filename)) {
return false;
}
return is_file($filename);
}
/**
* @param string $filename
* @return bool
*/
public function is_link($filename): bool
{
if (!$this->checkFilename($filename)) {
return false;
}
return is_link($filename);
}
/**
* @param string $filename
* @return bool
*/
public function is_readable($filename): bool
{
if (!$this->checkFilename($filename)) {
return false;
}
return is_readable($filename);
}
/**
* @param string $filename
* @return bool
*/
public function is_writable($filename): bool
{
if (!$this->checkFilename($filename)) {
return false;
}
return is_writable($filename);
}
/**
* @param string $filename
* @return array|false
*/
public function lstat($filename)
{
if (!$this->checkFilename($filename)) {
return false;
}
return lstat($filename);
}
/**
* @param string $filename
* @return array|false
*/
public function getimagesize($filename)
{
if (!$this->checkFilename($filename)) {
return false;
}
return getimagesize($filename);
}
/**
* @param string $file
* @param string|null $required_sections
* @param bool $as_arrays
* @param bool $read_thumbnail
* @return array|false
*/
public function exif_read_data($file, ?string $required_sections, bool $as_arrays = false, bool $read_thumbnail = false)
{
if (!Utils::functionExists('exif_read_data') || !$this->checkFilename($file)) {
return false;
}
return exif_read_data($file, $required_sections, $as_arrays, $read_thumbnail);
}
/**
* @param string $filename
* @return string|false
*/
public function exif_imagetype($filename)
{
if (!Utils::functionExists('exif_imagetype') || !$this->checkFilename($filename)) {
return false;
}
return @exif_imagetype($filename);
}
/**
* @param string $algo
* @param string $filename
* @param bool $binary
* @return string|false
*/
public function hash_file(string $algo, string $filename, bool $binary = false)
{
if (!$this->checkFilename($filename)) {
return false;
}
return hash_file($algo, $filename, $binary);
}
/**
* @param string $algo
* @param string $data
* @param string $key
* @param bool $binary
* @return string|false
*/
public function hash_hmac_file(string $algo, string $data, string $key, bool $binary = false)
{
if (!$this->checkFilename($data)) {
return false;
}
return hash_hmac_file($algo, $data, $key, $binary);
}
/**
* @param string $filename
* @param bool $binary
* @return string|false
*/
public function md5_file($filename, bool $binary = false)
{
if (!$this->checkFilename($filename)) {
return false;
}
return md5_file($filename, $binary);
}
/**
* @param string $filename
* @param bool $binary
* @return string|false
*/
public function sha1_file($filename, bool $binary = false)
{
if (!$this->checkFilename($filename)) {
return false;
}
return sha1_file($filename, $binary);
}
/**
* @param string $filename
* @return array|false
*/
public function get_meta_tags($filename)
{
if (!$this->checkFilename($filename)) {
return false;
}
return get_meta_tags($filename);
}
/**
* @param string $filename
* @return bool
*/
private function checkFilename($filename): bool
{
return is_string($filename) && (!str_contains($filename, '://') || $this->locator->isStream($filename));
}
}
File diff suppressed because it is too large Load Diff
@@ -47,6 +47,6 @@ class TwigNodeMarkdown extends Node implements NodeOutputInterface
->write('$lines = explode("\n", $content);' . PHP_EOL)
->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
->write('$content = join("\n", $content);' . PHP_EOL)
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($context, $content);' . PHP_EOL);
->write('echo $this->env->getExtension(\'Grav\Common\Twig\Extension\GravExtension\')->markdownFunction($context, $content);' . PHP_EOL);
}
}
+48 -16
View File
@@ -16,6 +16,9 @@ use Grav\Common\Language\Language;
use Grav\Common\Language\LanguageCodes;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Pages;
use Grav\Common\Twig\Extension\FilesystemExtension;
use Grav\Common\Twig\Extension\GravExtension;
use Grav\Common\Utils;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\Event\Event;
use Phive\Twig\Extensions\Deferred\DeferredExtension;
@@ -34,6 +37,8 @@ use Twig\Profiler\Profile;
use Twig\TwigFilter;
use Twig\TwigFunction;
use function function_exists;
use function in_array;
use function is_array;
/**
* Class Twig
@@ -154,27 +159,53 @@ class Twig
$this->twig = new TwigEnvironment($loader_chain, $params);
if ($config->get('system.twig.undefined_functions')) {
$this->twig->registerUndefinedFunctionCallback(function ($name) {
$this->twig->registerUndefinedFunctionCallback(function ($name) use ($config) {
$allowed = $config->get('system.twig.safe_functions');
if (is_array($allowed) && in_array($name, $allowed, true) && function_exists($name)) {
return new TwigFunction($name, $name);
}
if ($config->get('system.twig.undefined_functions')) {
if (function_exists($name)) {
return new TwigFunction($name, $name);
if (!Utils::isDangerousFunction($name)) {
user_error("PHP function {$name}() was used as Twig function. This is deprecated in Grav 1.7. Please add it to system configuration: `system.twig.safe_functions`", E_USER_DEPRECATED);
return new TwigFunction($name, $name);
}
/** @var Debugger $debugger */
$debugger = $this->grav['debugger'];
$debugger->addException(new RuntimeException("Blocked potentially dangerous PHP function {$name}() being used as Twig function. If you really want to use it, please add it to system configuration: `system.twig.safe_functions`"));
}
return new TwigFunction($name, static function () {
});
});
}
return new TwigFunction($name, static function () {});
}
if ($config->get('system.twig.undefined_filters')) {
$this->twig->registerUndefinedFilterCallback(function ($name) {
return false;
});
$this->twig->registerUndefinedFilterCallback(function ($name) use ($config) {
$allowed = $config->get('system.twig.safe_filters');
if (is_array($allowed) && in_array($name, $allowed, true) && function_exists($name)) {
return new TwigFilter($name, $name);
}
if ($config->get('system.twig.undefined_filters')) {
if (function_exists($name)) {
return new TwigFilter($name, $name);
if (!Utils::isDangerousFunction($name)) {
user_error("PHP function {$name}() used as Twig filter. This is deprecated in Grav 1.7. Please add it to system configuration: `system.twig.safe_filters`", E_USER_DEPRECATED);
return new TwigFilter($name, $name);
}
/** @var Debugger $debugger */
$debugger = $this->grav['debugger'];
$debugger->addException(new RuntimeException("Blocked potentially dangerous PHP function {$name}() being used as Twig filter. If you really want to use it, please add it to system configuration: `system.twig.safe_filters`"));
}
return new TwigFilter($name, static function () {
});
});
}
return new TwigFilter($name, static function () {});
}
return false;
});
$this->grav->fireEvent('onTwigInitialized');
@@ -188,7 +219,8 @@ class Twig
if ($config->get('system.twig.debug')) {
$this->twig->addExtension(new DebugExtension());
}
$this->twig->addExtension(new TwigExtension());
$this->twig->addExtension(new GravExtension());
$this->twig->addExtension(new FilesystemExtension());
$this->twig->addExtension(new DeferredExtension());
$this->twig->addExtension(new StringLoaderExtension());
@@ -211,7 +243,7 @@ class Twig
'assets' => $this->grav['assets'],
'taxonomy' => $this->grav['taxonomy'],
'browser' => $this->grav['browser'],
'base_dir' => rtrim(ROOT_DIR, '/'),
'base_dir' => GRAV_ROOT,
'home_url' => $pages->homeUrl($active_language),
'base_url' => $pages->baseUrl($active_language),
'base_url_absolute' => $pages->baseUrl($active_language, true),
File diff suppressed because it is too large Load Diff
+309 -72
View File
@@ -44,7 +44,7 @@ use function strlen;
*/
abstract class Utils
{
/** @var array */
/** @var array */
protected static $nonces = [];
protected const ROOTURL_REGEX = '{^((?:http[s]?:\/\/[^\/]+)|(?:\/\/[^\/]+))(.*)}';
@@ -178,8 +178,8 @@ abstract class Utils
/**
* Check if the $haystack string starts with the substring $needle
*
* @param string $haystack
* @param string|string[] $needle
* @param string $haystack
* @param string|string[] $needle
* @param bool $case_sensitive
* @return bool
*/
@@ -202,8 +202,8 @@ abstract class Utils
/**
* Check if the $haystack string ends with the substring $needle
*
* @param string $haystack
* @param string|string[] $needle
* @param string $haystack
* @param string|string[] $needle
* @param bool $case_sensitive
* @return bool
*/
@@ -227,9 +227,9 @@ abstract class Utils
/**
* Check if the $haystack string contains the substring $needle
*
* @param string $haystack
* @param string|string[] $needle
* @param bool $case_sensitive
* @param string $haystack
* @param string|string[] $needle
* @param bool $case_sensitive
* @return bool
*/
public static function contains($haystack, $needle, $case_sensitive = true)
@@ -266,19 +266,19 @@ abstract class Utils
{
$regex = str_replace(
array("\*", "\?"), // wildcard chars
array('.*','.'), // regexp chars
array('.*', '.'), // regexp chars
preg_quote($wildcard_pattern, '/')
);
return preg_match('/^'.$regex.'$/is', $haystack);
return preg_match('/^' . $regex . '$/is', $haystack);
}
/**
* Render simple template filling up the variables in it. If value is not defined, leave it as it was.
*
* @param string $template Template string
* @param array $variables Variables with values
* @param array $brackets Optional array of opening and closing brackets or symbols
* @param string $template Template string
* @param array $variables Variables with values
* @param array $brackets Optional array of opening and closing brackets or symbols
* @return string Final string filled with values
*/
public static function simpleTemplate(string $template, array $variables, array $brackets = ['{', '}']): string
@@ -376,8 +376,8 @@ abstract class Utils
/**
* Merge two objects into one.
*
* @param object $obj1
* @param object $obj2
* @param object $obj1
* @param object $obj2
*
* @return object
*/
@@ -415,7 +415,7 @@ abstract class Utils
*/
public static function arrayRemoveValue(array $search, $value)
{
foreach ((array) $value as $val) {
foreach ((array)$value as $val) {
$key = array_search($val, $search);
if ($key !== false) {
unset($search[$key]);
@@ -481,8 +481,8 @@ abstract class Utils
/**
* Array combine but supports different array lengths
*
* @param array $arr1
* @param array $arr2
* @param array $arr1
* @param array $arr2
* @return array|false
*/
public static function arrayCombine($arr1, $arr2)
@@ -495,7 +495,7 @@ abstract class Utils
/**
* Array is associative or not
*
* @param array $arr
* @param array $arr
* @return bool
*/
public static function arrayIsAssociative($arr)
@@ -517,15 +517,15 @@ abstract class Utils
$now = new DateTime();
$date_formats = [
'd-m-Y H:i' => 'd-m-Y H:i (e.g. '.$now->format('d-m-Y H:i').')',
'Y-m-d H:i' => 'Y-m-d H:i (e.g. '.$now->format('Y-m-d H:i').')',
'm/d/Y h:i a' => 'm/d/Y h:i a (e.g. '.$now->format('m/d/Y h:i a').')',
'H:i d-m-Y' => 'H:i d-m-Y (e.g. '.$now->format('H:i d-m-Y').')',
'h:i a m/d/Y' => 'h:i a m/d/Y (e.g. '.$now->format('h:i a m/d/Y').')',
];
'd-m-Y H:i' => 'd-m-Y H:i (e.g. ' . $now->format('d-m-Y H:i') . ')',
'Y-m-d H:i' => 'Y-m-d H:i (e.g. ' . $now->format('Y-m-d H:i') . ')',
'm/d/Y h:i a' => 'm/d/Y h:i a (e.g. ' . $now->format('m/d/Y h:i a') . ')',
'H:i d-m-Y' => 'H:i d-m-Y (e.g. ' . $now->format('H:i d-m-Y') . ')',
'h:i a m/d/Y' => 'h:i a m/d/Y (e.g. ' . $now->format('h:i a m/d/Y') . ')',
];
$default_format = Grav::instance()['config']->get('system.pages.dateformat.default');
if ($default_format) {
$date_formats = array_merge([$default_format => $default_format.' (e.g. '.$now->format($default_format).')'], $date_formats);
$date_formats = array_merge([$default_format => $default_format . ' (e.g. ' . $now->format($default_format) . ')'], $date_formats);
}
return $date_formats;
@@ -552,11 +552,11 @@ abstract class Utils
/**
* Truncate text by number of characters but can cut off words.
*
* @param string $string
* @param int $limit Max number of characters.
* @param bool $up_to_break truncate up to breakpoint after char count
* @param string $break Break point.
* @param string $pad Appended padding to the end of the string.
* @param string $string
* @param int $limit Max number of characters.
* @param bool $up_to_break truncate up to breakpoint after char count
* @param string $break Break point.
* @param string $pad Appended padding to the end of the string.
* @return string
*/
public static function truncate($string, $limit = 150, $up_to_break = false, $break = ' ', $pad = '&hellip;')
@@ -582,7 +582,7 @@ abstract class Utils
* Truncate text by number of characters in a "word-safe" manor.
*
* @param string $string
* @param int $limit
* @param int $limit
* @return string
*/
public static function safeTruncate($string, $limit = 150)
@@ -594,9 +594,9 @@ abstract class Utils
/**
* Truncate HTML by number of characters. not "word-safe"!
*
* @param string $text
* @param int $length in characters
* @param string $ellipsis
* @param string $text
* @param int $length in characters
* @param string $ellipsis
* @return string
*/
public static function truncateHtml($text, $length = 100, $ellipsis = '...')
@@ -607,9 +607,9 @@ abstract class Utils
/**
* Truncate HTML by number of characters in a "word-safe" manor.
*
* @param string $text
* @param int $length in words
* @param string $ellipsis
* @param string $text
* @param int $length in words
* @param string $ellipsis
* @return string
*/
public static function safeTruncateHtml($text, $length = 25, $ellipsis = '...')
@@ -633,8 +633,8 @@ abstract class Utils
*
* @param string $file the full path to the file to be downloaded
* @param bool $force_download as opposed to letting browser choose if to download or render
* @param int $sec Throttling, try 0.1 for some speed throttling of downloads
* @param int $bytes Size of chunks to send in bytes. Default is 1024
* @param int $sec Throttling, try 0.1 for some speed throttling of downloads
* @param int $bytes Size of chunks to send in bytes. Default is 1024
* @throws Exception
*/
public static function download($file, $force_download = true, $sec = 0, $bytes = 1024)
@@ -645,7 +645,7 @@ abstract class Utils
$file_parts = pathinfo($file);
$mimetype = static::getMimeByExtension($file_parts['extension']);
$size = filesize($file); // File size
$size = filesize($file); // File size
// clean all buffers
while (ob_get_level()) {
@@ -742,7 +742,7 @@ abstract class Utils
// Set from uri extension
$uri_extension = $uri->extension();
if (is_string($uri_extension) && $uri->isValidExtension($uri_extension)) {
return($uri_extension);
return ($uri_extension);
}
// Use content negotiation via the `accept:` header
@@ -1060,7 +1060,7 @@ abstract class Utils
$pretty_offset = "UTC${offset_prefix}${offset_formatted}";
$timezone_list[$timezone] = "(${pretty_offset}) ".str_replace('_', ' ', $timezone);
$timezone_list[$timezone] = "(${pretty_offset}) " . str_replace('_', ' ', $timezone);
}
return $timezone_list;
@@ -1069,11 +1069,11 @@ abstract class Utils
/**
* Recursively filter an array, filtering values by processing them through the $fn function argument
*
* @param array $source the Array to filter
* @param callable $fn the function to pass through each array item
* @param array $source the Array to filter
* @param callable $fn the function to pass through each array item
* @return array
*/
public static function arrayFilterRecursive(Array $source, $fn)
public static function arrayFilterRecursive(array $source, $fn)
{
$result = [];
foreach ($source as $key => $value) {
@@ -1093,15 +1093,15 @@ abstract class Utils
/**
* Flatten a multi-dimensional associative array into query params.
*
* @param array $array
* @param string $prepend
* @param array $array
* @param string $prepend
* @return array
*/
public static function arrayToQueryParams($array, $prepend = '')
{
$results = [];
foreach ($array as $key => $value) {
$name = $prepend ? $prepend . '[' . $key . ']' : $key;
$name = $prepend ? $prepend . '[' . $key . ']' : $key;
if (is_array($value)) {
$results = array_merge($results, static::arrayToQueryParams($value, $name));
@@ -1138,8 +1138,8 @@ abstract class Utils
/**
* Flatten a multi-dimensional associative array into dot notation
*
* @param array $array
* @param string $prepend
* @param array $array
* @param string $prepend
* @return array
*/
public static function arrayFlattenDotNotation($array, $prepend = '')
@@ -1147,9 +1147,9 @@ abstract class Utils
$results = array();
foreach ($array as $key => $value) {
if (is_array($value)) {
$results = array_merge($results, static::arrayFlattenDotNotation($value, $prepend.$key.'.'));
$results = array_merge($results, static::arrayFlattenDotNotation($value, $prepend . $key . '.'));
} else {
$results[$prepend.$key] = $value;
$results[$prepend . $key] = $value;
}
}
@@ -1297,7 +1297,7 @@ abstract class Utils
* with reverse proxy setups.
*
* @param string $action
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
* @return string the nonce string
*/
private static function generateNonceString($action, $previousTick = false)
@@ -1334,8 +1334,8 @@ abstract class Utils
* Creates a hashed nonce tied to the passed action. Tied to the current user and time. The nonce for a given
* action is the same for 12 hours.
*
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
* @param bool $previousTick if true, generates the token for the previous tick (the previous 12 hours)
* @return string the nonce
*/
public static function getNonce($action, $previousTick = false)
@@ -1353,7 +1353,7 @@ abstract class Utils
/**
* Verify the passed nonce for the give action
*
* @param string|string[] $nonce the nonce to verify
* @param string|string[] $nonce the nonce to verify
* @param string $action the action to verify the nonce to
* @return boolean verified or not
*/
@@ -1370,9 +1370,7 @@ abstract class Utils
}
//Nonce generated 12-24 hours ago
$previousTick = true;
return $nonce === self::getNonce($action, $previousTick);
return $nonce === self::getNonce($action, true);
}
/**
@@ -1382,11 +1380,7 @@ abstract class Utils
*/
public static function isAdminPlugin()
{
if (isset(Grav::instance()['admin'])) {
return true;
}
return false;
return isset(Grav::instance()['admin']);
}
/**
@@ -1440,7 +1434,7 @@ abstract class Utils
while (count($keys) > 1) {
$key = array_shift($keys);
if (! isset($array[$key]) || ! is_array($array[$key])) {
if (!isset($array[$key]) || !is_array($array[$key])) {
$array[$key] = array();
}
@@ -1731,7 +1725,7 @@ abstract class Utils
$size *= 1024 ** stripos('bkmgtpezy', $unit[0]);
}
return (int) abs(round($size));
return (int)abs(round($size));
}
/**
@@ -1776,7 +1770,7 @@ abstract class Utils
public static function processMarkdown($string, $block = true, $page = null)
{
$grav = Grav::instance();
$page = $page ?? $grav['page'] ?? null;
$page = $page ?? $grav['page'] ?? null;
$defaults = [
'markdown' => $grav['config']->get('system.pages.markdown', []),
'images' => $grav['config']->get('system.images', [])
@@ -1818,12 +1812,12 @@ abstract class Utils
$ip = (string)inet_pton($ip);
// Maximum netmask length = same as packed address
$len = 8*strlen($ip);
$len = 8 * strlen($ip);
if ($prefix > $len) {
$prefix = $len;
}
$mask = str_repeat('f', $prefix>>2);
$mask = str_repeat('f', $prefix >> 2);
switch ($prefix & 3) {
case 3:
@@ -1836,7 +1830,7 @@ abstract class Utils
$mask .= '8';
break;
}
$mask = str_pad($mask, $len>>2, '0');
$mask = str_pad($mask, $len >> 2, '0');
// Packed representation of netmask
$mask = pack('H*', $mask);
@@ -1850,11 +1844,14 @@ abstract class Utils
* Wrapper to ensure html, htm in the front of the supported page types
*
* @param array|null $defaults
* @return array|mixed
* @return array
*/
public static function getSupportPageTypes(array $defaults = null)
{
$types = Grav::instance()['config']->get('system.pages.types', $defaults);
if (!is_array($types)) {
return [];
}
// remove html/htm
$types = static::arrayRemoveValue($types, ['html', 'htm']);
@@ -1864,4 +1861,244 @@ abstract class Utils
return $types;
}
/**
* @param string $name
* @return bool
*/
public static function isDangerousFunction(string $name): bool
{
static $commandExecutionFunctions = [
'exec',
'passthru',
'system',
'shell_exec',
'popen',
'proc_open',
'pcntl_exec',
];
static $codeExecutionFunctions = [
'assert',
'preg_replace',
'create_function',
'include',
'include_once',
'require',
'require_once'
];
static $callbackFunctions = [
'ob_start' => 0,
'array_diff_uassoc' => -1,
'array_diff_ukey' => -1,
'array_filter' => 1,
'array_intersect_uassoc' => -1,
'array_intersect_ukey' => -1,
'array_map' => 0,
'array_reduce' => 1,
'array_udiff_assoc' => -1,
'array_udiff_uassoc' => [-1, -2],
'array_udiff' => -1,
'array_uintersect_assoc' => -1,
'array_uintersect_uassoc' => [-1, -2],
'array_uintersect' => -1,
'array_walk_recursive' => 1,
'array_walk' => 1,
'assert_options' => 1,
'uasort' => 1,
'uksort' => 1,
'usort' => 1,
'preg_replace_callback' => 1,
'spl_autoload_register' => 0,
'iterator_apply' => 1,
'call_user_func' => 0,
'call_user_func_array' => 0,
'register_shutdown_function' => 0,
'register_tick_function' => 0,
'set_error_handler' => 0,
'set_exception_handler' => 0,
'session_set_save_handler' => [0, 1, 2, 3, 4, 5],
'sqlite_create_aggregate' => [2, 3],
'sqlite_create_function' => 2,
];
static $informationDiscosureFunctions = [
'phpinfo',
'posix_mkfifo',
'posix_getlogin',
'posix_ttyname',
'getenv',
'get_current_user',
'proc_get_status',
'get_cfg_var',
'disk_free_space',
'disk_total_space',
'diskfreespace',
'getcwd',
'getlastmo',
'getmygid',
'getmyinode',
'getmypid',
'getmyuid'
];
static $otherFunctions = [
'extract',
'parse_str',
'putenv',
'ini_set',
'mail',
'header',
'proc_nice',
'proc_terminate',
'proc_close',
'pfsockopen',
'fsockopen',
'apache_child_terminate',
'posix_kill',
'posix_mkfifo',
'posix_setpgid',
'posix_setsid',
'posix_setuid',
];
if (in_array($name, $commandExecutionFunctions)) {
return true;
}
if (in_array($name, $codeExecutionFunctions)) {
return true;
}
if (isset($callbackFunctions[$name])) {
return true;
}
if (in_array($name, $informationDiscosureFunctions)) {
return true;
}
if (in_array($name, $otherFunctions)) {
return true;
}
return static::isFilesystemFunction($name);
}
/**
* @param string $name
* @return bool
*/
public static function isFilesystemFunction(string $name): bool
{
static $fileWriteFunctions = [
'fopen',
'tmpfile',
'bzopen',
'gzopen',
// write to filesystem (partially in combination with reading)
'chgrp',
'chmod',
'chown',
'copy',
'file_put_contents',
'lchgrp',
'lchown',
'link',
'mkdir',
'move_uploaded_file',
'rename',
'rmdir',
'symlink',
'tempnam',
'touch',
'unlink',
'imagepng',
'imagewbmp',
'image2wbmp',
'imagejpeg',
'imagexbm',
'imagegif',
'imagegd',
'imagegd2',
'iptcembed',
'ftp_get',
'ftp_nb_get',
];
static $fileContentFunctions = [
'file_get_contents',
'file',
'filegroup',
'fileinode',
'fileowner',
'fileperms',
'glob',
'is_executable',
'is_uploaded_file',
'parse_ini_file',
'readfile',
'readlink',
'realpath',
'gzfile',
'readgzfile',
'stat',
'imagecreatefromgif',
'imagecreatefromjpeg',
'imagecreatefrompng',
'imagecreatefromwbmp',
'imagecreatefromxbm',
'imagecreatefromxpm',
'ftp_put',
'ftp_nb_put',
'hash_update_file',
'highlight_file',
'show_source',
'php_strip_whitespace',
];
static $filesystemFunctions = [
// read from filesystem
'file_exists',
'fileatime',
'filectime',
'filemtime',
'filesize',
'filetype',
'is_dir',
'is_file',
'is_link',
'is_readable',
'is_writable',
'is_writeable',
'linkinfo',
'lstat',
//'pathinfo',
'getimagesize',
'exif_read_data',
'read_exif_data',
'exif_thumbnail',
'exif_imagetype',
'hash_file',
'hash_hmac_file',
'md5_file',
'sha1_file',
'get_meta_tags',
];
if (in_array($name, $fileWriteFunctions)) {
return true;
}
if (in_array($name, $fileContentFunctions)) {
return true;
}
if (in_array($name, $filesystemFunctions)) {
return true;
}
return false;
}
}
@@ -73,11 +73,11 @@ class InstallCommand extends GravCommand
$io = $this->getIO();
$dependencies_file = '.dependencies';
$this->destination = $input->getArgument('destination') ?: GRAV_ROOT;
$this->destination = $input->getArgument('destination') ?: GRAV_WEBROOT;
// fix trailing slash
$this->destination = rtrim($this->destination, DS) . DS;
$this->user_path = $this->destination . USER_PATH;
$this->user_path = $this->destination . GRAV_USER_PATH . DS;
if ($local_config_file = $this->loadLocalConfig()) {
$io->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
}
@@ -600,7 +600,13 @@ class InstallCommand extends GpmCommand
try {
$output = Response::get($package->zipball_url . $query, [], [$this, 'progress']);
} catch (Exception $e) {
$error = str_replace("\n", "\n | '- ", $e->getMessage());
if (!empty($package->premium) && $e->getCode() === 401) {
$message = '<yellow>Unauthorized Premium License Key</yellow>';
} else {
$message = $e->getMessage();
}
$error = str_replace("\n", "\n | '- ", $message);
$io->write("\x0D");
// extra white spaces to clear out the buffer properly
$io->writeln(' |- Downloading package... <red>error</red> ');
@@ -112,7 +112,7 @@ class UninstallCommand extends GpmCommand
unset($this->data['not_found'], $this->data['total']);
// Plugins need to be initialized in order to make clear-cache to work.
// Plugins need to be initialized in order to make clearcache to work.
try {
$this->initializePlugins();
} catch (Throwable $e) {
@@ -173,6 +173,7 @@ trait ControllerResponseTrait
if ($method !== 'GET' && $method !== 'HEAD') {
$this->setMessage($message, 'error');
$referer = $request->getHeaderLine('Referer');
return $this->createRedirectResponse($referer, 303);
}
+1 -1
View File
@@ -119,7 +119,7 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
}
$this->setUniqueId($uniqueId);
$directory = $object->getFlexDirectory();
$this->setFlashLookupFolder($directory->getBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]');
$this->setFlashLookupFolder($options['flash_folder'] ?? $directory->getBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]');
$this->form = $options['form'] ?? null;
if (!empty($options['reset'])) {
+24 -7
View File
@@ -196,9 +196,10 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
/**
* Refresh object from the storage.
*
* @param bool $keepMissing
* @return bool True if the object was refreshed
*/
public function refresh(): bool
public function refresh(bool $keepMissing = false): bool
{
$key = $this->getStorageKey();
if ('' === $key) {
@@ -216,20 +217,36 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
return false;
}
// Get current elements (if requested).
$current = $keepMissing ? $this->getElements() : [];
// Get elements from the filesystem.
$elements = $storage->readRows([$key => null])[$key] ?? null;
if (null !== $elements || isset($elements['__ERROR'])) {
$meta = $elements['_META'] ?? $meta;
if (null !== $elements) {
$meta = $elements['__META'] ?? $meta;
unset($elements['__META']);
$this->filterElements($elements);
$newKey = $meta['key'] ?? $this->getKey();
if ($meta) {
$this->setMetaData($meta);
}
$this->objectConstruct($elements, $newKey);
}
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];
$debugger->addMessage("Refreshed {$this->getFlexType()} object {$this->getKey()}", 'debug');
if ($current) {
// Inject back elements which are missing in the filesystem.
$data = $this->getBlueprint()->flattenData($current);
foreach ($data as $property => $value) {
if (strpos($property, '.') === false) {
$this->defProperty($property, $value);
} else {
$this->defNestedProperty($property, $value);
}
}
}
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];
$debugger->addMessage("Refreshed {$this->getFlexType()} object {$this->getKey()}", 'debug');
}
return true;
}
@@ -320,14 +320,21 @@ trait PageLegacyTrait
// Find non-existing key.
$parentKey = $parent ? $parent->getKey() : '';
$key = trim($parentKey . '/' . basename($this->getKey()), '/');
$key = preg_replace('/-\d+$/', '', $key);
$i = 1;
do {
$i++;
$test = "{$key}-{$i}";
} while ($index->containsKey($test));
$key = $test;
if ($this instanceof FlexPageObject) {
$key = trim($parentKey . '/' . $this->folder(), '/');
} else {
$key = trim($parentKey . '/' . basename($this->getKey()), '/');
}
if ($index->containsKey($key)) {
$key = preg_replace('/\d+$/', '', $key);
$i = 1;
do {
$i++;
$test = "{$key}{$i}";
} while ($index->containsKey($test));
$key = $test;
}
$folder = basename($key);
// Get the folder name.