diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d7352ebb..73d6f40d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# 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 diff --git a/index.php b/index.php index 9842952d5..fcbcfd7f3 100644 --- a/index.php +++ b/index.php @@ -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:
php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php
"); } diff --git a/system/blueprints/flex/pages.yaml b/system/blueprints/flex/pages.yaml index ee2e7e5fa..5c6ed8eb5 100644 --- a/system/blueprints/flex/pages.yaml +++ b/system/blueprints/flex/pages.yaml @@ -184,9 +184,9 @@ config: # Fields to be searched fields: - key + - slug - menu - title - - name blueprints: configure: diff --git a/system/defines.php b/system/defines.php index 46126d059..4cd520a60 100644 --- a/system/defines.php +++ b/system/defines.php @@ -9,7 +9,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.7.17'); +define('GRAV_VERSION', '1.7.18'); define('GRAV_SCHEMA', '1.7.0_2020-11-20_1'); define('GRAV_TESTING', false); diff --git a/system/src/Grav/Common/Flex/Types/Pages/PageObject.php b/system/src/Grav/Common/Flex/Types/Pages/PageObject.php index a8342d121..21a03d35f 100644 --- a/system/src/Grav/Common/Flex/Types/Pages/PageObject.php +++ b/system/src/Grav/Common/Flex/Types/Pages/PageObject.php @@ -273,7 +273,7 @@ class PageObject extends FlexPageObject $parentKey = $this->getProperty('parent_key'); /** @var PageObject|null $parent */ - $parent = $this->getFlexDirectory()->getObject($parentKey); + $parent = $this->getFlexDirectory()->getObject($parentKey, 'storage_key'); if (!$parent || !$parent->isAuthorized('create', null, $user)) { throw new \RuntimeException('Forbidden', 403); } diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php index 222aa0ada..dfed77a14 100644 --- a/system/src/Grav/Common/GPM/GPM.php +++ b/system/src/Grav/Common/GPM/GPM.php @@ -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(GRAV_ROOT . '/cache/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 * diff --git a/system/src/Grav/Common/Security.php b/system/src/Grav/Common/Security.php index fe33d7964..c464e432d 100644 --- a/system/src/Grav/Common/Security.php +++ b/system/src/Grav/Common/Security.php @@ -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'); } } } diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index 798176bbd..c7f71048e 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -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; diff --git a/system/src/Grav/Framework/Flex/FlexDirectory.php b/system/src/Grav/Framework/Flex/FlexDirectory.php index 1f6962663..8d8dd1e64 100644 --- a/system/src/Grav/Framework/Flex/FlexDirectory.php +++ b/system/src/Grav/Framework/Flex/FlexDirectory.php @@ -235,7 +235,17 @@ class FlexDirectory implements FlexDirectoryInterface /** @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 = strtr(substr($uri, 9, -5), '/', '.'); + + return $grav['config']->get($path); + } + + // Load the configuration file. + $filename = $locator->findResource($uri, true); if ($filename === false) { return []; } diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php index 06e383bff..97b2569b0 100644 --- a/system/src/Grav/Framework/Flex/FlexObject.php +++ b/system/src/Grav/Framework/Flex/FlexObject.php @@ -1074,6 +1074,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 /** diff --git a/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php b/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php index 85c14290e..6faac9d84 100644 --- a/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php +++ b/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php @@ -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(); diff --git a/system/src/Grav/Framework/Form/FormFlash.php b/system/src/Grav/Framework/Form/FormFlash.php index 00aab2893..864e992f3 100644 --- a/system/src/Grav/Framework/Form/FormFlash.php +++ b/system/src/Grav/Framework/Form/FormFlash.php @@ -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)) { diff --git a/system/src/Grav/Framework/Object/ObjectCollection.php b/system/src/Grav/Framework/Object/ObjectCollection.php index 0d82434a1..3a51ec80c 100644 --- a/system/src/Grav/Framework/Object/ObjectCollection.php +++ b/system/src/Grav/Framework/Object/ObjectCollection.php @@ -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;