mirror of
https://github.com/getgrav/grav.git
synced 2026-03-05 12:01:37 +01:00
Merge branch 'develop' of github.com:getgrav/grav into feature/v1.8
Conflicts: CHANGELOG.md composer.lock
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -13,9 +13,18 @@
|
||||
* Removed `system.umask_fix` setting for security reasons
|
||||
* Support phpstan level 6 in Framework classes
|
||||
|
||||
# v1.7.32
|
||||
# v1.7.33
|
||||
## mm/dd/2022
|
||||
|
||||
1. [](#improved)
|
||||
* When saving yaml and markdown, create also a cached version of the file and recompile it in opcache
|
||||
2. [](#bugfix)
|
||||
* Fixed missing changes in yaml & markdown files if saved multiple times during the same second because of a caching issue
|
||||
* Fixed XSS check not detecting onX events without quotes
|
||||
|
||||
# v1.7.32
|
||||
## 03/28/2022
|
||||
|
||||
1. [](#new)
|
||||
* Added `|replace_last(search, replace)` filter
|
||||
* Added `parseurl` Twig function to expose PHP's `parse_url` function
|
||||
@@ -27,8 +36,9 @@
|
||||
- `text`, `url`, `hidden`, `commalist`: 2048
|
||||
- `text` (multiline), `textarea`: 65536
|
||||
3. [](#bugfix)
|
||||
* Fixed issue with `system.cache.gzip: true` resulted in admin "Fetch Failed" for PHP 8.0+
|
||||
* Fixed issue with `system.cache.gzip: true` resulted in "Fetch Failed" for PHP 8.0.17 and PHP 8.1.4 [PHP issue #8218](https://github.com/php/php-src/issues/8218)
|
||||
* Fix for multi-lang issues with Security Report
|
||||
* Fixed page search not working with selected language [#3316](https://github.com/getgrav/grav/issues/3316)
|
||||
|
||||
# v1.7.31
|
||||
## 03/14/2022
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.7.31');
|
||||
define('GRAV_VERSION', '1.7.32');
|
||||
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
|
||||
define('GRAV_TESTING', false);
|
||||
|
||||
|
||||
@@ -43,4 +43,25 @@ class SystemFacade extends \Whoops\Util\SystemFacade
|
||||
$handler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $httpCode
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function setHttpResponseCode($httpCode)
|
||||
{
|
||||
if (!headers_sent()) {
|
||||
// Ensure that no 'location' header is present as otherwise this
|
||||
// will override the HTTP code being set here, and mask the
|
||||
// expected error page.
|
||||
header_remove('location');
|
||||
|
||||
// Work around PHP bug #8218 (8.0.17 & 8.1.4).
|
||||
header_remove('Content-Encoding');
|
||||
}
|
||||
|
||||
return http_response_code($httpCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
namespace Grav\Common\File;
|
||||
|
||||
use Exception;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\File\PhpFile;
|
||||
use RuntimeException;
|
||||
@@ -32,9 +34,10 @@ trait CompiledFile
|
||||
public function content($var = null)
|
||||
{
|
||||
try {
|
||||
$filename = $this->filename;
|
||||
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
|
||||
if ($var === null && $this->raw === null && $this->content === null) {
|
||||
$key = md5($this->filename);
|
||||
$key = md5($filename);
|
||||
$file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
|
||||
|
||||
$modified = $this->modified();
|
||||
@@ -48,39 +51,49 @@ trait CompiledFile
|
||||
|
||||
$class = get_class($this);
|
||||
|
||||
$size = filesize($filename);
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
|
||||
// Load real file if cache isn't up to date (or is invalid).
|
||||
if (!isset($cache['@class'])
|
||||
|| $cache['@class'] !== $class
|
||||
|| $cache['modified'] !== $modified
|
||||
|| $cache['filename'] !== $this->filename
|
||||
|| ($cache['size'] ?? null) !== $size
|
||||
|| $cache['filename'] !== $filename
|
||||
) {
|
||||
// Attempt to lock the file for writing.
|
||||
try {
|
||||
$file->lock(false);
|
||||
$locked = $file->lock(false);
|
||||
} catch (Exception $e) {
|
||||
// Another process has locked the file; we will check this in a bit.
|
||||
$locked = false;
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
$debugger->addMessage(sprintf('%s(): Cannot obtain a lock for compiling cache file for %s: %s', __METHOD__, $this->filename, $e->getMessage()), 'warning');
|
||||
}
|
||||
|
||||
// Decode RAW file into compiled array.
|
||||
$data = (array)$this->decode($this->raw());
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'filename' => $this->filename,
|
||||
'filename' => $filename,
|
||||
'modified' => $modified,
|
||||
'size' => $size,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
if ($locked) {
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
|
||||
// Compile cached file into bytecode cache
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
if (function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
|
||||
$lockName = $file->filename();
|
||||
|
||||
// Silence error if function exists, but is restricted.
|
||||
@opcache_invalidate($file->filename(), true);
|
||||
@opcache_invalidate($lockName, true);
|
||||
@opcache_compile_file($lockName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,12 +102,64 @@ trait CompiledFile
|
||||
$this->content = $cache['data'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new RuntimeException(sprintf('Failed to read %s: %s', Utils::basename($this->filename), $e->getMessage()), 500, $e);
|
||||
throw new RuntimeException(sprintf('Failed to read %s: %s', Utils::basename($filename), $e->getMessage()), 500, $e);
|
||||
}
|
||||
|
||||
return parent::content($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save file.
|
||||
*
|
||||
* @param mixed $data Optional data to be saved, usually array.
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save($data = null)
|
||||
{
|
||||
// Make sure that the cache file is always up to date!
|
||||
$key = md5($this->filename);
|
||||
$file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
|
||||
try {
|
||||
$locked = $file->lock();
|
||||
} catch (Exception $e) {
|
||||
$locked = false;
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
$debugger->addMessage(sprintf('%s(): Cannot obtain a lock for compiling cache file for %s: %s', __METHOD__, $this->filename, $e->getMessage()), 'warning');
|
||||
}
|
||||
|
||||
parent::save($data);
|
||||
|
||||
if ($locked) {
|
||||
$modified = $this->modified();
|
||||
$filename = $this->filename;
|
||||
$class = get_class($this);
|
||||
$size = filesize($filename);
|
||||
|
||||
// Decode data into compiled array.
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'filename' => $filename,
|
||||
'modified' => $modified,
|
||||
'size' => $size,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
|
||||
// Compile cached file into bytecode cache
|
||||
if (function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
|
||||
$lockName = $file->filename();
|
||||
// Silence error if function exists, but is restricted.
|
||||
@opcache_invalidate($lockName, true);
|
||||
@opcache_compile_file($lockName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize file.
|
||||
*
|
||||
|
||||
@@ -454,7 +454,7 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the main key without template and langauge.
|
||||
// Get the main key without template and language.
|
||||
[$main_key,] = explode('|', $entry['storage_key'] . '|', 2);
|
||||
|
||||
// Update storage key and language.
|
||||
@@ -527,10 +527,7 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
|
||||
$language = $options['lang'];
|
||||
|
||||
$status = 'error';
|
||||
$msg = null;
|
||||
$response = [];
|
||||
$children = null;
|
||||
$sub_route = null;
|
||||
$extra = null;
|
||||
|
||||
// Handle leaf_route
|
||||
@@ -610,7 +607,7 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
|
||||
$children = $page->children();
|
||||
/** @var PageIndex $children */
|
||||
$children = $children->getIndex();
|
||||
$selectedChildren = $children->filterBy($filters, true);
|
||||
$selectedChildren = $children->filterBy($filters + ['language' => $language], true);
|
||||
|
||||
/** @var Header $header */
|
||||
$header = $page->header();
|
||||
|
||||
@@ -242,6 +242,7 @@ class PageObject extends FlexPageObject
|
||||
{
|
||||
/** @var PageCollection $siblings */
|
||||
$siblings = $variables['siblings'];
|
||||
/** @var PageObject $sibling */
|
||||
foreach ($siblings as $sibling) {
|
||||
$sibling->save(false);
|
||||
}
|
||||
@@ -585,38 +586,46 @@ class PageObject extends FlexPageObject
|
||||
*/
|
||||
public function filterBy(array $filters, bool $recursive = false): bool
|
||||
{
|
||||
$language = $filters['language'] ?? null;
|
||||
if (null !== $language) {
|
||||
/** @var PageObject $test */
|
||||
$test = $this->getTranslation($language) ?? $this;
|
||||
} else {
|
||||
$test = $this;
|
||||
}
|
||||
|
||||
foreach ($filters as $key => $value) {
|
||||
switch ($key) {
|
||||
case 'search':
|
||||
$matches = $this->search((string)$value) > 0.0;
|
||||
$matches = $test->search((string)$value) > 0.0;
|
||||
break;
|
||||
case 'page_type':
|
||||
$types = $value ? explode(',', $value) : [];
|
||||
$matches = in_array($this->template(), $types, true);
|
||||
$matches = in_array($test->template(), $types, true);
|
||||
break;
|
||||
case 'extension':
|
||||
$matches = Utils::contains((string)$value, $this->extension());
|
||||
$matches = Utils::contains((string)$value, $test->extension());
|
||||
break;
|
||||
case 'routable':
|
||||
$matches = $this->isRoutable() === (bool)$value;
|
||||
$matches = $test->isRoutable() === (bool)$value;
|
||||
break;
|
||||
case 'published':
|
||||
$matches = $this->isPublished() === (bool)$value;
|
||||
$matches = $test->isPublished() === (bool)$value;
|
||||
break;
|
||||
case 'visible':
|
||||
$matches = $this->isVisible() === (bool)$value;
|
||||
$matches = $test->isVisible() === (bool)$value;
|
||||
break;
|
||||
case 'module':
|
||||
$matches = $this->isModule() === (bool)$value;
|
||||
$matches = $test->isModule() === (bool)$value;
|
||||
break;
|
||||
case 'page':
|
||||
$matches = $this->isPage() === (bool)$value;
|
||||
$matches = $test->isPage() === (bool)$value;
|
||||
break;
|
||||
case 'folder':
|
||||
$matches = $this->isPage() === !$value;
|
||||
$matches = $test->isPage() === !$value;
|
||||
break;
|
||||
case 'translated':
|
||||
$matches = $this->hasTranslation() === (bool)$value;
|
||||
$matches = $test->hasTranslation() === (bool)$value;
|
||||
break;
|
||||
default:
|
||||
$matches = true;
|
||||
|
||||
@@ -350,14 +350,12 @@ class Grav extends Container
|
||||
*/
|
||||
public function cleanOutputBuffers(): void
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = $this['config'];
|
||||
$gzip_enabled = (int) $config->get('system.cache.gzip');
|
||||
|
||||
// Make sure nothing extra gets written to the response.
|
||||
while (ob_get_level() > 2 + $gzip_enabled) {
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
// Work around PHP bug #8218 (8.0.17 & 8.1.4).
|
||||
header_remove('Content-Encoding');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -219,7 +219,8 @@ class Security
|
||||
$string = html_entity_decode($string, ENT_NOQUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
// Strip whitespace characters
|
||||
$string = preg_replace('!\s!u', '', $string);
|
||||
$string = preg_replace('!\s!u', ' ', $string);
|
||||
$stripped = preg_replace('!\s!u', '', $string);
|
||||
|
||||
// Set the patterns we'll test against
|
||||
$patterns = [
|
||||
@@ -242,7 +243,7 @@ class Security
|
||||
// Iterate over rules and return label if fail
|
||||
foreach ($patterns as $name => $regex) {
|
||||
if (!empty($enabled_rules[$name])) {
|
||||
if (preg_match($regex, $string) || preg_match($regex, $orig)) {
|
||||
if (preg_match($regex, $string) || preg_match($regex, $stripped) || preg_match($regex, $orig)) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,9 +550,9 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $str
|
||||
* @param $search
|
||||
* @param $replace
|
||||
* @param string|mixed $str
|
||||
* @param string $search
|
||||
* @param string $replace
|
||||
* @return string|mixed
|
||||
*/
|
||||
public function replaceLastFilter($str, $search, $replace)
|
||||
|
||||
@@ -147,6 +147,10 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
|
||||
*/
|
||||
public function search(string $search, $properties = null, array $options = null)
|
||||
{
|
||||
$directory = $this->getFlexDirectory();
|
||||
$properties = $directory->getSearchProperties($properties);
|
||||
$options = $directory->getSearchOptions($options);
|
||||
|
||||
$matching = $this->call('search', [$search, $properties, $options]);
|
||||
$matching = array_filter($matching);
|
||||
|
||||
|
||||
@@ -171,6 +171,44 @@ class FlexDirectory implements FlexDirectoryInterface
|
||||
return null === $name ? $this->config : $this->config->get($name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|string[]|null
|
||||
* @return array
|
||||
*/
|
||||
public function getSearchProperties($properties = null): array
|
||||
{
|
||||
if (null !== $properties) {
|
||||
return (array)$properties;
|
||||
}
|
||||
|
||||
$properties = $this->getConfig('data.search.fields');
|
||||
if (!$properties) {
|
||||
$fields = $this->getConfig('admin.views.list.fields') ?? $this->getConfig('admin.list.fields', []);
|
||||
foreach ($fields as $property => $value) {
|
||||
if (!empty($value['link'])) {
|
||||
$properties[] = $property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $options
|
||||
* @return array
|
||||
*/
|
||||
public function getSearchOptions(array $options = null): array
|
||||
{
|
||||
if (empty($options['merge'])) {
|
||||
return $options ?? (array)$this->getConfig('data.search.options');
|
||||
}
|
||||
|
||||
unset($options['merge']);
|
||||
|
||||
return $options + (array)$this->getConfig('data.search.options');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
* @param array $options
|
||||
|
||||
@@ -162,6 +162,10 @@ class FlexIndex extends ObjectIndex implements FlexIndexInterface
|
||||
*/
|
||||
public function search(string $search, $properties = null, array $options = null)
|
||||
{
|
||||
$directory = $this->getFlexDirectory();
|
||||
$properties = $directory->getSearchProperties($properties);
|
||||
$options = $directory->getSearchOptions($options);
|
||||
|
||||
return $this->__call('search', [$search, $properties, $options]);
|
||||
}
|
||||
|
||||
|
||||
@@ -287,17 +287,9 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
*/
|
||||
public function search(string $search, $properties = null, array $options = null): float
|
||||
{
|
||||
$properties = (array)($properties ?? $this->getFlexDirectory()->getConfig('data.search.fields'));
|
||||
if (!$properties) {
|
||||
$fields = $this->getFlexDirectory()->getConfig('admin.views.list.fields') ?? $this->getFlexDirectory()->getConfig('admin.list.fields', []);
|
||||
foreach ($fields as $property => $value) {
|
||||
if (!empty($value['link'])) {
|
||||
$properties[] = $property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$options = $options ?? (array)$this->getFlexDirectory()->getConfig('data.search.options');
|
||||
$directory = $this->getFlexDirectory();
|
||||
$properties = $directory->getSearchProperties($properties);
|
||||
$options = $directory->getSearchOptions($options);
|
||||
|
||||
$weight = 0;
|
||||
foreach ($properties as $property) {
|
||||
|
||||
Reference in New Issue
Block a user