màj
This commit is contained in:
parent
8bd1b83c5f
commit
4ca5c9f82d
80
CHANGELOG.md
80
CHANGELOG.md
@ -1,3 +1,83 @@
|
||||
# v1.7.21
|
||||
## 09/14/2021
|
||||
|
||||
1. [](#new)
|
||||
* Added `|yaml` filter to convert input to YAML
|
||||
* Added `route` and `request` to `onPageNotFound` event
|
||||
* Added file upload/remove support for `Flex Forms`
|
||||
* Added support for `flex-required@: not exists` and `flex-required@: '!exists'` in blueprints
|
||||
* Added `$object->getOriginalData()` to get flex objects data before it was modified with `update()`
|
||||
* Throwing exceptions from Twig templates fires `onDisplayErrorPage.[code]` event allowing better error pages
|
||||
2. [](#improved)
|
||||
* Use a simplified text-based `cron` field for scheduler
|
||||
* Add timestamp to logging output of scheduler jobs to see when they ran
|
||||
3. [](#bugfix)
|
||||
* Fixed escaping in PageIndex::getLevelListing()
|
||||
* Fixed validation of `number` type [#3433](https://github.com/getgrav/grav/issues/3433)
|
||||
* Fixed excessive `security.yaml` file creation [#3432](https://github.com/getgrav/grav/issues/3432)
|
||||
* Fixed incorrect port :0 with nginx unix socket setup [#3439](https://github.com/getgrav/grav/issues/3439)
|
||||
* Fixed `Session::setFlashCookieObject()` to use the same options as the main session cookie
|
||||
|
||||
# v1.7.20
|
||||
## 09/01/2021
|
||||
|
||||
2. [](#improved)
|
||||
* Added support for `task` and `action` inside JSON request body
|
||||
|
||||
# v1.7.19
|
||||
## 08/31/2021
|
||||
|
||||
1. [](#new)
|
||||
* Include active form and request in `onPageTask` and `onPageAction` events (defaults to `null`)
|
||||
* Added `UserObject::$authorizeCallable` to allow `$user->authorize()` customization
|
||||
2. [](#improved)
|
||||
* Added meta support for `UploadedFile` class
|
||||
* Added support for multiple mime-types per file extension [#3422](https://github.com/getgrav/grav/issues/3422)
|
||||
* Added `setCurrent()` method to Page Collection [#3398](https://github.com/getgrav/grav/pull/3398)
|
||||
* Initialize `$grav['uri']` before session
|
||||
3. [](#bugfix)
|
||||
* Fixed `Warning: Undefined array key "SERVER_SOFTWARE" in index.php` [#3408](https://github.com/getgrav/grav/issues/3408)
|
||||
* Fixed error in `loadDirectoryConfig()` if configuration hasn't been saved [#3409](https://github.com/getgrav/grav/issues/3409)
|
||||
* Fixed GPM not using non-standard cache path [#3410](https://github.com/getgrav/grav/issues/3410)
|
||||
* Fixed broken `environment://` stream when it doesn't have configuration
|
||||
* Fixed `Flex Object` missing key field value when using `FolderStorage`
|
||||
* Fixed broken Twig try tag when catch has not been defined or is empty
|
||||
* Fixed `FlexForm` serialization
|
||||
* Fixed form validation for numeric values in PHP 8
|
||||
* Fixed `flex-options@` in blueprints duplicating items in array
|
||||
* Fixed wrong form issue with flex objects after cache clear
|
||||
* Fixed Flex object types not implementing `MediaInterface`
|
||||
* Fixed issue with `svgImageFunction()` that was causing broken output
|
||||
|
||||
# v1.7.18
|
||||
## 07/19/2021
|
||||
|
||||
1. [](#improved)
|
||||
* Added support for loading Flex Directory configuration from main configuration
|
||||
* Move SVGs that cannot be sanitized to quarantine folder under `log://quarantine`
|
||||
* Added support for CloudFlare-forwarded client IP in the `URI::ip()` method
|
||||
1. [](#bugfix)
|
||||
* Fixed error when using Flex `SimpleStorage` with no entries
|
||||
* Fixed page search to include slug field [#3316](https://github.com/getgrav/grav/issues/3316)
|
||||
* Fixed Admin becoming unusable when GPM cannot be reached [#3383](https://github.com/getgrav/grav/issues/3383)
|
||||
* Fixed `Failed to save entry: Forbidden` when moving a page to a visible page [#3389](https://github.com/getgrav/grav/issues/3389)
|
||||
* Better support for Symfony local server on linux [#3400](https://github.com/getgrav/grav/pull/3400)
|
||||
* Fixed `open_basedir()` error with some forms
|
||||
|
||||
# v1.7.17
|
||||
## 06/15/2021
|
||||
|
||||
1. [](#new)
|
||||
* Interface `FlexDirectoryInterface` now extends `FlexAuthorizeInterface`
|
||||
1. [](#improved)
|
||||
* Allow to unset an asset attribute by specifying null (ie, `'defer': null`)
|
||||
* Support specifying custom attributes to assets in a collection [Read more](https://learn.getgrav.org/17/themes/asset-manager#collections-with-attributes?target=_blank) [#3358](https://github.com/getgrav/grav/issues/3358)
|
||||
* File `frontmatter.yaml` isn't part of media, ignore it
|
||||
* Switched default `JQuery` collection to use 3.x rather than 2.x
|
||||
1. [](#bugfix)
|
||||
* Fixed missing styles when CSS/JS Pipeline is used and `asset://` folder is missing
|
||||
* Fixed permission check when moving a page [#3382](https://github.com/getgrav/grav/issues/3382)
|
||||
|
||||
# v1.7.16
|
||||
## 06/02/2021
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
#  Grav
|
||||
|
||||
[](https://github.com/phpstan/phpstan)
|
||||
[](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad)
|
||||
[](https://chat.getgrav.org)
|
||||
[](https://github.com/getgrav/grav/actions?query=workflow%3A%22PHP+Tests%22) [](#backers) [](#sponsors)
|
||||
|
||||
|
607
composer.lock
generated
607
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -17,8 +17,8 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli-server') {
|
||||
$symfony_server = stripos(getenv('_'), 'symfony') !== false || stripos($_SERVER['SERVER_SOFTWARE'], 'symfony
|
||||
') !== false;
|
||||
$symfony_server = stripos(getenv('_'), 'symfony') !== false || stripos($_SERVER['SERVER_SOFTWARE'] ?? '', 'symfony') !== false || stripos($_ENV['SERVER_SOFTWARE'] ?? '', 'symfony') !== false;
|
||||
|
||||
if (!isset($_SERVER['PHP_CLI_ROUTER']) && !$symfony_server) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
|
@ -47,7 +47,8 @@ form:
|
||||
label: PLUGIN_ADMIN.EXTRA_ARGUMENTS
|
||||
placeholder: '-lah'
|
||||
.at:
|
||||
type: cron
|
||||
type: text
|
||||
wrapper_classes: cron-selector
|
||||
label: PLUGIN_ADMIN.SCHEDULER_RUNAT
|
||||
help: PLUGIN_ADMIN.SCHEDULER_RUNAT_HELP
|
||||
placeholder: '* * * * *'
|
||||
|
@ -184,9 +184,9 @@ config:
|
||||
# Fields to be searched
|
||||
fields:
|
||||
- key
|
||||
- slug
|
||||
- menu
|
||||
- title
|
||||
- name
|
||||
|
||||
blueprints:
|
||||
configure:
|
||||
|
@ -28,6 +28,10 @@ types:
|
||||
type: image
|
||||
thumb: media/thumb-webp.png
|
||||
mime: image/webp
|
||||
avif:
|
||||
type: image
|
||||
thumb: media/thumb.png
|
||||
mime: image/avif
|
||||
gif:
|
||||
type: animated
|
||||
thumb: media/thumb-gif.png
|
||||
@ -91,7 +95,7 @@ types:
|
||||
aif:
|
||||
type: audio
|
||||
thumb: media/thumb-aif.png
|
||||
mime: audio/aif
|
||||
mime: audio/aiff
|
||||
txt:
|
||||
type: file
|
||||
thumb: media/thumb-txt.png
|
||||
@ -207,7 +211,7 @@ types:
|
||||
js:
|
||||
type: file
|
||||
thumb: media/thumb-js.png
|
||||
mime: application/javascript
|
||||
mime: text/javascript
|
||||
json:
|
||||
type: file
|
||||
thumb: media/thumb-json.png
|
||||
|
1986
system/config/mime.yaml
Normal file
1986
system/config/mime.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@ -96,7 +96,7 @@ cache:
|
||||
purge_at: '0 4 * * *' # How often to purge old file cache (using new scheduler)
|
||||
clear_at: '0 3 * * *' # How often to clear cache (using new scheduler)
|
||||
clear_job_type: 'standard' # Type to clear when processing the scheduled clear job `standard`|`all`
|
||||
clear_images_by_default: false # By default grav will include processed images in cache clear, this can be disabled
|
||||
clear_images_by_default: false # By default grav does not include processed images in cache clear, this can be enabled
|
||||
cli_compatibility: false # Ensures only non-volatile drivers are used (file, redis, memcache, etc.)
|
||||
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
|
||||
gzip: false # GZip compress the page output
|
||||
@ -131,7 +131,7 @@ assets: # Configuration for Assets Mana
|
||||
enable_asset_timestamp: false # Enable asset timestamps
|
||||
enable_asset_sri: false # Enable asset SRI
|
||||
collections:
|
||||
jquery: system://assets/jquery/jquery-2.x.min.js
|
||||
jquery: system://assets/jquery/jquery-3.x.min.js
|
||||
|
||||
errors:
|
||||
display: 0 # Display either (1) Full backtrace | (0) Simple Error | (-1) System Error
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.7.16');
|
||||
define('GRAV_VERSION', '1.7.21');
|
||||
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
|
||||
define('GRAV_TESTING', false);
|
||||
|
||||
|
@ -110,7 +110,7 @@ class Assets extends PropertyObject
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
$this->assets_dir = $locator->findResource('asset://') . DS;
|
||||
$this->assets_dir = $locator->findResource('asset://');
|
||||
$this->assets_url = $locator->findResource('asset://', false);
|
||||
|
||||
$this->config($asset_config);
|
||||
@ -164,10 +164,19 @@ class Assets extends PropertyObject
|
||||
|
||||
// More than one asset
|
||||
if (is_array($asset)) {
|
||||
foreach ($asset as $a) {
|
||||
array_shift($args);
|
||||
$args = array_merge([$a], $args);
|
||||
call_user_func_array([$this, 'add'], $args);
|
||||
foreach ($asset as $index => $location) {
|
||||
$params = array_slice($args, 1);
|
||||
if (is_array($location)) {
|
||||
$params = array_shift($params);
|
||||
if (is_numeric($params)) {
|
||||
$params = [ 'priority' => $params ];
|
||||
}
|
||||
$params = [array_replace_recursive([], $location, $params)];
|
||||
$location = $index;
|
||||
}
|
||||
|
||||
$params = array_merge([$location], $params);
|
||||
call_user_func_array([$this, 'add'], $params);
|
||||
}
|
||||
} elseif (isset($this->collections[$asset])) {
|
||||
array_shift($args);
|
||||
@ -201,8 +210,13 @@ class Assets extends PropertyObject
|
||||
protected function addType($collection, $type, $asset, $options)
|
||||
{
|
||||
if (is_array($asset)) {
|
||||
foreach ($asset as $a) {
|
||||
$this->addType($collection, $type, $a, $options);
|
||||
foreach ($asset as $index => $location) {
|
||||
$assetOptions = $options;
|
||||
if (is_array($location)) {
|
||||
$assetOptions = array_replace_recursive([], $options, $location);
|
||||
$location = $index;
|
||||
}
|
||||
$this->addType($collection, $type, $location, $assetOptions);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -9,9 +9,9 @@
|
||||
|
||||
namespace Grav\Common\Assets;
|
||||
|
||||
use Grav\Common\Assets\BaseAsset;
|
||||
use Grav\Common\Assets\Traits\AssetUtilsTrait;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
@ -88,7 +88,14 @@ class Pipeline extends PropertyObject
|
||||
$uri = Grav::instance()['uri'];
|
||||
|
||||
$this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
|
||||
$this->assets_dir = $locator->findResource('asset://') . DS;
|
||||
$this->assets_dir = $locator->findResource('asset://');
|
||||
if (!$this->assets_dir) {
|
||||
// Attempt to create assets folder if it doesn't exist yet.
|
||||
$this->assets_dir = $locator->findResource('asset://', true, true);
|
||||
Folder::mkdir($this->assets_dir);
|
||||
$locator->clearCache();
|
||||
}
|
||||
|
||||
$this->assets_url = $locator->findResource('asset://', false);
|
||||
}
|
||||
|
||||
@ -119,10 +126,9 @@ class Pipeline extends PropertyObject
|
||||
$file = $uid . '.css';
|
||||
$relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
|
||||
|
||||
$buffer = null;
|
||||
|
||||
if (file_exists($this->assets_dir . $file)) {
|
||||
$buffer = file_get_contents($this->assets_dir . $file) . "\n";
|
||||
$filepath = "{$this->assets_dir}/{$file}";
|
||||
if (file_exists($filepath)) {
|
||||
$buffer = file_get_contents($filepath) . "\n";
|
||||
} else {
|
||||
//if nothing found get out of here!
|
||||
if (empty($assets)) {
|
||||
@ -141,7 +147,7 @@ class Pipeline extends PropertyObject
|
||||
|
||||
// Write file
|
||||
if (trim($buffer) !== '') {
|
||||
file_put_contents($this->assets_dir . $file, $buffer);
|
||||
file_put_contents($filepath, $buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,10 +188,9 @@ class Pipeline extends PropertyObject
|
||||
$file = $uid . '.js';
|
||||
$relative_path = "{$this->base_url}{$this->assets_url}/{$file}";
|
||||
|
||||
$buffer = null;
|
||||
|
||||
if (file_exists($this->assets_dir . $file)) {
|
||||
$buffer = file_get_contents($this->assets_dir . $file) . "\n";
|
||||
$filepath = "{$this->assets_dir}/{$file}";
|
||||
if (file_exists($filepath)) {
|
||||
$buffer = file_get_contents($filepath) . "\n";
|
||||
} else {
|
||||
//if nothing found get out of here!
|
||||
if (empty($assets)) {
|
||||
@ -204,7 +209,7 @@ class Pipeline extends PropertyObject
|
||||
|
||||
// Write file
|
||||
if (trim($buffer) !== '') {
|
||||
file_put_contents($this->assets_dir . $file, $buffer);
|
||||
file_put_contents($filepath, $buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,6 +156,10 @@ trait AssetUtilsTrait
|
||||
$no_key = ['loading'];
|
||||
|
||||
foreach ($this->attributes as $key => $value) {
|
||||
if ($value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_numeric($key)) {
|
||||
$key = $value;
|
||||
}
|
||||
|
@ -41,6 +41,9 @@ class Setup extends Data
|
||||
*/
|
||||
public static $environment;
|
||||
|
||||
/** @var string */
|
||||
public static $securityFile = 'config://security.yaml';
|
||||
|
||||
/** @var array */
|
||||
protected $streams = [
|
||||
'user' => [
|
||||
@ -390,12 +393,19 @@ class Setup extends Data
|
||||
|
||||
if (!$locator->findResource('environment://config', true)) {
|
||||
// If environment does not have its own directory, remove it from the lookup.
|
||||
$this->set('streams.schemes.environment.prefixes', ['config' => []]);
|
||||
$prefixes = $this->get('streams.schemes.environment.prefixes');
|
||||
$prefixes['config'] = [];
|
||||
|
||||
$this->set('streams.schemes.environment.prefixes', $prefixes);
|
||||
$this->initializeLocator($locator);
|
||||
}
|
||||
|
||||
// Create security.yaml if it doesn't exist.
|
||||
$filename = $locator->findResource('config://security.yaml', true, true);
|
||||
// Create security.yaml salt if it doesn't exist into existing configuration environment if possible.
|
||||
$securityFile = basename(static::$securityFile);
|
||||
$securityFolder = substr(static::$securityFile, 0, -\strlen($securityFile));
|
||||
$securityFolder = $locator->findResource($securityFolder, true) ?: $locator->findResource($securityFolder, true, true);
|
||||
$filename = "{$securityFolder}/{$securityFile}";
|
||||
|
||||
$security_file = CompiledYamlFile::instance($filename);
|
||||
$security_content = (array)$security_file->content();
|
||||
|
||||
|
@ -519,17 +519,32 @@ class Validation
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($params['min']) && $value < $params['min']) {
|
||||
return false;
|
||||
$value = (float)$value;
|
||||
|
||||
$min = 0;
|
||||
if (isset($params['min'])) {
|
||||
$min = (float)$params['min'];
|
||||
if ($value < $min) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['max']) && $value > $params['max']) {
|
||||
return false;
|
||||
if (isset($params['max'])) {
|
||||
$max = (float)$params['max'];
|
||||
if ($value > $max) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$min = $params['min'] ?? 0;
|
||||
if (isset($params['step'])) {
|
||||
$step = (float)$params['step'];
|
||||
// Count of how many steps we are above/below the minimum value.
|
||||
$pos = ($value - $min) / $step;
|
||||
|
||||
return !(isset($params['step']) && fmod($value - $min, $params['step']) === 0);
|
||||
return is_int(static::filterNumber($pos, $params, $field));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,6 +13,7 @@ namespace Grav\Common\Flex;
|
||||
|
||||
use Grav\Common\Flex\Traits\FlexGravTrait;
|
||||
use Grav\Common\Flex\Traits\FlexObjectTrait;
|
||||
use Grav\Common\Media\Interfaces\MediaInterface;
|
||||
use Grav\Framework\Flex\Traits\FlexMediaTrait;
|
||||
use function is_array;
|
||||
|
||||
@ -21,7 +22,7 @@ use function is_array;
|
||||
*
|
||||
* @package Grav\Common\Flex
|
||||
*/
|
||||
abstract class FlexObject extends \Grav\Framework\Flex\FlexObject
|
||||
abstract class FlexObject extends \Grav\Framework\Flex\FlexObject implements MediaInterface
|
||||
{
|
||||
use FlexGravTrait;
|
||||
use FlexObjectTrait;
|
||||
|
@ -192,6 +192,14 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
|
||||
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current page.
|
||||
*/
|
||||
public function setCurrent(string $path): void
|
||||
{
|
||||
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return previous item.
|
||||
*
|
||||
|
@ -674,12 +674,12 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
|
||||
$count = $filters ? $tmp->filterBy($filters, true)->count() : null;
|
||||
$route = $child->getRoute();
|
||||
$payload = [
|
||||
'item-key' => basename($child->rawRoute() ?? $child->getKey()),
|
||||
'item-key' => htmlspecialchars(basename($child->rawRoute() ?? $child->getKey())),
|
||||
'icon' => $icon,
|
||||
'title' => htmlspecialchars($child->menu()),
|
||||
'route' => [
|
||||
'display' => ($route ? ($route->toString(false) ?: '/') : null) ?? '',
|
||||
'raw' => $child->rawRoute(),
|
||||
'display' => htmlspecialchars(($route ? ($route->toString(false) ?: '/') : null) ?? ''),
|
||||
'raw' => htmlspecialchars($child->rawRoute()),
|
||||
],
|
||||
'modified' => $this->jsDate($child->modified()),
|
||||
'child_count' => $child_count ?: null,
|
||||
|
@ -262,6 +262,24 @@ class PageObject extends FlexPageObject
|
||||
$this->getFlexDirectory()->reloadIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserInterface|null $user
|
||||
*/
|
||||
public function check(UserInterface $user = null): void
|
||||
{
|
||||
parent::check($user);
|
||||
|
||||
if ($user && $this->isMoved()) {
|
||||
$parentKey = $this->getProperty('parent_key');
|
||||
|
||||
/** @var PageObject|null $parent */
|
||||
$parent = $this->getFlexDirectory()->getObject($parentKey, 'storage_key');
|
||||
if (!$parent || !$parent->isAuthorized('create', null, $user)) {
|
||||
throw new \RuntimeException('Forbidden', 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|bool $reorder
|
||||
* @return FlexObject|FlexObjectInterface
|
||||
@ -357,6 +375,19 @@ class PageObject extends FlexPageObject
|
||||
return parent::isAuthorizedOverride($user, $action, $scope, $isMe);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function isMoved(): bool
|
||||
{
|
||||
$storageKey = $this->getMasterKey();
|
||||
$filesystem = Filesystem::getInstance(false);
|
||||
$oldParentKey = ltrim($filesystem->dirname("/{$storageKey}"), '/');
|
||||
$newParentKey = $this->getProperty('parent_key');
|
||||
|
||||
return $this->exists() && $oldParentKey !== $newParentKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ordering
|
||||
* @return PageCollection|null
|
||||
@ -364,10 +395,7 @@ class PageObject extends FlexPageObject
|
||||
protected function reorderSiblings(array $ordering)
|
||||
{
|
||||
$storageKey = $this->getMasterKey();
|
||||
$filesystem = Filesystem::getInstance(false);
|
||||
$oldParentKey = ltrim($filesystem->dirname("/{$storageKey}"), '/');
|
||||
$newParentKey = $this->getProperty('parent_key');
|
||||
$isMoved = $this->exists() && $oldParentKey !== $newParentKey;
|
||||
$isMoved = $this->isMoved();
|
||||
$order = !$isMoved ? $this->order() : false;
|
||||
if ($order !== false) {
|
||||
$order = (int)$order;
|
||||
|
@ -41,6 +41,14 @@ class UserGroupObject extends FlexObject implements UserGroupInterface
|
||||
] + parent::getCachedMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->getProperty('readableName');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks user authorization to the action.
|
||||
*
|
||||
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Grav\Common\Flex\Types\Users;
|
||||
|
||||
use Closure;
|
||||
use Countable;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
@ -31,6 +32,7 @@ use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\User\Traits\UserTrait;
|
||||
use Grav\Framework\File\Formatter\JsonFormatter;
|
||||
use Grav\Framework\File\Formatter\YamlFormatter;
|
||||
use Grav\Framework\Filesystem\Filesystem;
|
||||
use Grav\Framework\Flex\Flex;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\Storage\FileStorage;
|
||||
@ -75,6 +77,9 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
||||
use UserTrait;
|
||||
use UserObjectLegacyTrait;
|
||||
|
||||
/** @var Closure|null */
|
||||
static public $authorizeCallable;
|
||||
|
||||
/** @var array|null */
|
||||
protected $_uploads_original;
|
||||
/** @var FileInterface|null */
|
||||
@ -259,6 +264,15 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
||||
}
|
||||
}
|
||||
|
||||
$authorizeCallable = static::$authorizeCallable;
|
||||
if ($authorizeCallable instanceof Closure) {
|
||||
$authorizeCallable->bindTo($this);
|
||||
$authorized = $authorizeCallable($action, $scope);
|
||||
if (is_bool($authorized)) {
|
||||
return $authorized;
|
||||
}
|
||||
}
|
||||
|
||||
// Check user access.
|
||||
$access = $this->getAccess();
|
||||
$authorized = $access->authorize($action, $scope);
|
||||
@ -292,6 +306,14 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserGroupIndex
|
||||
*/
|
||||
public function getRoles(): UserGroupIndex
|
||||
{
|
||||
return $this->getGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert object into an array.
|
||||
*
|
||||
@ -689,6 +711,7 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
* @return void
|
||||
*/
|
||||
protected function setUpdatedMedia(array $files): void
|
||||
{
|
||||
@ -700,9 +723,12 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
||||
return;
|
||||
}
|
||||
|
||||
$filesystem = Filesystem::getInstance(false);
|
||||
|
||||
$list = [];
|
||||
$list_original = [];
|
||||
foreach ($files as $field => $group) {
|
||||
// Ignore files without a field.
|
||||
if ($field === '') {
|
||||
continue;
|
||||
}
|
||||
@ -724,7 +750,7 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
// Check file upload against media limits.
|
||||
// Check file upload against media limits (except for max size).
|
||||
$filename = $media->checkUploadedFile($file, $filename, ['filesize' => 0] + $settings);
|
||||
}
|
||||
|
||||
@ -748,15 +774,19 @@ class UserObject extends FlexObject implements UserInterface, Countable
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate path without the retina scaling factor.
|
||||
$realpath = $filesystem->pathname($filepath) . str_replace(['@3x', '@2x'], '', basename($filepath));
|
||||
|
||||
$list[$filename] = [$file, $settings];
|
||||
|
||||
$path = str_replace('.', "\n", $field);
|
||||
if (null !== $data) {
|
||||
$data['name'] = $filename;
|
||||
$data['path'] = $filepath;
|
||||
|
||||
$this->setNestedProperty("{$field}\n{$filepath}", $data, "\n");
|
||||
$this->setNestedProperty("{$path}\n{$realpath}", $data, "\n");
|
||||
} else {
|
||||
$this->unsetNestedProperty("{$field}\n{$filepath}", "\n");
|
||||
$this->unsetNestedProperty("{$path}\n{$realpath}", "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,11 @@ class GPM extends Iterator
|
||||
/** @var Remote\Packages|null Remote available Packages */
|
||||
private $repository;
|
||||
/** @var Remote\GravCore|null Remove Grav Packages */
|
||||
public $grav;
|
||||
private $grav;
|
||||
/** @var bool */
|
||||
private $refresh;
|
||||
/** @var callable|null */
|
||||
private $callback;
|
||||
|
||||
/** @var array Internal cache */
|
||||
protected $cache;
|
||||
@ -55,13 +59,45 @@ class GPM extends Iterator
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
Folder::create(CACHE_DIR . '/gpm');
|
||||
|
||||
$this->cache = [];
|
||||
$this->installed = new Local\Packages();
|
||||
try {
|
||||
$this->repository = new Remote\Packages($refresh, $callback);
|
||||
$this->grav = new Remote\GravCore($refresh, $callback);
|
||||
} catch (Exception $e) {
|
||||
$this->refresh = $refresh;
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter method
|
||||
*
|
||||
* @param string $offset Asset name value
|
||||
* @return mixed Asset value
|
||||
*/
|
||||
public function __get($offset)
|
||||
{
|
||||
switch ($offset) {
|
||||
case 'grav':
|
||||
return $this->getGrav();
|
||||
}
|
||||
|
||||
return parent::__get($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to determine if the attribute is set
|
||||
*
|
||||
* @param string $offset Asset name value
|
||||
* @return bool True if the value is set
|
||||
*/
|
||||
public function __isset($offset)
|
||||
{
|
||||
switch ($offset) {
|
||||
case 'grav':
|
||||
return $this->getGrav() !== null;
|
||||
}
|
||||
|
||||
return parent::__isset($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -266,11 +302,12 @@ class GPM extends Iterator
|
||||
{
|
||||
$items = [];
|
||||
|
||||
if (null === $this->repository) {
|
||||
$repository = $this->getRepository();
|
||||
if (null === $repository) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
$repository = $this->repository['plugins'];
|
||||
$plugins = $repository['plugins'];
|
||||
|
||||
// local cache to speed things up
|
||||
if (isset($this->cache[__METHOD__])) {
|
||||
@ -278,18 +315,18 @@ class GPM extends Iterator
|
||||
}
|
||||
|
||||
foreach ($this->installed['plugins'] as $slug => $plugin) {
|
||||
if (!isset($repository[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
|
||||
if (!isset($plugins[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$local_version = $plugin->version ?? 'Unknown';
|
||||
$remote_version = $repository[$slug]->version;
|
||||
$remote_version = $plugins[$slug]->version;
|
||||
|
||||
if (version_compare($local_version, $remote_version) < 0) {
|
||||
$repository[$slug]->available = $remote_version;
|
||||
$repository[$slug]->version = $local_version;
|
||||
$repository[$slug]->type = $repository[$slug]->release_type;
|
||||
$items[$slug] = $repository[$slug];
|
||||
$plugins[$slug]->available = $remote_version;
|
||||
$plugins[$slug]->version = $local_version;
|
||||
$plugins[$slug]->type = $plugins[$slug]->release_type;
|
||||
$items[$slug] = $plugins[$slug];
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,19 +343,20 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getLatestVersionOfPackage($package_name)
|
||||
{
|
||||
if (null === $this->repository) {
|
||||
$repository = $this->getRepository();
|
||||
if (null === $repository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$repository = $this->repository['plugins'];
|
||||
if (isset($repository[$package_name])) {
|
||||
return $repository[$package_name]->available ?: $repository[$package_name]->version;
|
||||
$plugins = $repository['plugins'];
|
||||
if (isset($plugins[$package_name])) {
|
||||
return $plugins[$package_name]->available ?: $plugins[$package_name]->version;
|
||||
}
|
||||
|
||||
//Not a plugin, it's a theme?
|
||||
$repository = $this->repository['themes'];
|
||||
if (isset($repository[$package_name])) {
|
||||
return $repository[$package_name]->available ?: $repository[$package_name]->version;
|
||||
$themes = $repository['themes'];
|
||||
if (isset($themes[$package_name])) {
|
||||
return $themes[$package_name]->available ?: $themes[$package_name]->version;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -356,11 +394,12 @@ class GPM extends Iterator
|
||||
{
|
||||
$items = [];
|
||||
|
||||
if (null === $this->repository) {
|
||||
$repository = $this->getRepository();
|
||||
if (null === $repository) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
$repository = $this->repository['themes'];
|
||||
$themes = $repository['themes'];
|
||||
|
||||
// local cache to speed things up
|
||||
if (isset($this->cache[__METHOD__])) {
|
||||
@ -368,18 +407,18 @@ class GPM extends Iterator
|
||||
}
|
||||
|
||||
foreach ($this->installed['themes'] as $slug => $plugin) {
|
||||
if (!isset($repository[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
|
||||
if (!isset($themes[$slug]) || $plugin->symlink || !$plugin->version || $plugin->gpm === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$local_version = $plugin->version ?? 'Unknown';
|
||||
$remote_version = $repository[$slug]->version;
|
||||
$remote_version = $themes[$slug]->version;
|
||||
|
||||
if (version_compare($local_version, $remote_version) < 0) {
|
||||
$repository[$slug]->available = $remote_version;
|
||||
$repository[$slug]->version = $local_version;
|
||||
$repository[$slug]->type = $repository[$slug]->release_type;
|
||||
$items[$slug] = $repository[$slug];
|
||||
$themes[$slug]->available = $remote_version;
|
||||
$themes[$slug]->version = $local_version;
|
||||
$themes[$slug]->type = $themes[$slug]->release_type;
|
||||
$items[$slug] = $themes[$slug];
|
||||
}
|
||||
}
|
||||
|
||||
@ -407,19 +446,20 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getReleaseType($package_name)
|
||||
{
|
||||
if (null === $this->repository) {
|
||||
$repository = $this->getRepository();
|
||||
if (null === $repository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$repository = $this->repository['plugins'];
|
||||
if (isset($repository[$package_name])) {
|
||||
return $repository[$package_name]->release_type;
|
||||
$plugins = $repository['plugins'];
|
||||
if (isset($plugins[$package_name])) {
|
||||
return $plugins[$package_name]->release_type;
|
||||
}
|
||||
|
||||
//Not a plugin, it's a theme?
|
||||
$repository = $this->repository['themes'];
|
||||
if (isset($repository[$package_name])) {
|
||||
return $repository[$package_name]->release_type;
|
||||
$themes = $repository['themes'];
|
||||
if (isset($themes[$package_name])) {
|
||||
return $themes[$package_name]->release_type;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -470,7 +510,7 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getRepositoryPlugins()
|
||||
{
|
||||
return $this->repository['plugins'] ?? null;
|
||||
return $this->getRepository()['plugins'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -493,7 +533,7 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getRepositoryThemes()
|
||||
{
|
||||
return $this->repository['themes'] ?? null;
|
||||
return $this->getRepository()['themes'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -504,9 +544,31 @@ class GPM extends Iterator
|
||||
*/
|
||||
public function getRepository()
|
||||
{
|
||||
if (null === $this->repository) {
|
||||
try {
|
||||
$this->repository = new Remote\Packages($this->refresh, $this->callback);
|
||||
} catch (Exception $e) {}
|
||||
}
|
||||
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Grav version available in the repository
|
||||
*
|
||||
* @return Remote\GravCore|null
|
||||
*/
|
||||
public function getGrav()
|
||||
{
|
||||
if (null === $this->grav) {
|
||||
try {
|
||||
$this->grav = new Remote\GravCore($this->refresh, $this->callback);
|
||||
} catch (Exception $e) {}
|
||||
}
|
||||
|
||||
return $this->grav;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a Package in the repository
|
||||
*
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Config\Setup;
|
||||
use Grav\Common\Helpers\Exif;
|
||||
@ -152,6 +153,13 @@ class Grav extends Container
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
self::$instance = static::load($values);
|
||||
|
||||
/** @var ClassLoader|null $loader */
|
||||
$loader = self::$instance['loader'] ?? null;
|
||||
if ($loader) {
|
||||
// Load fix for Deferred Twig Extension
|
||||
$loader->addPsr4('Phive\\Twig\\Extensions\\Deferred\\', LIB_DIR . 'Phive/Twig/Extensions/Deferred/', true);
|
||||
}
|
||||
} elseif ($values) {
|
||||
$instance = self::$instance;
|
||||
foreach ($values as $key => $value) {
|
||||
|
@ -20,11 +20,13 @@ use Grav\Common\Security;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Filesystem\Filesystem;
|
||||
use Grav\Framework\Form\FormFlashFile;
|
||||
use Grav\Framework\Mime\MimeTypes;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RuntimeException;
|
||||
use function dirname;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Implements media upload and delete functionality.
|
||||
@ -179,16 +181,20 @@ trait MediaUploadTrait
|
||||
}
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
/** @var MimeTypes $mimeChecker */
|
||||
$mimeChecker = $grav['mime'];
|
||||
|
||||
// Handle Accepted file types. Accept can only be mime types (image/png | image/*) or file extensions (.pdf | .jpg)
|
||||
$accepted = false;
|
||||
$errors = [];
|
||||
// Do not trust mime type sent by the browser.
|
||||
$mime = Utils::getMimeByFilename($filename);
|
||||
$mimeTest = $metadata['mime'] ?? $mime;
|
||||
if ($mime !== $mimeTest) {
|
||||
$mime = $metadata['mime'] ?? $mimeChecker->getMimeType($extension);
|
||||
$validExtensions = $mimeChecker->getExtensions($mime);
|
||||
if (!in_array($extension, $validExtensions, true)) {
|
||||
throw new RuntimeException('The mime type does not match to file extension', 400);
|
||||
}
|
||||
|
||||
$accepted = false;
|
||||
$errors = [];
|
||||
foreach ((array)$settings['accept'] as $type) {
|
||||
// Force acceptance of any file when star notation
|
||||
if ($type === '*') {
|
||||
@ -418,6 +424,17 @@ trait MediaUploadTrait
|
||||
$uploadedFile->moveTo($filepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload settings.
|
||||
*
|
||||
* @param array|null $settings Form field specific settings (override).
|
||||
* @return array
|
||||
*/
|
||||
public function getUploadSettings(?array $settings = null): array
|
||||
{
|
||||
return null !== $settings ? $settings + $this->_upload_defaults : $this->_upload_defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal logic to copy file.
|
||||
*
|
||||
@ -604,17 +621,6 @@ trait MediaUploadTrait
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload settings.
|
||||
*
|
||||
* @param array|null $settings Form field specific settings (override).
|
||||
* @return array
|
||||
*/
|
||||
protected function getUploadSettings(?array $settings = null): array
|
||||
{
|
||||
return null !== $settings ? $settings + $this->_upload_defaults : $this->_upload_defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $path
|
||||
|
@ -145,6 +145,18 @@ class Collection extends Iterator implements PageCollectionInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current page.
|
||||
*/
|
||||
public function setCurrent(string $path): void
|
||||
{
|
||||
reset($this->items);
|
||||
|
||||
while (($key = key($this->items)) !== null && $key !== $path) {
|
||||
next($this->items);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current page.
|
||||
*
|
||||
|
@ -102,12 +102,13 @@ class Media extends AbstractMedia
|
||||
|
||||
foreach ($iterator as $file => $info) {
|
||||
// Ignore folders and Markdown files.
|
||||
if (!$info->isFile() || $info->getExtension() === 'md' || strpos($info->getFilename(), '.') === 0) {
|
||||
$filename = $info->getFilename();
|
||||
if (!$info->isFile() || $info->getExtension() === 'md' || $filename === 'frontmatter.yaml' || strpos($filename, '.') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find out what type we're dealing with
|
||||
[$basename, $ext, $type, $extra] = $this->getFileParts($info->getFilename());
|
||||
[$basename, $ext, $type, $extra] = $this->getFileParts($filename);
|
||||
|
||||
if (!in_array(strtolower($ext), $media_types, true)) {
|
||||
continue;
|
||||
|
@ -105,12 +105,12 @@ class InitializeProcessor extends ProcessorBase
|
||||
// TODO: remove in 2.0.
|
||||
$this->container['accounts'];
|
||||
|
||||
// Initialize session.
|
||||
$this->initializeSession($config);
|
||||
|
||||
// Initialize URI (uses session, see issue #3269).
|
||||
$this->initializeUri($config);
|
||||
|
||||
// Initialize session.
|
||||
$this->initializeSession($config);
|
||||
|
||||
// Grav may return redirect response right away.
|
||||
$redirectCode = (int)$config->get('system.pages.redirect_trailing_slash', 1);
|
||||
if ($redirectCode) {
|
||||
|
@ -10,6 +10,8 @@
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Framework\RequestHandler\Exception\RequestException;
|
||||
use Grav\Plugin\Form\Forms;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@ -47,8 +49,17 @@ class PagesProcessor extends ProcessorBase
|
||||
$page = $this->container['page'];
|
||||
|
||||
if (!$page->routable()) {
|
||||
$exception = new RequestException($request, 'Page Not Found', 404);
|
||||
$route = $this->container['route'];
|
||||
// If no page found, fire event
|
||||
$event = new Event(['page' => $page]);
|
||||
$event = new Event([
|
||||
'page' => $page,
|
||||
'code' => $exception->getCode(),
|
||||
'message' => $exception->getMessage(),
|
||||
'exception' => $exception,
|
||||
'route' => $route,
|
||||
'request' => $request
|
||||
]);
|
||||
$event->page = null;
|
||||
$event = $this->container->fireEvent('onPageNotFound', $event);
|
||||
|
||||
@ -65,12 +76,18 @@ class PagesProcessor extends ProcessorBase
|
||||
|
||||
$task = $this->container['task'];
|
||||
$action = $this->container['action'];
|
||||
|
||||
/** @var Forms $forms */
|
||||
$forms = $this->container['forms'] ?? null;
|
||||
$form = $forms ? $forms->getActiveForm() : null;
|
||||
|
||||
$options = ['page' => $page, 'form' => $form, 'request' => $request];
|
||||
if ($task) {
|
||||
$event = new Event(['task' => $task, 'page' => $page]);
|
||||
$event = new Event(['task' => $task] + $options);
|
||||
$this->container->fireEvent('onPageTask', $event);
|
||||
$this->container->fireEvent('onPageTask.' . $task, $event);
|
||||
} elseif ($action) {
|
||||
$event = new Event(['action' => $action, 'page' => $page]);
|
||||
$event = new Event(['action' => $action] + $options);
|
||||
$this->container->fireEvent('onPageAction', $event);
|
||||
$this->container->fireEvent('onPageAction.' . $action, $event);
|
||||
}
|
||||
|
@ -390,7 +390,9 @@ class Job
|
||||
if (count($this->outputTo) > 0) {
|
||||
foreach ($this->outputTo as $file) {
|
||||
$output_mode = $this->outputMode === 'append' ? FILE_APPEND | LOCK_EX : LOCK_EX;
|
||||
file_put_contents($file, $this->output, $output_mode);
|
||||
$timestamp = (new DateTime('now'))->format('c');
|
||||
$output = $timestamp . "\n" . str_pad('', strlen($timestamp), '>') . "\n" . $this->output;
|
||||
file_put_contents($file, $output, $output_mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ namespace Grav\Common;
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
use Exception;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Page\Pages;
|
||||
use function chr;
|
||||
use function count;
|
||||
@ -56,9 +57,16 @@ class Security
|
||||
$original_svg = file_get_contents($file);
|
||||
$clean_svg = $sanitizer->sanitize($original_svg);
|
||||
|
||||
// TODO: what to do with bad SVG files which return false?
|
||||
if ($clean_svg !== false && $clean_svg !== $original_svg) {
|
||||
// Quarantine bad SVG files and throw exception
|
||||
if ($clean_svg !== false ) {
|
||||
file_put_contents($file, $clean_svg);
|
||||
} else {
|
||||
$quarantine_file = basename($file);
|
||||
$quarantine_dir = 'log://quarantine';
|
||||
Folder::mkdir($quarantine_dir);
|
||||
file_put_contents("$quarantine_dir/$quarantine_file", $original_svg);
|
||||
unlink($file);
|
||||
throw new Exception('SVG could not be sanitized, it has been moved to the logs/quarantine folder');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ use Grav\Common\Config\Config;
|
||||
use Grav\Common\Config\ConfigFileFinder;
|
||||
use Grav\Common\Config\Setup;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Framework\Mime\MimeTypes;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
@ -56,6 +57,19 @@ class ConfigServiceProvider implements ServiceProviderInterface
|
||||
return $config;
|
||||
};
|
||||
|
||||
$container['mime'] = function ($c) {
|
||||
/** @var Config $config */
|
||||
$config = $c['config'];
|
||||
$mimes = $config->get('mime.types', []);
|
||||
foreach ($config->get('media.types', []) as $ext => $media) {
|
||||
if (!empty($media['mime'])) {
|
||||
$mimes[$ext] = array_unique(array_merge([$media['mime']], $mimes[$ext] ?? []));
|
||||
}
|
||||
}
|
||||
|
||||
return MimeTypes::createFromMimes($mimes);
|
||||
};
|
||||
|
||||
$container['languages'] = function ($c) {
|
||||
return static::languages($c);
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ namespace Grav\Common\Service;
|
||||
use Grav\Common\Grav;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Class TaskServiceProvider
|
||||
@ -26,7 +27,11 @@ class TaskServiceProvider implements ServiceProviderInterface
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container['task'] = function (Grav $c) {
|
||||
$task = $_POST['task'] ?? $c['uri']->param('task');
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = $c['request'];
|
||||
$body = $request->getParsedBody();
|
||||
|
||||
$task = $body['task'] ?? $c['uri']->param('task');
|
||||
if (null !== $task) {
|
||||
$task = filter_var($task, FILTER_SANITIZE_STRING);
|
||||
}
|
||||
@ -35,7 +40,11 @@ class TaskServiceProvider implements ServiceProviderInterface
|
||||
};
|
||||
|
||||
$container['action'] = function (Grav $c) {
|
||||
$action = $_POST['action'] ?? $c['uri']->param('action');
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = $c['request'];
|
||||
$body = $request->getParsedBody();
|
||||
|
||||
$action = $body['action'] ?? $c['uri']->param('action');
|
||||
if (null !== $action) {
|
||||
$action = filter_var($action, FILTER_SANITIZE_STRING);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ namespace Grav\Common;
|
||||
use Grav\Common\Form\FormFlash;
|
||||
use Grav\Events\SessionStartEvent;
|
||||
use Grav\Plugin\Form\Forms;
|
||||
use JsonException;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
@ -148,10 +149,11 @@ class Session extends \Grav\Framework\Session\Session
|
||||
* @param mixed $object
|
||||
* @param int $time
|
||||
* @return $this
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function setFlashCookieObject($name, $object, $time = 60)
|
||||
{
|
||||
setcookie($name, json_encode($object), time() + $time, '/');
|
||||
setcookie($name, json_encode($object, JSON_THROW_ON_ERROR), $this->getCookieOptions($time));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -161,13 +163,15 @@ class Session extends \Grav\Framework\Session\Session
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed|null
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function getFlashCookieObject($name)
|
||||
{
|
||||
if (isset($_COOKIE[$name])) {
|
||||
$object = json_decode($_COOKIE[$name], false);
|
||||
setcookie($name, '', time() - 3600, '/');
|
||||
return $object;
|
||||
$cookie = $_COOKIE[$name];
|
||||
setcookie($name, '', $this->getCookieOptions(-42000));
|
||||
|
||||
return json_decode($cookie, false, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
19
system/src/Grav/Common/Twig/Exception/TwigException.php
Normal file
19
system/src/Grav/Common/Twig/Exception/TwigException.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig\Exception
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Exception;
|
||||
|
||||
/**
|
||||
* TwigException gets thrown when you use {% throw code message %} in twig.
|
||||
*
|
||||
* This allows Grav to catch 401, 403 and 404 exceptions and display proper error page.
|
||||
*/
|
||||
class TwigException extends \RuntimeException
|
||||
{
|
||||
}
|
@ -155,6 +155,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
new TwigFilter('bool', [$this, 'boolFilter']),
|
||||
new TwigFilter('float', [$this, 'floatFilter'], ['is_safe' => ['all']]),
|
||||
new TwigFilter('array', [$this, 'arrayFilter']),
|
||||
new TwigFilter('yaml', [$this, 'yamlFilter']),
|
||||
|
||||
// Object Types
|
||||
new TwigFilter('get_type', [$this, 'getTypeFunc']),
|
||||
@ -807,6 +808,17 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
return (array)$input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|object $value
|
||||
* @param int|null $inline
|
||||
* @param int|null $indent
|
||||
* @return string
|
||||
*/
|
||||
public function yamlFilter($value, $inline = null, $indent = null): string
|
||||
{
|
||||
return Yaml::dump($value, $inline, $indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Environment $twig
|
||||
* @return string
|
||||
@ -1499,7 +1511,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
|
||||
}
|
||||
|
||||
//Look for existing class
|
||||
$svg = preg_replace_callback('/^<svg[^>]*(class=\")([^"]*)(\")[^>]*>/', function($matches) use ($classes, &$matched) {
|
||||
$svg = preg_replace_callback('/^<svg[^>]*(class=\"([^"]*)\")[^>]*>/', function($matches) use ($classes, &$matched) {
|
||||
if (isset($matches[2])) {
|
||||
$new_classes = $matches[2] . $classes;
|
||||
$matched = true;
|
||||
|
@ -43,7 +43,7 @@ class TwigNodeThrow extends Node
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
$compiler
|
||||
->write('throw new \RuntimeException(')
|
||||
->write('throw new \Grav\Common\Twig\Exception\TwigException(')
|
||||
->subcompile($this->getNode('message'))
|
||||
->write(', ')
|
||||
->write($this->getAttribute('code') ?: 500)
|
||||
|
@ -49,16 +49,15 @@ class TwigNodeTryCatch extends Node
|
||||
|
||||
$compiler
|
||||
->indent()
|
||||
->subcompile($this->getNode('try'));
|
||||
->subcompile($this->getNode('try'))
|
||||
->outdent()
|
||||
->write('} catch (\Exception $e) {' . "\n")
|
||||
->indent()
|
||||
->write('if (isset($context[\'grav\'][\'debugger\'])) $context[\'grav\'][\'debugger\']->addException($e);' . "\n")
|
||||
->write('$context[\'e\'] = $e;' . "\n");
|
||||
|
||||
if ($this->hasNode('catch')) {
|
||||
$compiler
|
||||
->outdent()
|
||||
->write('} catch (\Exception $e) {' . "\n")
|
||||
->indent()
|
||||
->write('if (isset($context[\'grav\'][\'debugger\'])) $context[\'grav\'][\'debugger\']->addException($e);' . "\n")
|
||||
->write('$context[\'e\'] = $e;' . "\n")
|
||||
->subcompile($this->getNode('catch'));
|
||||
$compiler->subcompile($this->getNode('catch'));
|
||||
}
|
||||
|
||||
$compiler
|
||||
|
@ -16,6 +16,7 @@ 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\Exception\TwigException;
|
||||
use Grav\Common\Twig\Extension\FilesystemExtension;
|
||||
use Grav\Common\Twig\Extension\GravExtension;
|
||||
use Grav\Common\Utils;
|
||||
@ -26,6 +27,7 @@ use RuntimeException;
|
||||
use Twig\Cache\FilesystemCache;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Extension\CoreExtension;
|
||||
use Twig\Extension\DebugExtension;
|
||||
use Twig\Extension\StringLoaderExtension;
|
||||
@ -404,38 +406,63 @@ class Twig
|
||||
*/
|
||||
public function processSite($format = null, array $vars = [])
|
||||
{
|
||||
// set the page now its been processed
|
||||
$this->grav->fireEvent('onTwigSiteVariables');
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
/** @var PageInterface $page */
|
||||
$page = $this->grav['page'];
|
||||
$content = $page->content();
|
||||
|
||||
$twig_vars = $this->twig_vars;
|
||||
|
||||
$twig_vars['theme'] = $this->grav['config']->get('theme');
|
||||
$twig_vars['pages'] = $pages->root();
|
||||
$twig_vars['page'] = $page;
|
||||
$twig_vars['header'] = $page->header();
|
||||
$twig_vars['media'] = $page->media();
|
||||
$twig_vars['content'] = $content;
|
||||
|
||||
// determine if params are set, if so disable twig cache
|
||||
$params = $this->grav['uri']->params(null, true);
|
||||
if (!empty($params)) {
|
||||
$this->twig->setCache(false);
|
||||
}
|
||||
|
||||
// Get Twig template layout
|
||||
$template = $this->getPageTwigTemplate($page, $format);
|
||||
$page->templateFormat($format);
|
||||
|
||||
try {
|
||||
$grav = $this->grav;
|
||||
|
||||
// set the page now its been processed
|
||||
$grav->fireEvent('onTwigSiteVariables');
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $grav['pages'];
|
||||
|
||||
/** @var PageInterface $page */
|
||||
$page = $grav['page'];
|
||||
|
||||
$twig_vars = $this->twig_vars;
|
||||
$twig_vars['theme'] = $grav['config']->get('theme');
|
||||
$twig_vars['pages'] = $pages->root();
|
||||
$twig_vars['page'] = $page;
|
||||
$twig_vars['header'] = $page->header();
|
||||
$twig_vars['media'] = $page->media();
|
||||
$twig_vars['content'] = $page->content();
|
||||
|
||||
// determine if params are set, if so disable twig cache
|
||||
$params = $grav['uri']->params(null, true);
|
||||
if (!empty($params)) {
|
||||
$this->twig->setCache(false);
|
||||
}
|
||||
|
||||
// Get Twig template layout
|
||||
$template = $this->getPageTwigTemplate($page, $format);
|
||||
$page->templateFormat($format);
|
||||
|
||||
$output = $this->twig->render($template, $vars + $twig_vars);
|
||||
} catch (LoaderError $e) {
|
||||
$error_msg = $e->getMessage();
|
||||
throw new RuntimeException($error_msg, 400, $e);
|
||||
throw new RuntimeException($e->getMessage(), 400, $e);
|
||||
} catch (RuntimeError $e) {
|
||||
$prev = $e->getPrevious();
|
||||
if ($prev instanceof TwigException) {
|
||||
$code = $prev->getCode() ?: 500;
|
||||
// Fire onPageNotFound event.
|
||||
$event = new Event([
|
||||
'page' => $page,
|
||||
'code' => $code,
|
||||
'message' => $prev->getMessage(),
|
||||
'exception' => $prev,
|
||||
'route' => $grav['route'],
|
||||
'request' => $grav['request']
|
||||
]);
|
||||
$event = $grav->fireEvent("onDisplayErrorPage.{$code}", $event);
|
||||
$newPage = $event['page'];
|
||||
if ($newPage && $newPage !== $page) {
|
||||
unset($grav['page']);
|
||||
$grav['page'] = $newPage;
|
||||
|
||||
return $this->processSite($newPage->templateFormat(), $vars);
|
||||
}
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
@ -675,10 +675,15 @@ class Uri
|
||||
*/
|
||||
public static function ip()
|
||||
{
|
||||
$ip = 'UNKNOWN';
|
||||
|
||||
if (getenv('HTTP_CLIENT_IP')) {
|
||||
$ip = getenv('HTTP_CLIENT_IP');
|
||||
} elseif (getenv('HTTP_CF_CONNECTING_IP')) {
|
||||
$ip = getenv('HTTP_CF_CONNECTING_IP');
|
||||
} elseif (getenv('HTTP_X_FORWARDED_FOR') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
|
||||
$ip = getenv('HTTP_X_FORWARDED_FOR');
|
||||
$ips = array_map('trim', explode(',', getenv('HTTP_X_FORWARDED_FOR')));
|
||||
$ip = array_shift($ips);
|
||||
} elseif (getenv('HTTP_X_FORWARDED') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
|
||||
$ip = getenv('HTTP_X_FORWARDED');
|
||||
} elseif (getenv('HTTP_FORWARDED_FOR')) {
|
||||
@ -687,8 +692,6 @@ class Uri
|
||||
$ip = getenv('HTTP_FORWARDED');
|
||||
} elseif (getenv('REMOTE_ADDR')) {
|
||||
$ip = getenv('REMOTE_ADDR');
|
||||
} else {
|
||||
$ip = 'UNKNOWN';
|
||||
}
|
||||
|
||||
return $ip;
|
||||
@ -1258,7 +1261,7 @@ class Uri
|
||||
$this->port = null;
|
||||
}
|
||||
|
||||
if ($this->hasStandardPort()) {
|
||||
if ($this->port === 0 || $this->hasStandardPort()) {
|
||||
$this->port = null;
|
||||
}
|
||||
|
||||
@ -1311,11 +1314,13 @@ class Uri
|
||||
if ($parts === false) {
|
||||
throw new RuntimeException('Malformed URL: ' . $url);
|
||||
}
|
||||
$port = (int)($parts['port'] ?? 0);
|
||||
|
||||
$this->scheme = $parts['scheme'] ?? null;
|
||||
$this->user = $parts['user'] ?? null;
|
||||
$this->password = $parts['pass'] ?? null;
|
||||
$this->host = $parts['host'] ?? null;
|
||||
$this->port = isset($parts['port']) ? (int)$parts['port'] : null;
|
||||
$this->port = $port ?: null;
|
||||
$this->path = $parts['path'] ?? '';
|
||||
$this->query = $parts['query'] ?? '';
|
||||
$this->fragment = $parts['fragment'] ?? null;
|
||||
|
@ -47,7 +47,7 @@ use function is_callable;
|
||||
* @package Grav\Framework\Flex
|
||||
* @template T
|
||||
*/
|
||||
class FlexDirectory implements FlexDirectoryInterface, FlexAuthorizeInterface
|
||||
class FlexDirectory implements FlexDirectoryInterface
|
||||
{
|
||||
use FlexAuthorizeTrait;
|
||||
|
||||
@ -235,7 +235,17 @@ class FlexDirectory implements FlexDirectoryInterface, FlexAuthorizeInterface
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
$filename = $locator->findResource($this->getDirectoryConfigUri($name), true);
|
||||
$uri = $this->getDirectoryConfigUri($name);
|
||||
|
||||
// If configuration is found in main configuration, use it.
|
||||
if (str_starts_with($uri, 'config://')) {
|
||||
$path = str_replace('/', '.', substr($uri, 9, -5));
|
||||
|
||||
return (array)$grav['config']->get($path);
|
||||
}
|
||||
|
||||
// Load the configuration file.
|
||||
$filename = $locator->findResource($uri, true);
|
||||
if ($filename === false) {
|
||||
return [];
|
||||
}
|
||||
@ -821,20 +831,46 @@ class FlexDirectory implements FlexDirectoryInterface, FlexAuthorizeInterface
|
||||
* @param array $call
|
||||
* @return void
|
||||
*/
|
||||
protected function dynamicFlexField(array &$field, $property, array $call)
|
||||
protected function dynamicFlexField(array &$field, $property, array $call): void
|
||||
{
|
||||
$params = (array)$call['params'];
|
||||
$object = $call['object'] ?? null;
|
||||
$method = array_shift($params);
|
||||
$not = false;
|
||||
if (str_starts_with($method, '!')) {
|
||||
$method = substr($method, 1);
|
||||
$not = true;
|
||||
} elseif (str_starts_with($method, 'not ')) {
|
||||
$method = substr($method, 4);
|
||||
$not = true;
|
||||
}
|
||||
$method = trim($method);
|
||||
|
||||
if ($object && method_exists($object, $method)) {
|
||||
$value = $object->{$method}(...$params);
|
||||
if (is_array($value) && isset($field[$property]) && is_array($field[$property])) {
|
||||
$field[$property] = array_merge_recursive($field[$property], $value);
|
||||
$value = $this->mergeArrays($field[$property], $value);
|
||||
}
|
||||
$field[$property] = $not ? !$value : $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array1
|
||||
* @param array $array2
|
||||
* @return array
|
||||
*/
|
||||
protected function mergeArrays(array $array1, array $array2): array
|
||||
{
|
||||
foreach ($array2 as $key => $value) {
|
||||
if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
|
||||
$array1[$key] = $this->mergeArrays($array1[$key], $value);
|
||||
} else {
|
||||
$field[$property] = $value;
|
||||
$array1[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $array1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -318,11 +318,11 @@ class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $filename
|
||||
* @param string|null $field
|
||||
* @param string|null $filename
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileDeleteAjaxRoute($field, $filename): ?Route
|
||||
public function getFileDeleteAjaxRoute($field = null, $filename = null): ?Route
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -453,7 +453,9 @@ class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
|
||||
protected function doSerialize(): array
|
||||
{
|
||||
return $this->doTraitSerialize() + [
|
||||
'form' => $this->form,
|
||||
'directory' => $this->directory,
|
||||
'flexName' => $this->flexName
|
||||
];
|
||||
}
|
||||
|
||||
@ -465,7 +467,9 @@ class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
|
||||
{
|
||||
$this->doTraitUnserialize($data);
|
||||
|
||||
$this->form = $data['form'];
|
||||
$this->directory = $data['directory'];
|
||||
$this->flexName = $data['flexName'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,7 +103,14 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->setObject($object);
|
||||
$this->setName($object->getFlexType(), $name);
|
||||
|
||||
if (isset($options['form']['name'])) {
|
||||
// Use custom form name.
|
||||
$this->flexName = $options['form']['name'];
|
||||
} else {
|
||||
// Use standard form name.
|
||||
$this->setName($object->getFlexType(), $name);
|
||||
}
|
||||
$this->setId($this->getName());
|
||||
|
||||
$uniqueId = $options['unique_id'] ?? null;
|
||||
@ -371,22 +378,28 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
/** @var Route $route */
|
||||
$route = Grav::instance()['route'];
|
||||
|
||||
return $route->withExtension('json')->withGravParam('task', 'media.upload');
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $filename
|
||||
* @param string|null $field
|
||||
* @param string|null $filename
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileDeleteAjaxRoute($field, $filename): ?Route
|
||||
public function getFileDeleteAjaxRoute($field = null, $filename = null): ?Route
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
/** @var Route $route */
|
||||
$route = Grav::instance()['route'];
|
||||
|
||||
return $route->withExtension('json')->withGravParam('task', 'media.delete');
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.delete');
|
||||
@ -536,7 +549,11 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
|
||||
protected function doSerialize(): array
|
||||
{
|
||||
return $this->doTraitSerialize() + [
|
||||
'items' => $this->items,
|
||||
'form' => $this->form,
|
||||
'object' => $this->object,
|
||||
'flexName' => $this->flexName,
|
||||
'submitMethod' => $this->submitMethod,
|
||||
];
|
||||
}
|
||||
|
||||
@ -548,7 +565,11 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
|
||||
{
|
||||
$this->doTraitUnserialize($data);
|
||||
|
||||
$this->object = $data['object'];
|
||||
$this->items = $data['items'] ?? null;
|
||||
$this->form = $data['form'] ?? null;
|
||||
$this->object = $data['object'] ?? null;
|
||||
$this->flexName = $data['flexName'] ?? null;
|
||||
$this->submitMethod = $data['submitMethod'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,6 +44,7 @@ use function is_array;
|
||||
use function is_object;
|
||||
use function is_scalar;
|
||||
use function is_string;
|
||||
use function json_encode;
|
||||
|
||||
/**
|
||||
* Class FlexObject
|
||||
@ -70,6 +71,8 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
/** @var array */
|
||||
private $_meta;
|
||||
/** @var array */
|
||||
protected $_original;
|
||||
/** @var array */
|
||||
protected $_changes;
|
||||
/** @var string */
|
||||
protected $storage_key;
|
||||
@ -369,7 +372,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
*/
|
||||
public function searchProperty(string $property, string $search, array $options = null): float
|
||||
{
|
||||
$options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
|
||||
$options = $options ?? (array)$this->getFlexDirectory()->getConfig('data.search.options');
|
||||
$value = $this->getProperty($property);
|
||||
|
||||
return $this->searchValue($property, $value, $search, $options);
|
||||
@ -383,7 +386,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
*/
|
||||
public function searchNestedProperty(string $property, string $search, array $options = null): float
|
||||
{
|
||||
$options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
|
||||
$options = $options ?? (array)$this->getFlexDirectory()->getConfig('data.search.options');
|
||||
if ($property === 'key') {
|
||||
$value = $this->getKey();
|
||||
} else {
|
||||
@ -440,6 +443,16 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get original data before update
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOriginalData(): array
|
||||
{
|
||||
return $this->_original ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any changes based on data sent to update
|
||||
*
|
||||
@ -653,7 +666,8 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
}
|
||||
|
||||
// Store the changes
|
||||
$this->_changes = Utils::arrayDiffMultidimensional($this->getElements(), $elements);
|
||||
$this->_original = $this->getElements();
|
||||
$this->_changes = Utils::arrayDiffMultidimensional($this->_original, $elements);
|
||||
}
|
||||
|
||||
if ($files && method_exists($this, 'setUpdatedMedia')) {
|
||||
@ -691,6 +705,17 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
return $this->create($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserInterface|null $user
|
||||
*/
|
||||
public function check(UserInterface $user = null): void
|
||||
{
|
||||
// If user has been provided, check if the user has permissions to save this object.
|
||||
if ($user && !$this->isAuthorized('save', null, $user)) {
|
||||
throw new \RuntimeException('Forbidden', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @see FlexObjectInterface::save()
|
||||
@ -809,11 +834,12 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
*/
|
||||
public function getForm(string $name = '', array $options = null)
|
||||
{
|
||||
if (!isset($this->_forms[$name])) {
|
||||
$this->_forms[$name] = $this->createFormObject($name, $options);
|
||||
$hash = $name . '-' . md5(json_encode($options, JSON_THROW_ON_ERROR));
|
||||
if (!isset($this->_forms[$hash])) {
|
||||
$this->_forms[$hash] = $this->createFormObject($name, $options);
|
||||
}
|
||||
|
||||
return $this->_forms[$name];
|
||||
return $this->_forms[$hash];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1063,6 +1089,17 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
return $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to reset blueprints if the type changes.
|
||||
*
|
||||
* @return void
|
||||
* @since 1.7.18
|
||||
*/
|
||||
protected function resetBlueprints(): void
|
||||
{
|
||||
$this->_blueprint = [];
|
||||
}
|
||||
|
||||
// DEPRECATED METHODS
|
||||
|
||||
/**
|
||||
|
@ -17,7 +17,7 @@ use Grav\Framework\Cache\CacheInterface;
|
||||
* Interface FlexDirectoryInterface
|
||||
* @package Grav\Framework\Flex\Interfaces
|
||||
*/
|
||||
interface FlexDirectoryInterface
|
||||
interface FlexDirectoryInterface extends FlexAuthorizeInterface
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
|
@ -38,8 +38,8 @@ interface FlexFormInterface extends Serializable, FormInterface
|
||||
/**
|
||||
* Get route for deleting files by AJAX.
|
||||
*
|
||||
* @param string $field Field where the file is associated into.
|
||||
* @param string $filename Filename for the file.
|
||||
* @param string|null $field Field where the file is associated into.
|
||||
* @param string|null $filename Filename for the file.
|
||||
* @return Route|null Returns Route object or null if file uploads are not enabled.
|
||||
*/
|
||||
public function getFileDeleteAjaxRoute($field, $filename);
|
||||
|
@ -40,6 +40,8 @@ class FolderStorage extends AbstractFilesystemStorage
|
||||
protected $dataFolder;
|
||||
/** @var string Pattern to access an object. */
|
||||
protected $dataPattern = '{FOLDER}/{KEY}/{FILE}{EXT}';
|
||||
/** @var string[] */
|
||||
protected $variables = ['FOLDER' => '%1$s', 'KEY' => '%2$s', 'KEY:2' => '%3$s', 'FILE' => '%4$s', 'EXT' => '%5$s'];
|
||||
/** @var string Filename for the object. */
|
||||
protected $dataFile;
|
||||
/** @var string File extension for the object. */
|
||||
@ -380,6 +382,12 @@ class FolderStorage extends AbstractFilesystemStorage
|
||||
if (isset($data[0])) {
|
||||
throw new RuntimeException('Broken object file');
|
||||
}
|
||||
|
||||
// Add key field to the object.
|
||||
$keyField = $this->keyField;
|
||||
if ($keyField !== 'storage_key' && !isset($data[$keyField])) {
|
||||
$data[$keyField] = $key;
|
||||
}
|
||||
} catch (RuntimeException $e) {
|
||||
$data = ['__ERROR' => $e->getMessage()];
|
||||
} finally {
|
||||
@ -692,9 +700,7 @@ class FolderStorage extends AbstractFilesystemStorage
|
||||
$this->keyLen = (int)($options['key_len'] ?? 32);
|
||||
$this->caseSensitive = (bool)($options['case_sensitive'] ?? true);
|
||||
|
||||
$variables = ['FOLDER' => '%1$s', 'KEY' => '%2$s', 'KEY:2' => '%3$s', 'FILE' => '%4$s', 'EXT' => '%5$s'];
|
||||
$pattern = Utils::simpleTemplate($pattern, $variables);
|
||||
|
||||
$pattern = Utils::simpleTemplate($pattern, $this->variables);
|
||||
if (!$pattern) {
|
||||
throw new RuntimeException('Bad storage folder pattern');
|
||||
}
|
||||
|
@ -455,7 +455,7 @@ class SimpleStorage extends AbstractFilesystemStorage
|
||||
$content = (array) $file->content();
|
||||
if ($this->prefix) {
|
||||
$data = new Data($content);
|
||||
$content = $data->get($this->prefix);
|
||||
$content = $data->get($this->prefix, []);
|
||||
}
|
||||
|
||||
$file->free();
|
||||
|
@ -120,7 +120,7 @@ trait FlexMediaTrait
|
||||
// Load settings for the field.
|
||||
$schema = $this->getBlueprint()->schema();
|
||||
$settings = $field && is_object($schema) ? (array)$schema->getProperty($field) : null;
|
||||
if (!isset($settings) || !is_array($settings)) {
|
||||
if (!is_array($settings)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ class FormFlash implements FormFlashInterface
|
||||
protected function loadStoredForm(): ?array
|
||||
{
|
||||
$file = $this->getTmpIndex();
|
||||
$exists = $file->exists();
|
||||
$exists = $file && $file->exists();
|
||||
|
||||
$data = null;
|
||||
if ($exists) {
|
||||
@ -246,8 +246,10 @@ class FormFlash implements FormFlashInterface
|
||||
if ($force || $this->data || $this->files) {
|
||||
// Only save if there is data or files to be saved.
|
||||
$file = $this->getTmpIndex();
|
||||
$file->save($this->jsonSerialize());
|
||||
$this->exists = true;
|
||||
if ($file) {
|
||||
$file->save($this->jsonSerialize());
|
||||
$this->exists = true;
|
||||
}
|
||||
} elseif ($this->exists) {
|
||||
// Delete empty form flash if it exists (it carries no information).
|
||||
return $this->delete();
|
||||
@ -476,12 +478,14 @@ class FormFlash implements FormFlashInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return YamlFile
|
||||
* @return ?YamlFile
|
||||
*/
|
||||
protected function getTmpIndex(): YamlFile
|
||||
protected function getTmpIndex(): ?YamlFile
|
||||
{
|
||||
$tmpDir = $this->getTmpDir();
|
||||
|
||||
// Do not use CompiledYamlFile as the file can change multiple times per second.
|
||||
return YamlFile::instance($this->getTmpDir() . '/index.yaml');
|
||||
return $tmpDir ? YamlFile::instance($tmpDir . '/index.yaml') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -503,7 +507,9 @@ class FormFlash implements FormFlashInterface
|
||||
{
|
||||
// Make sure that index file cache gets always cleared.
|
||||
$file = $this->getTmpIndex();
|
||||
$file->free();
|
||||
if ($file) {
|
||||
$file->free();
|
||||
}
|
||||
|
||||
$tmpDir = $this->getTmpDir();
|
||||
if ($tmpDir && file_exists($tmpDir)) {
|
||||
|
107
system/src/Grav/Framework/Mime/MimeTypes.php
Normal file
107
system/src/Grav/Framework/Mime/MimeTypes.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Mime
|
||||
*
|
||||
* @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Mime;
|
||||
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Class to handle mime-types.
|
||||
*/
|
||||
class MimeTypes
|
||||
{
|
||||
/** @var array */
|
||||
protected $extensions;
|
||||
/** @var array */
|
||||
protected $mimes;
|
||||
|
||||
/**
|
||||
* Create a new mime types instance with the given mappings.
|
||||
*
|
||||
* @param array $mimes An associative array containing ['ext' => ['mime/type', 'mime/type2']]
|
||||
*/
|
||||
public static function createFromMimes(array $mimes): self
|
||||
{
|
||||
$extensions = [];
|
||||
foreach ($mimes as $ext => $list) {
|
||||
foreach ($list as $mime) {
|
||||
$list = $extensions[$mime] ?? [];
|
||||
if (!in_array($ext, $list, true)) {
|
||||
$list[] = $ext;
|
||||
$extensions[$mime] = $list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new static($extensions, $mimes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMimeType(string $extension): ?string
|
||||
{
|
||||
$extension = $this->cleanInput($extension);
|
||||
|
||||
return $this->mimes[$extension][0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $mime
|
||||
* @return string|null
|
||||
*/
|
||||
public function getExtension(string $mime): ?string
|
||||
{
|
||||
$mime = $this->cleanInput($mime);
|
||||
|
||||
return $this->extensions[$mime][0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @return array
|
||||
*/
|
||||
public function getMimeTypes(string $extension): array
|
||||
{
|
||||
$extension = $this->cleanInput($extension);
|
||||
|
||||
return $this->mimes[$extension] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $mime
|
||||
* @return array
|
||||
*/
|
||||
public function getExtensions(string $mime): array
|
||||
{
|
||||
$mime = $this->cleanInput($mime);
|
||||
|
||||
return $this->extensions[$mime] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
protected function cleanInput(string $input): string
|
||||
{
|
||||
return strtolower(trim($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $extensions
|
||||
* @param array $mimes
|
||||
*/
|
||||
protected function __construct(array $extensions, array $mimes)
|
||||
{
|
||||
$this->extensions = $extensions;
|
||||
$this->mimes = $mimes;
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Grav\Framework\Collection\ArrayCollection;
|
||||
use Grav\Framework\Object\Access\NestedPropertyCollectionTrait;
|
||||
|
@ -23,6 +23,9 @@ class UploadedFile implements UploadedFileInterface
|
||||
{
|
||||
use UploadedFileDecoratorTrait;
|
||||
|
||||
/** @var array */
|
||||
private $meta = [];
|
||||
|
||||
/**
|
||||
* @param StreamInterface|string|resource $streamOrFile
|
||||
* @param int $size
|
||||
@ -34,4 +37,34 @@ class UploadedFile implements UploadedFileInterface
|
||||
{
|
||||
$this->uploadedFile = new \Nyholm\Psr7\UploadedFile($streamOrFile, $size, $errorStatus, $clientFilename, $clientMediaType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $meta
|
||||
* @return $this
|
||||
*/
|
||||
public function setMeta(array $meta)
|
||||
{
|
||||
$this->meta = $meta;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $meta
|
||||
* @return $this
|
||||
*/
|
||||
public function addMeta(array $meta)
|
||||
{
|
||||
$this->meta = array_merge($this->meta, $meta);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMeta(): array
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
}
|
||||
|
@ -338,23 +338,12 @@ class Session implements SessionInterface
|
||||
{
|
||||
$name = $this->getName();
|
||||
if (null !== $name) {
|
||||
$params = session_get_cookie_params();
|
||||
|
||||
$cookie_options = array (
|
||||
'expires' => time() - 42000,
|
||||
'path' => $params['path'],
|
||||
'domain' => $params['domain'],
|
||||
'secure' => $params['secure'],
|
||||
'httponly' => $params['httponly'],
|
||||
'samesite' => $params['samesite']
|
||||
);
|
||||
|
||||
$this->removeCookie();
|
||||
|
||||
setcookie(
|
||||
session_name(),
|
||||
'',
|
||||
$cookie_options
|
||||
$this->getCookieOptions(-42000)
|
||||
);
|
||||
}
|
||||
|
||||
@ -463,27 +452,36 @@ class Session implements SessionInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* Store something in cookie temporarily.
|
||||
*
|
||||
* @param int|null $lifetime
|
||||
* @return array
|
||||
*/
|
||||
protected function setCookie(): void
|
||||
public function getCookieOptions(int $lifetime = null): array
|
||||
{
|
||||
$params = session_get_cookie_params();
|
||||
|
||||
$cookie_options = array (
|
||||
'expires' => time() + $params['lifetime'],
|
||||
return [
|
||||
'expires' => time() + ($lifetime ?? $params['lifetime']),
|
||||
'path' => $params['path'],
|
||||
'domain' => $params['domain'],
|
||||
'secure' => $params['secure'],
|
||||
'httponly' => $params['httponly'],
|
||||
'samesite' => $params['samesite']
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function setCookie(): void
|
||||
{
|
||||
$this->removeCookie();
|
||||
|
||||
setcookie(
|
||||
session_name(),
|
||||
session_id(),
|
||||
$cookie_options
|
||||
$this->getCookieOptions()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
// Fix too many ob_get_clean() calls when exception is thrown inside the template.
|
||||
|
||||
namespace Phive\Twig\Extensions\Deferred;
|
||||
|
||||
class DeferredExtension extends \Twig_Extension
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $blocks = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTokenParsers()
|
||||
{
|
||||
return array(new DeferredTokenParser());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodeVisitors()
|
||||
{
|
||||
return array(new DeferredNodeVisitor());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'deferred';
|
||||
}
|
||||
|
||||
public function defer(\Twig_Template $template, $blockName)
|
||||
{
|
||||
ob_start();
|
||||
$templateName = $template->getTemplateName();
|
||||
$this->blocks[$templateName][] = [ob_get_level(), $blockName];
|
||||
}
|
||||
|
||||
public function resolve(\Twig_Template $template, array $context, array $blocks)
|
||||
{
|
||||
$templateName = $template->getTemplateName();
|
||||
if (empty($this->blocks[$templateName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
while ($block = array_pop($this->blocks[$templateName])) {
|
||||
[$level, $blockName] = $block;
|
||||
if (ob_get_level() !== $level) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$buffer = ob_get_clean();
|
||||
|
||||
$blocks[$blockName] = array($template, 'block_'.$blockName.'_deferred');
|
||||
$template->displayBlock($blockName, $context, $blocks);
|
||||
|
||||
echo $buffer;
|
||||
}
|
||||
|
||||
if ($parent = $template->getParent($context)) {
|
||||
$this->resolve($parent, $context, $blocks);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
core:
|
||||
grav:
|
||||
version: 1.7.16
|
||||
version: 1.7.21
|
||||
schema: 1.7.0_2020-11-20_1
|
||||
history:
|
||||
- { version: 1.7.16, date: '2021-06-10 14:03:35' }
|
||||
- { version: 1.7.21, date: '2021-09-16 12:41:14' }
|
||||
|
Loading…
x
Reference in New Issue
Block a user