maj
This commit is contained in:
@@ -182,13 +182,14 @@ class Setup extends Data
|
||||
// If no environment is set, make sure we get one (CLI or hostname).
|
||||
if (null === $environment) {
|
||||
if (defined('GRAV_CLI')) {
|
||||
$request = null;
|
||||
$uri = null;
|
||||
$environment = 'cli';
|
||||
} else {
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = $container['request'];
|
||||
$host = $request->getUri()->getHost();
|
||||
|
||||
$environment = Utils::substrToString($host, ':');
|
||||
$uri = $request->getUri();
|
||||
$environment = $uri->getHost();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -515,7 +515,7 @@ class Blueprint extends BlueprintForm
|
||||
$success = $this->resolveActions($user, $actions);
|
||||
}
|
||||
if (!$success) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
static::addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +566,7 @@ class Blueprint extends BlueprintForm
|
||||
}
|
||||
|
||||
if ($matches) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
static::addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -577,7 +577,7 @@ class Blueprint extends BlueprintForm
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
protected function addPropertyRecursive(array &$field, $property, $value)
|
||||
public static function addPropertyRecursive(array &$field, $property, $value)
|
||||
{
|
||||
if (is_array($value) && isset($field[$property]) && is_array($field[$property])) {
|
||||
$field[$property] = array_merge_recursive($field[$property], $value);
|
||||
@@ -587,7 +587,7 @@ class Blueprint extends BlueprintForm
|
||||
|
||||
if (!empty($field['fields'])) {
|
||||
foreach ($field['fields'] as $key => &$child) {
|
||||
$this->addPropertyRecursive($child, $property, $value);
|
||||
static::addPropertyRecursive($child, $property, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,9 @@ class Validation
|
||||
return false;
|
||||
}
|
||||
|
||||
$max = (int)($params['max'] ?? 0);
|
||||
$multiline = isset($params['multiline']) && $params['multiline'];
|
||||
|
||||
$max = (int)($params['max'] ?? ($multiline ? 65536 : 2048));
|
||||
if ($max && $len > $max) {
|
||||
return false;
|
||||
}
|
||||
@@ -256,7 +258,7 @@ class Validation
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((!isset($params['multiline']) || !$params['multiline']) && preg_match('/\R/um', $value)) {
|
||||
if (!$multiline && preg_match('/\R/um', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -317,6 +319,10 @@ class Validation
|
||||
*/
|
||||
public static function typeCommaList($value, array $params, array $field)
|
||||
{
|
||||
if (!isset($params['max'])) {
|
||||
$params['max'] = 2048;
|
||||
}
|
||||
|
||||
return is_array($value) ? true : self::typeText($value, $params, $field);
|
||||
}
|
||||
|
||||
@@ -379,6 +385,10 @@ class Validation
|
||||
*/
|
||||
public static function typePassword($value, array $params, array $field)
|
||||
{
|
||||
if (!isset($params['max'])) {
|
||||
$params['max'] = 256;
|
||||
}
|
||||
|
||||
return self::typeText($value, $params, $field);
|
||||
}
|
||||
|
||||
@@ -621,6 +631,10 @@ class Validation
|
||||
*/
|
||||
public static function typeEmail($value, array $params, array $field)
|
||||
{
|
||||
if (!isset($params['max'])) {
|
||||
$params['max'] = 320;
|
||||
}
|
||||
|
||||
$values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
|
||||
|
||||
foreach ($values as $val) {
|
||||
@@ -642,6 +656,10 @@ class Validation
|
||||
*/
|
||||
public static function typeUrl($value, array $params, array $field)
|
||||
{
|
||||
if (!isset($params['max'])) {
|
||||
$params['max'] = 2048;
|
||||
}
|
||||
|
||||
return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_URL);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,71 @@ 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);
|
||||
|
||||
// windows doesn't play nicely with this as it can't read when locked
|
||||
if (!Utils::isWindows()) {
|
||||
// Reload data from the filesystem. This ensures that we always cache the correct data (see issue #2282).
|
||||
$this->raw = $this->content = null;
|
||||
$data = (array)$this->decode($this->raw());
|
||||
}
|
||||
|
||||
// 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.
|
||||
*
|
||||
|
||||
@@ -31,32 +31,34 @@ abstract class Folder
|
||||
/**
|
||||
* Recursively find the last modified time under given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $paths
|
||||
* @return int
|
||||
*/
|
||||
public static function lastModifiedFolder($path)
|
||||
public static function lastModifiedFolder(array $paths): int
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$last_modified = 0;
|
||||
|
||||
/** @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);
|
||||
}
|
||||
$filter = new RecursiveFolderFilterIterator($directory);
|
||||
$iterator = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
foreach ($iterator as $dir) {
|
||||
$dir_modified = $dir->getMTime();
|
||||
if ($dir_modified > $last_modified) {
|
||||
$last_modified = $dir_modified;
|
||||
foreach ($paths as $path) {
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
$filter = new RecursiveFolderFilterIterator($directory);
|
||||
$iterator = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
foreach ($iterator as $dir) {
|
||||
$dir_modified = $dir->getMTime();
|
||||
if ($dir_modified > $last_modified) {
|
||||
$last_modified = $dir_modified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,38 +68,40 @@ abstract class Folder
|
||||
/**
|
||||
* Recursively find the last modified time under given path by file.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $paths
|
||||
* @param string $extensions which files to search for specifically
|
||||
* @return int
|
||||
*/
|
||||
public static function lastModifiedFile($path, $extensions = 'md|yaml')
|
||||
public static function lastModifiedFile(array $paths, $extensions = 'md|yaml'): int
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$last_modified = 0;
|
||||
|
||||
/** @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 {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_modified;
|
||||
foreach($paths as $path) {
|
||||
if (!file_exists($path)) {
|
||||
return 0;
|
||||
}
|
||||
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 $file) {
|
||||
try {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_modified;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Grav::instance()['log']->error('Could not process file: ' . $e->getMessage());
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Grav::instance()['log']->error('Could not process file: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,28 +111,30 @@ abstract class Folder
|
||||
/**
|
||||
* Recursively md5 hash all files in a path
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $paths
|
||||
* @return string
|
||||
*/
|
||||
public static function hashAllFiles($path)
|
||||
public static function hashAllFiles(array $paths): string
|
||||
{
|
||||
$files = [];
|
||||
|
||||
if (file_exists($path)) {
|
||||
$flags = RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
foreach ($paths as $path) {
|
||||
if (file_exists($path)) {
|
||||
$flags = RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
|
||||
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = $file->getPathname() . '?'. $file->getMTime();
|
||||
foreach ($iterator as $file) {
|
||||
$files[] = $file->getPathname() . '?'. $file->getMTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,12 +607,12 @@ 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();
|
||||
|
||||
if (!$field && $header->get('admin.children_display_order') === 'collection' && ($orderby = $header->get('content.order.by'))) {
|
||||
if (!$field && $header->get('admin.children_display_order', 'collection') === 'collection' && ($orderby = $header->get('content.order.by'))) {
|
||||
// Use custom sorting by page header.
|
||||
$sortby = $orderby;
|
||||
$order = $header->get('content.order.dir', $order);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -30,7 +30,7 @@ use function is_string;
|
||||
*/
|
||||
class UserIndex extends FlexIndex implements UserCollectionInterface
|
||||
{
|
||||
public const VERSION = parent::VERSION . '.1';
|
||||
public const VERSION = parent::VERSION . '.2';
|
||||
|
||||
/**
|
||||
* @param FlexStorageInterface $storage
|
||||
@@ -50,7 +50,7 @@ class UserIndex extends FlexIndex implements UserCollectionInterface
|
||||
// return $index['index'];
|
||||
//}
|
||||
|
||||
// Load up to date index.
|
||||
// Load up-to-date index.
|
||||
$entries = parent::loadEntriesFromStorage($storage);
|
||||
|
||||
return static::updateIndexFile($storage, $index['index'], $entries, ['force_update' => $force]);
|
||||
@@ -142,9 +142,11 @@ class UserIndex extends FlexIndex implements UserCollectionInterface
|
||||
} elseif ($field === 'flex_key') {
|
||||
$user = $this->withKeyField('flex_key')->get($query);
|
||||
} elseif ($field === 'email') {
|
||||
$user = $this->withKeyField('email')->get($query);
|
||||
$email = mb_strtolower($query);
|
||||
$user = $this->withKeyField('email')->get($email);
|
||||
} elseif ($field === 'username') {
|
||||
$user = $this->get(static::filterUsername($query, $this->getFlexDirectory()->getStorage()));
|
||||
$username = static::filterUsername($query, $this->getFlexDirectory()->getStorage());
|
||||
$user = $this->get($username);
|
||||
} else {
|
||||
$user = $this->__call('find', [$query, $field]);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ use Grav\Common\Flex\Types\UserGroups\UserGroupIndex;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\User\Traits\UserTrait;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Contracts\Relationships\ToOneRelationshipInterface;
|
||||
use Grav\Framework\File\Formatter\JsonFormatter;
|
||||
use Grav\Framework\File\Formatter\YamlFormatter;
|
||||
use Grav\Framework\Filesystem\Filesystem;
|
||||
@@ -38,7 +39,10 @@ use Grav\Framework\Flex\Flex;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\Storage\FileStorage;
|
||||
use Grav\Framework\Flex\Traits\FlexMediaTrait;
|
||||
use Grav\Framework\Flex\Traits\FlexRelationshipsTrait;
|
||||
use Grav\Framework\Form\FormFlashFile;
|
||||
use Grav\Framework\Media\MediaIdentifier;
|
||||
use Grav\Framework\Media\UploadedMediaObject;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
@@ -77,6 +81,7 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
||||
}
|
||||
use UserTrait;
|
||||
use UserObjectLegacyTrait;
|
||||
use FlexRelationshipsTrait;
|
||||
|
||||
/** @var Closure|null */
|
||||
static public $authorizeCallable;
|
||||
@@ -672,6 +677,81 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
||||
return $folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return array|object|null
|
||||
* @internal
|
||||
*/
|
||||
public function initRelationship(string $name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'media':
|
||||
$list = [];
|
||||
foreach ($this->getMedia()->all() as $filename => $object) {
|
||||
$list[] = $this->buildMediaObject(null, $filename, $object);
|
||||
}
|
||||
|
||||
return $list;
|
||||
case 'avatar':
|
||||
return $this->buildMediaObject('avatar', basename($this->getAvatarUrl()), $this->getAvatarImage());
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('%s: Relationship %s does not exist', $this->getFlexType(), $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool Return true if relationships were updated.
|
||||
*/
|
||||
protected function updateRelationships(): bool
|
||||
{
|
||||
$modified = $this->getRelationships()->getModified();
|
||||
if ($modified) {
|
||||
foreach ($modified as $relationship) {
|
||||
$name = $relationship->getName();
|
||||
switch ($name) {
|
||||
case 'avatar':
|
||||
\assert($relationship instanceof ToOneRelationshipInterface);
|
||||
$this->updateAvatarRelationship($relationship);
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('%s: Relationship %s cannot be modified', $this->getFlexType(), $name), 400);
|
||||
}
|
||||
}
|
||||
|
||||
$this->resetRelationships();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ToOneRelationshipInterface $relationship
|
||||
*/
|
||||
protected function updateAvatarRelationship(ToOneRelationshipInterface $relationship): void
|
||||
{
|
||||
$files = [];
|
||||
$avatar = $this->getAvatarImage();
|
||||
if ($avatar) {
|
||||
$files['avatar'][$avatar->filename] = null;
|
||||
}
|
||||
|
||||
$identifier = $relationship->getIdentifier();
|
||||
if ($identifier) {
|
||||
\assert($identifier instanceof MediaIdentifier);
|
||||
$object = $identifier->getObject();
|
||||
if ($object instanceof UploadedMediaObject) {
|
||||
$uploadedFile = $object->getUploadedFile();
|
||||
if ($uploadedFile) {
|
||||
$files['avatar'][$uploadedFile->getClientFilename()] = $uploadedFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->update([], $files);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Blueprint
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Grav\Common\GPM;
|
||||
use Exception;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\HTTP\Response;
|
||||
use Grav\Common\Inflector;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\HTTP\Response;
|
||||
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
|
||||
use Grav\Common\GPM\Response;
|
||||
use \Doctrine\Common\Cache\FilesystemCache;
|
||||
use RuntimeException;
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ use Grav\Common\Service\TaskServiceProvider;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Framework\DI\Container;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Grav\Framework\RequestHandler\Middlewares\MultipartRequestSupport;
|
||||
use Grav\Framework\RequestHandler\RequestHandler;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Grav\Framework\Session\Messages;
|
||||
@@ -117,6 +118,7 @@ class Grav extends Container
|
||||
* @var array All middleware processors that are processed in $this->process()
|
||||
*/
|
||||
protected $middleware = [
|
||||
'multipartRequestSupport',
|
||||
'initializeProcessor',
|
||||
'pluginsProcessor',
|
||||
'themesProcessor',
|
||||
@@ -259,6 +261,9 @@ class Grav extends Container
|
||||
|
||||
$container = new Container(
|
||||
[
|
||||
'multipartRequestSupport' => function () {
|
||||
return new MultipartRequestSupport();
|
||||
},
|
||||
'initializeProcessor' => function () {
|
||||
return new InitializeProcessor($this);
|
||||
},
|
||||
@@ -341,6 +346,23 @@ class Grav extends Container
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean any output buffers. Useful when exiting from the application.
|
||||
*
|
||||
* Please use $grav->close() and $grav->redirect() instead of calling this one!
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cleanOutputBuffers(): void
|
||||
{
|
||||
// Make sure nothing extra gets written to the response.
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
// Work around PHP bug #8218 (8.0.17 & 8.1.4).
|
||||
header_remove('Content-Encoding');
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates Grav request with a response.
|
||||
*
|
||||
@@ -351,10 +373,7 @@ class Grav extends Container
|
||||
*/
|
||||
public function close(ResponseInterface $response): void
|
||||
{
|
||||
// Make sure nothing extra gets written to the response.
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
$this->cleanOutputBuffers();
|
||||
|
||||
// Close the session.
|
||||
if (isset($this['session'])) {
|
||||
@@ -400,7 +419,7 @@ class Grav extends Container
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @return never-return
|
||||
* @deprecated 1.7 Do not use
|
||||
* @deprecated 1.7 Use $grav->close() instead.
|
||||
*/
|
||||
public function exit(ResponseInterface $response): void
|
||||
{
|
||||
|
||||
@@ -153,6 +153,8 @@ class LanguageCodes
|
||||
'vi' => [ 'name' => 'Vietnamese', 'nativeName' => 'Tiếng Việt' ],
|
||||
'wo' => [ 'name' => 'Wolof', 'nativeName' => 'Wolof' ],
|
||||
'xh' => [ 'name' => 'Xhosa', 'nativeName' => 'isiXhosa' ],
|
||||
'yi' => [ 'name' => 'Yiddish', 'nativeName' => 'ייִדיש', 'orientation' => 'rtl' ],
|
||||
'ydd' => [ 'name' => 'Yiddish', 'nativeName' => 'ייִדיש', 'orientation' => 'rtl' ],
|
||||
'zh' => [ 'name' => 'Chinese (Simplified)', 'nativeName' => '中文 (简体)' ],
|
||||
'zh-CN' => [ 'name' => 'Chinese (Simplified)', 'nativeName' => '中文 (简体)' ],
|
||||
'zh-TW' => [ 'name' => 'Chinese (Traditional)', 'nativeName' => '正體中文 (繁體)' ],
|
||||
|
||||
@@ -158,14 +158,6 @@ interface MediaObjectInterface extends \Grav\Framework\Media\Interfaces\MediaObj
|
||||
*/
|
||||
public function thumbnail($type = 'auto');
|
||||
|
||||
/**
|
||||
* Return URL to file.
|
||||
*
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function url($reset = true);
|
||||
|
||||
/**
|
||||
* Turn the current Medium into a Link
|
||||
*
|
||||
@@ -221,18 +213,6 @@ interface MediaObjectInterface extends \Grav\Framework\Media\Interfaces\MediaObj
|
||||
#[\ReturnTypeWillChange]
|
||||
public function __call($method, $args);
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @example $value = $this->get('this.is.my.nested.variable');
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $default Default value (or null).
|
||||
* @param string|null $separator Separator, defaults to '.'
|
||||
* @return mixed Value.
|
||||
*/
|
||||
public function get($name, $default = null, $separator = null);
|
||||
|
||||
/**
|
||||
* Set value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
|
||||
@@ -152,7 +152,7 @@ class Media extends AbstractMedia
|
||||
foreach ($iterator as $file => $info) {
|
||||
// Ignore folders and Markdown files.
|
||||
$filename = $info->getFilename();
|
||||
if (!$info->isFile() || $info->getExtension() === 'md' || $filename === 'frontmatter.yaml' || strpos($filename, '.') === 0) {
|
||||
if (!$info->isFile() || $info->getExtension() === 'md' || $filename === 'frontmatter.yaml' || $filename === 'media.json' || strpos($filename, '.') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -361,8 +361,8 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
|
||||
|
||||
// Scaling operations
|
||||
$scale = ($scale ?? $config->get('system.images.watermark.scale', 100)) / 100;
|
||||
$wwidth = $this->get('width') * $scale;
|
||||
$wheight = $this->get('height') * $scale;
|
||||
$wwidth = (int)$this->get('width') * $scale;
|
||||
$wheight = (int)$this->get('height') * $scale;
|
||||
$watermark->resize($wwidth, $wheight);
|
||||
|
||||
// Position operations
|
||||
@@ -377,11 +377,11 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
|
||||
break;
|
||||
|
||||
case 'bottom':
|
||||
$positionY = $this->get('height')-$wheight;
|
||||
$positionY = (int)$this->get('height')-$wheight;
|
||||
break;
|
||||
|
||||
case 'center':
|
||||
$positionY = ($this->get('height')/2) - ($wheight/2);
|
||||
$positionY = ((int)$this->get('height')/2) - ($wheight/2);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -392,11 +392,11 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
|
||||
break;
|
||||
|
||||
case 'right':
|
||||
$positionX = $this->get('width')-$wwidth;
|
||||
$positionX = (int)$this->get('width')-$wwidth;
|
||||
break;
|
||||
|
||||
case 'center':
|
||||
$positionX = ($this->get('width')/2) - ($wwidth/2);
|
||||
$positionX = ((int)$this->get('width')/2) - ($wwidth/2);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -622,7 +622,12 @@ class Page implements PageInterface
|
||||
$headers['Vary'] = 'Accept-Encoding';
|
||||
}
|
||||
|
||||
return $headers;
|
||||
|
||||
// Added new Headers event
|
||||
$headers_obj = (object) $headers;
|
||||
Grav::instance()->fireEvent('onPageHeaders', new Event(['headers' => $headers_obj]));
|
||||
|
||||
return (array)$headers_obj;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,6 +88,8 @@ class Pages
|
||||
/** @var string */
|
||||
protected $check_method;
|
||||
/** @var string */
|
||||
protected $simple_pages_hash;
|
||||
/** @var string */
|
||||
protected $pages_cache_id;
|
||||
/** @var bool */
|
||||
protected $initialized = false;
|
||||
@@ -100,6 +102,7 @@ class Pages
|
||||
/** @var string|null */
|
||||
protected static $home_route;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -1712,10 +1715,7 @@ class Pages
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
$pages_dir = $locator->findResource('page://');
|
||||
if (!is_string($pages_dir)) {
|
||||
throw new RuntimeException('Internal Error');
|
||||
}
|
||||
$pages_dirs = $this->getPagesPaths();
|
||||
|
||||
// Set active language
|
||||
$this->active_lang = $language->getActive();
|
||||
@@ -1731,16 +1731,17 @@ class Pages
|
||||
$hash = 0;
|
||||
break;
|
||||
case 'folder':
|
||||
$hash = Folder::lastModifiedFolder($pages_dir);
|
||||
$hash = Folder::lastModifiedFolder($pages_dirs);
|
||||
break;
|
||||
case 'hash':
|
||||
$hash = Folder::hashAllFiles($pages_dir);
|
||||
$hash = Folder::hashAllFiles($pages_dirs);
|
||||
break;
|
||||
default:
|
||||
$hash = Folder::lastModifiedFile($pages_dir);
|
||||
$hash = Folder::lastModifiedFile($pages_dirs);
|
||||
}
|
||||
|
||||
$this->pages_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum());
|
||||
$this->simple_pages_hash = json_encode($pages_dirs) . $hash . $config->checksum();
|
||||
$this->pages_cache_id = md5($this->simple_pages_hash . $language->getActive());
|
||||
|
||||
/** @var Cache $cache */
|
||||
$cache = $this->grav['cache'];
|
||||
@@ -1760,18 +1761,39 @@ class Pages
|
||||
$this->grav['debugger']->addMessage('Page cache disabled, rebuilding pages..');
|
||||
}
|
||||
|
||||
$this->resetPages($pages_dir);
|
||||
$this->resetPages($pages_dirs);
|
||||
}
|
||||
|
||||
protected function getPagesPaths(): array
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$locator = $grav['locator'];
|
||||
$paths = [];
|
||||
|
||||
$dirs = (array) $grav['config']->get('system.pages.dirs', ['page://']);
|
||||
foreach ($dirs as $dir) {
|
||||
$path = $locator->findResource($dir);
|
||||
if (file_exists($path)) {
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessible method to manually reset the pages cache
|
||||
*
|
||||
* @param string $pages_dir
|
||||
* @param array $pages_dirs
|
||||
*/
|
||||
public function resetPages($pages_dir): void
|
||||
public function resetPages(array $pages_dirs): void
|
||||
{
|
||||
$this->sort = [];
|
||||
$this->recurse($pages_dir);
|
||||
|
||||
foreach ($pages_dirs as $dir) {
|
||||
$this->recurse($dir);
|
||||
}
|
||||
|
||||
$this->buildRoutes();
|
||||
|
||||
// cache if needed
|
||||
@@ -1795,7 +1817,7 @@ class Pages
|
||||
* @throws RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function recurse($directory, PageInterface $parent = null)
|
||||
protected function recurse(string $directory, PageInterface $parent = null)
|
||||
{
|
||||
$directory = rtrim($directory, DS);
|
||||
$page = new Page;
|
||||
@@ -2177,7 +2199,7 @@ class Pages
|
||||
* @param array $list
|
||||
* @return array
|
||||
*/
|
||||
protected function arrayShuffle($list)
|
||||
protected function arrayShuffle(array $list): array
|
||||
{
|
||||
$keys = array_keys($list);
|
||||
shuffle($keys);
|
||||
@@ -2193,7 +2215,7 @@ class Pages
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getVersion()
|
||||
protected function getVersion(): string
|
||||
{
|
||||
return $this->directory ? 'flex' : 'regular';
|
||||
}
|
||||
@@ -2204,10 +2226,20 @@ class Pages
|
||||
* this is particularly useful to know if pages have changed and you want
|
||||
* to sync another cache with pages cache - works best in `onPagesInitialized()`
|
||||
*
|
||||
* @return string
|
||||
* @return null|string
|
||||
*/
|
||||
public function getPagesCacheId()
|
||||
public function getPagesCacheId(): ?string
|
||||
{
|
||||
return $this->pages_cache_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the simple pages hash that is not md5 encoded, and isn't specific to language
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getSimplePagesHash(): ?string
|
||||
{
|
||||
return $this->simple_pages_hash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +251,8 @@ class InitializeProcessor extends ProcessorBase
|
||||
$log->popHandler();
|
||||
|
||||
$facility = $config->get('system.log.syslog.facility', 'local6');
|
||||
$logHandler = new SyslogHandler('grav', $facility);
|
||||
$tag = $config->get('system.log.syslog.tag', 'grav');
|
||||
$logHandler = new SyslogHandler($tag, $facility);
|
||||
$formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
|
||||
$logHandler->setFormatter($formatter);
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ class Security
|
||||
*/
|
||||
public static function detectXssFromPages(Pages $pages, $route = true, callable $status = null)
|
||||
{
|
||||
$routes = $pages->routes();
|
||||
$routes = $pages->getList(null, 0, true);
|
||||
|
||||
// Remove duplicate for homepage
|
||||
unset($routes['/']);
|
||||
@@ -110,26 +110,23 @@ class Security
|
||||
'steps' => count($routes),
|
||||
]);
|
||||
|
||||
foreach ($routes as $path) {
|
||||
foreach (array_keys($routes) as $route) {
|
||||
$status && $status([
|
||||
'type' => 'progress',
|
||||
]);
|
||||
|
||||
try {
|
||||
$page = $pages->get($path);
|
||||
$page = $pages->find($route);
|
||||
if ($page->exists()) {
|
||||
// call the content to load/cache it
|
||||
$header = (array) $page->header();
|
||||
$content = $page->value('content');
|
||||
|
||||
// call the content to load/cache it
|
||||
$header = (array) $page->header();
|
||||
$content = $page->value('content');
|
||||
$data = ['header' => $header, 'content' => $content];
|
||||
$results = static::detectXssFromArray($data);
|
||||
|
||||
$data = ['header' => $header, 'content' => $content];
|
||||
$results = static::detectXssFromArray($data);
|
||||
|
||||
if (!empty($results)) {
|
||||
if ($route) {
|
||||
$list[$page->route()] = $results;
|
||||
} else {
|
||||
$list[$page->filePathClean()] = $results;
|
||||
if (!empty($results)) {
|
||||
$list[$page->rawRoute()] = $results;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
@@ -222,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 = [
|
||||
@@ -245,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,9 @@ class FlexServiceProvider implements ServiceProviderInterface
|
||||
'options' => [
|
||||
'file' => 'user',
|
||||
'pattern' => '{FOLDER}/{KEY:2}/{KEY}/{FILE}{EXT}',
|
||||
'key' => 'storage_key'
|
||||
'key' => 'storage_key',
|
||||
'indexed' => true,
|
||||
'case_sensitive' => false
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -107,7 +109,9 @@ class FlexServiceProvider implements ServiceProviderInterface
|
||||
'class' => UserFileStorage::class,
|
||||
'options' => [
|
||||
'pattern' => '{FOLDER}/{KEY}{EXT}',
|
||||
'key' => 'username'
|
||||
'key' => 'username',
|
||||
'indexed' => true,
|
||||
'case_sensitive' => false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Grav\Common\Twig\Extension;
|
||||
|
||||
use CallbackFilterIterator;
|
||||
use Cron\CronExpression;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Data;
|
||||
@@ -41,6 +42,7 @@ use JsonSerializable;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Traversable;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\Extension\GlobalsInterface;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
@@ -145,6 +147,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
new TwigFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
|
||||
new TwigFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
|
||||
new TwigFilter('nicecron', [$this, 'niceCronFilter']),
|
||||
new TwigFilter('replace_last', [$this, 'replaceLastFilter']),
|
||||
|
||||
// Translations
|
||||
new TwigFilter('t', [$this, 'translate'], ['needs_environment' => true]),
|
||||
@@ -166,6 +169,9 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
// PHP methods
|
||||
new TwigFilter('count', 'count'),
|
||||
new TwigFilter('array_diff', 'array_diff'),
|
||||
|
||||
// Security fix
|
||||
new TwigFilter('filter', [$this, 'filterFilter'], ['needs_environment' => true]),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -194,6 +200,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
new TwigFunction('gist', [$this, 'gistFunc']),
|
||||
new TwigFunction('nonce_field', [$this, 'nonceFieldFunc']),
|
||||
new TwigFunction('pathinfo', 'pathinfo'),
|
||||
new TwigFunction('parseurl', 'parse_url'),
|
||||
new TwigFunction('random_string', [$this, 'randomStringFunc']),
|
||||
new TwigFunction('repeat', [$this, 'repeatFunc']),
|
||||
new TwigFunction('regex_replace', [$this, 'regexReplace']),
|
||||
@@ -547,6 +554,21 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
return $cron->getText('en');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|mixed $str
|
||||
* @param string $search
|
||||
* @param string $replace
|
||||
* @return string|mixed
|
||||
*/
|
||||
public function replaceLastFilter($str, $search, $replace)
|
||||
{
|
||||
if (is_string($str) && ($pos = mb_strrpos($str, $search)) !== false) {
|
||||
$str = mb_substr($str, 0, $pos) . $replace . mb_substr($str, $pos + mb_strlen($search));
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cron object for a crontab 'at' format
|
||||
*
|
||||
@@ -1659,4 +1681,20 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
return is_string($var);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Environment $env
|
||||
* @param array $array
|
||||
* @param callable|string $arrow
|
||||
* @return array|CallbackFilterIterator
|
||||
* @throws RuntimeError
|
||||
*/
|
||||
function filterFilter(Environment $env, $array, $arrow)
|
||||
{
|
||||
if (is_string($arrow) && Utils::isDangerousFunction($arrow)) {
|
||||
throw new RuntimeError('Twig |filter("' . $arrow . '") is not allowed.');
|
||||
}
|
||||
|
||||
return twig_array_filter($env, $array, $arrow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,11 +112,12 @@ class UserCollection implements UserCollectionInterface
|
||||
|
||||
// If not found, try the fields
|
||||
if (!$user->exists()) {
|
||||
$query = mb_strtolower($query);
|
||||
foreach ($files as $file) {
|
||||
if (Utils::endsWith($file, YAML_EXT)) {
|
||||
$find_user = $this->load(trim(Utils::pathinfo($file, PATHINFO_FILENAME)));
|
||||
foreach ($fields as $field) {
|
||||
if (isset($find_user[$field]) && $find_user[$field] === $query) {
|
||||
if (isset($find_user[$field]) && mb_strtolower($find_user[$field]) === $query) {
|
||||
return $find_user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ abstract class Utils
|
||||
|
||||
$resource = false;
|
||||
if (static::contains((string)$input, '://')) {
|
||||
// Url contains a scheme (https:// , user:// etc).
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
|
||||
@@ -134,6 +135,16 @@ abstract class Utils
|
||||
$resource = $locator->findResource($input, false);
|
||||
}
|
||||
} else {
|
||||
// Just a path.
|
||||
/** @var Pages $pages */
|
||||
$pages = $grav['pages'];
|
||||
|
||||
// Is this a page?
|
||||
$page = $pages->find($input, true);
|
||||
if ($page && $page->routable()) {
|
||||
return $page->url($domain);
|
||||
}
|
||||
|
||||
$root = preg_quote($uri->rootUrl(), '#');
|
||||
$pattern = '#(' . $root . '$|' . $root . '/)#';
|
||||
if (!empty($root) && preg_match($pattern, $input, $matches)) {
|
||||
@@ -657,18 +668,17 @@ abstract class Utils
|
||||
*/
|
||||
public static function download($file, $force_download = true, $sec = 0, $bytes = 1024, array $options = [])
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
if (file_exists($file)) {
|
||||
// fire download event
|
||||
Grav::instance()->fireEvent('onBeforeDownload', new Event(['file' => $file, 'options' => &$options]));
|
||||
$grav->fireEvent('onBeforeDownload', new Event(['file' => $file, 'options' => &$options]));
|
||||
|
||||
$file_parts = static::pathinfo($file);
|
||||
$mimetype = $options['mime'] ?? static::getMimeByExtension($file_parts['extension']);
|
||||
$size = filesize($file); // File size
|
||||
|
||||
// clean all buffers
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
$grav->cleanOutputBuffers();
|
||||
|
||||
// required for IE, otherwise Content-Disposition may be ignored
|
||||
if (ini_get('zlib.output_compression')) {
|
||||
@@ -703,8 +713,8 @@ abstract class Utils
|
||||
$new_length = $size;
|
||||
header('Content-Length: ' . $size);
|
||||
|
||||
if (Grav::instance()['config']->get('system.cache.enabled')) {
|
||||
$expires = $options['expires'] ?? Grav::instance()['config']->get('system.pages.expires');
|
||||
if ($grav['config']->get('system.cache.enabled')) {
|
||||
$expires = $options['expires'] ?? $grav['config']->get('system.pages.expires');
|
||||
if ($expires > 0) {
|
||||
$expires_date = gmdate('D, d M Y H:i:s T', time() + $expires);
|
||||
header('Cache-Control: max-age=' . $expires);
|
||||
|
||||
Reference in New Issue
Block a user