mirror of
https://github.com/getgrav/grav.git
synced 2026-07-06 00:49:49 +02:00
New sites have compatibility features turned off by default, upgrading from older versions will keep the settings on
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
* Upgraded `bin/composer.phar` to `2.0.2` which is all new and much faster
|
||||
* Added search option `same_as` to Flex Objects
|
||||
* Added PHP 8 compatible `function_exists()`: `Utils::functionExists()`
|
||||
* New sites have `compatibility` features turned off by default, upgrading from older versions will keep the settings on
|
||||
1. [](#improved)
|
||||
* Updated bundled JQuery to latest version `3.5.1`
|
||||
* Forward a `sid` to GPM when downloading a premium package via CLI
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"ext-libxml": "*",
|
||||
"symfony/polyfill-mbstring": "~1.20",
|
||||
"symfony/polyfill-iconv": "^1.20",
|
||||
"symfony/polyfill-php73": "^1.20",
|
||||
"symfony/polyfill-php74": "^1.20",
|
||||
"symfony/polyfill-php80": "^1.20",
|
||||
"psr/simple-cache": "^1.0",
|
||||
|
||||
@@ -846,7 +846,8 @@ form:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES
|
||||
help: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES_HELP
|
||||
highlight: 0
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
@@ -1565,8 +1566,8 @@ form:
|
||||
strict_mode.blueprint_compat:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.STRICT_BLUEPRINT_COMPAT
|
||||
highlight: 1
|
||||
default: 1
|
||||
highlight: 0
|
||||
default: 0
|
||||
help: PLUGIN_ADMIN.STRICT_BLUEPRINT_COMPAT_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
@@ -1577,8 +1578,8 @@ form:
|
||||
strict_mode.yaml_compat:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.STRICT_YAML_COMPAT
|
||||
highlight: 1
|
||||
default: 1
|
||||
highlight: 0
|
||||
default: 0
|
||||
help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
@@ -1589,8 +1590,8 @@ form:
|
||||
strict_mode.twig_compat:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT
|
||||
highlight: 1
|
||||
default: 1
|
||||
highlight: 0
|
||||
default: 0
|
||||
help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
|
||||
@@ -108,7 +108,7 @@ twig:
|
||||
cache: true # Set to true to enable Twig caching
|
||||
debug: true # Enable Twig debug
|
||||
auto_reload: true # Refresh cache on changes
|
||||
autoescape: false # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
|
||||
autoescape: true # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
|
||||
undefined_functions: true # Allow undefined functions
|
||||
undefined_filters: true # Allow undefined filters
|
||||
umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
|
||||
@@ -196,6 +196,6 @@ flex:
|
||||
lifetime: 600 # Lifetime of cached HTML in seconds (0 = infinite)
|
||||
|
||||
strict_mode:
|
||||
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility
|
||||
twig_compat: true # Grav 1.5+: Enables deprecated Twig autoescape setting (autoescape: false)
|
||||
blueprint_compat: true # Grav 1.7+: Enables backward compatible strict support for blueprints
|
||||
yaml_compat: false # Set to true to enable YAML backwards compatibility
|
||||
twig_compat: false # Set to true to enable deprecated Twig settings (autoescape: false)
|
||||
blueprint_compat: false # Set to true to enable backward compatible strict support for blueprints
|
||||
|
||||
@@ -1106,6 +1106,14 @@ class Pages
|
||||
if (null === self::$types) {
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
|
||||
// Prevent calls made before theme:// has been initialized (happens when upgrading old version of Admin plugin).
|
||||
if (!$locator->isStream('theme://')) {
|
||||
return new Types();
|
||||
}
|
||||
|
||||
$scanBlueprintsAndTemplates = static function (Types $types) use ($grav) {
|
||||
// Scan blueprints
|
||||
$event = new Event();
|
||||
|
||||
@@ -14,13 +14,14 @@ use Grav\Common\Cache;
|
||||
use Grav\Common\GPM\Installer;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Plugins;
|
||||
use function dirname;
|
||||
|
||||
/**
|
||||
* Grav installer.
|
||||
*
|
||||
* NOTE: This class can be initialized during upgrade from an older version of Grav. Make sure it runs there!
|
||||
*/
|
||||
class Install
|
||||
final class Install
|
||||
{
|
||||
/** @var int Installer version. */
|
||||
public $version = 1;
|
||||
@@ -39,8 +40,7 @@ class Install
|
||||
'name' => 'Grav',
|
||||
'versions' => [
|
||||
'1.6' => '1.6.0',
|
||||
'1.5' => '1.5.0',
|
||||
'' => '1.6.22'
|
||||
'' => '1.6.28'
|
||||
]
|
||||
],
|
||||
'plugins' => [
|
||||
@@ -49,7 +49,6 @@ class Install
|
||||
'optional' => true,
|
||||
'versions' => [
|
||||
'1.9' => '1.9.0',
|
||||
'1.8' => '1.8.0',
|
||||
'' => '1.9.13'
|
||||
]
|
||||
],
|
||||
@@ -58,28 +57,26 @@ class Install
|
||||
'optional' => true,
|
||||
'versions' => [
|
||||
'3.0' => '3.0.0',
|
||||
'2.7' => '2.7.0',
|
||||
'' => '3.0.7'
|
||||
'' => '3.0.10'
|
||||
]
|
||||
],
|
||||
'form' => [
|
||||
'name' => 'Form',
|
||||
'optional' => true,
|
||||
'versions' => [
|
||||
'4.1' => '4.1.0',
|
||||
'4.0' => '4.0.0',
|
||||
'3.0' => '3.0.0',
|
||||
'2.16' => '2.16.0',
|
||||
'' => '4.0.5'
|
||||
'' => '4.1.2'
|
||||
]
|
||||
],
|
||||
'login' => [
|
||||
'name' => 'Login',
|
||||
'optional' => true,
|
||||
'versions' => [
|
||||
'3.1' => '3.1.0',
|
||||
'3.3' => '3.3.0',
|
||||
'3.0' => '3.0.0',
|
||||
'2.8' => '2.8.0',
|
||||
'' => '3.1.0'
|
||||
'' => '3.3.6'
|
||||
]
|
||||
],
|
||||
]
|
||||
@@ -99,7 +96,11 @@ class Install
|
||||
|
||||
/** @var array */
|
||||
private $classMap = [
|
||||
// 'Grav\\Installer\\Test' => __DIR__ . '/Test.php',
|
||||
InstallException::class => __DIR__ . '/InstallException.php',
|
||||
Versions::class => __DIR__ . '/Versions.php',
|
||||
VersionUpdate::class => __DIR__ . '/VersionUpdate.php',
|
||||
VersionUpdater::class => __DIR__ . '/VersionUpdater.php',
|
||||
YamlUpdater::class => __DIR__ . '/YamlUpdater.php',
|
||||
];
|
||||
|
||||
/** @var string|null */
|
||||
@@ -108,9 +109,15 @@ class Install
|
||||
/** @var string|null */
|
||||
private $location;
|
||||
|
||||
/** @var VersionUpdater */
|
||||
private $updater;
|
||||
|
||||
/** @var static */
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function instance()
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
@@ -143,7 +150,19 @@ class Install
|
||||
$error[] = "{$req['title']} >= <strong>v{$req['minimum']}</strong> required, you have <strong>v{$req['installed']}</strong>";
|
||||
}
|
||||
|
||||
throw new \RuntimeException(implode("<br />\n", $error));
|
||||
$errors = implode("<br />\n", $error);
|
||||
if (\defined('GRAV_CLI') && GRAV_CLI) {
|
||||
$errors = "\n\n" . strip_tags($errors) . "\n\n";
|
||||
$errors .= <<<ERR
|
||||
Please install Grav 1.6.28 first by running following commands:
|
||||
|
||||
wget -q https://getgrav.org/download/core/grav-update/1.6.28 -O grav-update.zip
|
||||
bin/gpm direct-install -y grav-update.zip
|
||||
rm grav-update.zip
|
||||
ERR;
|
||||
}
|
||||
|
||||
throw new \RuntimeException($errors);
|
||||
}
|
||||
|
||||
$this->prepare();
|
||||
@@ -200,7 +219,14 @@ class Install
|
||||
// Override Grav\Installer classes by using this version of Grav.
|
||||
$loader->addClassMap($this->classMap);
|
||||
|
||||
$this->legacySupport();
|
||||
|
||||
$this->location = dirname($location, 4);
|
||||
|
||||
$versions = Versions::instance(GRAV_ROOT . '/user/config/versions.yaml');
|
||||
$this->updater = new VersionUpdater('core/grav', __DIR__ . '/updates', $versions);
|
||||
|
||||
$this->updater->preflight();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,6 +264,8 @@ class Install
|
||||
*/
|
||||
public function finalize(): void
|
||||
{
|
||||
$this->updater->postflight();
|
||||
|
||||
Cache::clearCache();
|
||||
|
||||
clearstatcache();
|
||||
@@ -305,4 +333,10 @@ class Install
|
||||
$this->checkVersion($results, 'plugin', $name, $check, $version);
|
||||
}
|
||||
}
|
||||
|
||||
protected function legacySupport(): void
|
||||
{
|
||||
// Support install for Grav 1.6.0 - 1.6.20 by loading the original class from the older version of Grav.
|
||||
class_exists(\Grav\Console\Cli\CacheCommand::class, true);
|
||||
}
|
||||
}
|
||||
|
||||
29
system/src/Grav/Installer/InstallException.php
Normal file
29
system/src/Grav/Installer/InstallException.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Installer
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Installer;
|
||||
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class InstallException
|
||||
* @package Grav\Installer
|
||||
*/
|
||||
class InstallException extends \RuntimeException
|
||||
{
|
||||
/**
|
||||
* InstallException constructor.
|
||||
* @param string $message
|
||||
* @param Throwable $previous
|
||||
*/
|
||||
public function __construct(string $message, Throwable $previous)
|
||||
{
|
||||
parent::__construct($message, $previous->getCode(), $previous);
|
||||
}
|
||||
}
|
||||
82
system/src/Grav/Installer/VersionUpdate.php
Normal file
82
system/src/Grav/Installer/VersionUpdate.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Installer;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Class VersionUpdate
|
||||
* @package Grav\Installer
|
||||
*/
|
||||
final class VersionUpdate
|
||||
{
|
||||
/** @var string */
|
||||
private $revision;
|
||||
/** @var string */
|
||||
private $version;
|
||||
/** @var string */
|
||||
private $date;
|
||||
/** @var string */
|
||||
private $patch;
|
||||
/** @var VersionUpdater */
|
||||
private $updater;
|
||||
/** @var callable[] */
|
||||
private $methods;
|
||||
|
||||
public function __construct(string $file, VersionUpdater $updater)
|
||||
{
|
||||
$name = basename($file, '.php');
|
||||
|
||||
$this->revision = $name;
|
||||
[$this->version, $this->date, $this->patch] = explode('_', $name);
|
||||
$this->updater = $updater;
|
||||
$this->methods = require $file;
|
||||
}
|
||||
|
||||
public function getRevision(): string
|
||||
{
|
||||
return $this->revision;
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function getDate(): string
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function getPatch(): string
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function getUpdater(): VersionUpdater
|
||||
{
|
||||
return $this->updater;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run right before installation.
|
||||
*/
|
||||
public function preflight(VersionUpdater $updater): void
|
||||
{
|
||||
$method = $this->methods['preflight'] ?? null;
|
||||
if ($method instanceof Closure) {
|
||||
$method->call($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs right after installation.
|
||||
*/
|
||||
public function postflight(VersionUpdater $updater): void
|
||||
{
|
||||
$method = $this->methods['postflight'] ?? null;
|
||||
if ($method instanceof Closure) {
|
||||
$method->call($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
114
system/src/Grav/Installer/VersionUpdater.php
Normal file
114
system/src/Grav/Installer/VersionUpdater.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Installer;
|
||||
|
||||
use DirectoryIterator;
|
||||
|
||||
/**
|
||||
* Class Update_1_7_0_20201120_1
|
||||
* @package Grav\Installer\updates
|
||||
*/
|
||||
final class VersionUpdater
|
||||
{
|
||||
/** @var string */
|
||||
private $name;
|
||||
/** @var string */
|
||||
private $path;
|
||||
/** @var Versions */
|
||||
private $versions;
|
||||
/** @var VersionUpdate[] */
|
||||
private $updates;
|
||||
|
||||
/**
|
||||
* VersionUpdater constructor.
|
||||
* @param string $name
|
||||
* @param string $path
|
||||
* @param Versions $versions
|
||||
*/
|
||||
public function __construct(string $name, string $path, Versions $versions)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->path = $path;
|
||||
$this->versions = $versions;
|
||||
|
||||
$this->loadUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-installation methods.
|
||||
*/
|
||||
public function preflight(): void
|
||||
{
|
||||
foreach ($this->updates as $revision => $update) {
|
||||
$update->preflight($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-installation methods.
|
||||
*/
|
||||
public function postflight(): void
|
||||
{
|
||||
$versions = $this->getVersions();
|
||||
|
||||
foreach ($this->updates as $revision => $update) {
|
||||
$update->postflight($this);
|
||||
|
||||
$versions->setSchema($this->name, $revision);
|
||||
$versions->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Versions
|
||||
*/
|
||||
public function getVersions(): Versions
|
||||
{
|
||||
return $this->versions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
* @return string|null
|
||||
*/
|
||||
public function getExtensionVersion(string $name = null): ?string
|
||||
{
|
||||
return $this->versions->getVersion($name ?? $this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
* @return string|null
|
||||
*/
|
||||
public function getExtensionSchema(string $name = null): ?string
|
||||
{
|
||||
return $this->versions->getSchema($name ?? $this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $name
|
||||
* @return array
|
||||
*/
|
||||
public function getExtensionHistory(string $name = null): array
|
||||
{
|
||||
return $this->versions->getHistory($name ?? $this->name);
|
||||
}
|
||||
|
||||
protected function loadUpdates(): void
|
||||
{
|
||||
$schema = $this->getExtensionSchema();
|
||||
$iterator = new DirectoryIterator($this->path);
|
||||
foreach ($iterator as $item) {
|
||||
if (!$item->isFile() || $item->getExtension() !== 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$revision = $item->getBasename('.php');
|
||||
if (!$schema || version_compare($revision, $schema, '>')) {
|
||||
$this->updates[$revision] = new VersionUpdate($item->getRealPath(), $this);
|
||||
}
|
||||
}
|
||||
|
||||
uksort($this->updates, 'version_compare');
|
||||
}
|
||||
}
|
||||
308
system/src/Grav/Installer/Versions.php
Normal file
308
system/src/Grav/Installer/Versions.php
Normal file
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Installer
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Installer;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/**
|
||||
* Grav Versions
|
||||
*
|
||||
* NOTE: This class can be initialized during upgrade from an older version of Grav. Make sure it runs there!
|
||||
*/
|
||||
final class Versions
|
||||
{
|
||||
/** @var string */
|
||||
protected $filename;
|
||||
/** @var array */
|
||||
protected $items;
|
||||
/** @var bool */
|
||||
protected $updated = false;
|
||||
|
||||
/** @var self[] */
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* @param string|null $filename
|
||||
* @return static
|
||||
*/
|
||||
public static function instance(string $filename = null): self
|
||||
{
|
||||
$filename = $filename ?? GRAV_ROOT . '/user/config/versions.yaml';
|
||||
|
||||
if (!isset(self::$instance[$filename])) {
|
||||
self::$instance[$filename] = new self($filename);
|
||||
}
|
||||
|
||||
return self::$instance[$filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if the file was updated.
|
||||
*/
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->updated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_put_contents($this->filename, Yaml::dump($this->items, 5, 2));
|
||||
|
||||
$this->updated = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getGrav(): ?array
|
||||
{
|
||||
return $this->get('core/grav');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPlugins(): array
|
||||
{
|
||||
return $this->get('plugins', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function getPlugin(string $name): ?array
|
||||
{
|
||||
return $this->get("plugins/{$name}");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getThemes(): array
|
||||
{
|
||||
return $this->get('themes', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return array|null
|
||||
*/
|
||||
public function getTheme(string $name): ?array
|
||||
{
|
||||
return $this->get("themes/{$name}");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @return array|null
|
||||
*/
|
||||
public function getExtension(string $extension): ?array
|
||||
{
|
||||
return $this->get($extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @param array|null $value
|
||||
*/
|
||||
public function setExtension(string $extension, ?array $value): void
|
||||
{
|
||||
if (null !== $value) {
|
||||
$this->set($extension, $value);
|
||||
} else {
|
||||
$this->undef($extension);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @return string|null
|
||||
*/
|
||||
public function getVersion(string $extension): ?string
|
||||
{
|
||||
$version = $this->get("{$extension}/version", null);
|
||||
|
||||
return is_string($version) ? $version : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @param string|null $version
|
||||
*/
|
||||
public function setVersion(string $extension, ?string $version): void
|
||||
{
|
||||
$this->updateHistory($extension, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Updates also history.
|
||||
*
|
||||
* @param string $extension
|
||||
* @param string|null $version
|
||||
*/
|
||||
public function updateVersion(string $extension, ?string $version): void
|
||||
{
|
||||
$this->set("{$extension}/version", $version);
|
||||
$this->updateHistory($extension, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSchema(string $extension): ?string
|
||||
{
|
||||
$version = $this->get("{$extension}/schema", null);
|
||||
|
||||
return is_string($version) ? $version : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @param string|null $schema
|
||||
*/
|
||||
public function setSchema(string $extension, ?string $schema): void
|
||||
{
|
||||
if (null !== $schema) {
|
||||
$this->set("{$extension}/schema", $schema);
|
||||
} else {
|
||||
$this->undef("{$extension}/schema");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @return array
|
||||
*/
|
||||
public function getHistory(string $extension): array
|
||||
{
|
||||
$name = "{$extension}/history";
|
||||
|
||||
return $this->get($name, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @param string|null $version
|
||||
*/
|
||||
public function updateHistory(string $extension, ?string $version): void
|
||||
{
|
||||
$name = "{$extension}/history";
|
||||
$history = $this->get($name, []);
|
||||
$history[] = ['version' => $version, 'date' => gmdate('Y-m-d H:i:s')];
|
||||
$this->set($name, $history);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears extension history. Useful when creating skeletons.
|
||||
*
|
||||
* @param string|null $extension
|
||||
*/
|
||||
public function removeHistory(string $extension): void
|
||||
{
|
||||
$this->undef("{$extension}/history");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @param string $name Slash separated path to the requested value.
|
||||
* @param mixed $default Default value (or null).
|
||||
* @return mixed Value.
|
||||
*/
|
||||
private function get(string $name, $default = null)
|
||||
{
|
||||
$path = explode('/', $name) ?: [];
|
||||
$current = $this->items;
|
||||
|
||||
foreach ($path as $field) {
|
||||
if (is_array($current) && isset($current[$field])) {
|
||||
$current = $current[$field];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @param string $name Slash separated path to the requested value.
|
||||
* @param mixed $value New value.
|
||||
*/
|
||||
private function set(string $name, $value): void
|
||||
{
|
||||
$path = explode('/', $name) ?: [];
|
||||
$current = &$this->items;
|
||||
|
||||
foreach ($path as $field) {
|
||||
// Handle arrays and scalars.
|
||||
if (!is_array($current)) {
|
||||
$current = [$field => []];
|
||||
} elseif (!isset($current[$field])) {
|
||||
$current[$field] = [];
|
||||
}
|
||||
$current = &$current[$field];
|
||||
}
|
||||
|
||||
$current = $value;
|
||||
$this->updated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
*/
|
||||
private function undef(string $name): void
|
||||
{
|
||||
$path = $name !== '' ? explode('/', $name) : [];
|
||||
if (!$path) {
|
||||
return;
|
||||
}
|
||||
|
||||
$var = array_pop($path);
|
||||
$current = &$this->items;
|
||||
|
||||
foreach ($path as $field) {
|
||||
if (!is_array($current) || !isset($current[$field])) {
|
||||
return;
|
||||
}
|
||||
$current = &$current[$field];
|
||||
}
|
||||
|
||||
unset($current[$var]);
|
||||
$this->updated = true;
|
||||
}
|
||||
|
||||
private function __construct(string $filename)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
$content = is_file($filename) ? file_get_contents($filename) : null;
|
||||
if (false === $content) {
|
||||
throw new \RuntimeException('Versions file cannot be read');
|
||||
}
|
||||
$this->items = $content ? Yaml::parse($content) : [];
|
||||
}
|
||||
}
|
||||
407
system/src/Grav/Installer/YamlUpdater.php
Normal file
407
system/src/Grav/Installer/YamlUpdater.php
Normal file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Installer
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Installer;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Grav YAML updater.
|
||||
*
|
||||
* NOTE: This class can be initialized during upgrade from an older version of Grav. Make sure it runs there!
|
||||
*/
|
||||
final class YamlUpdater
|
||||
{
|
||||
/** @var string */
|
||||
protected $filename;
|
||||
/** @var string[] */
|
||||
protected $lines;
|
||||
/** @var array */
|
||||
protected $comments;
|
||||
/** @var array */
|
||||
protected $items;
|
||||
/** @var bool */
|
||||
protected $updated = false;
|
||||
|
||||
/** @var self[] */
|
||||
protected static $instance;
|
||||
|
||||
public static function instance(string $filename): self
|
||||
{
|
||||
if (!isset(self::$instance[$filename])) {
|
||||
self::$instance[$filename] = new self($filename);
|
||||
}
|
||||
|
||||
return self::$instance[$filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function save(): bool
|
||||
{
|
||||
if (!$this->updated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$this->isHandWritten()) {
|
||||
$yaml = Yaml::dump($this->items, 5, 2);
|
||||
} else {
|
||||
$yaml = implode("\n", $this->lines);
|
||||
|
||||
$items = Yaml::parse($yaml);
|
||||
if ($items !== $this->items) {
|
||||
throw new \RuntimeException('Failed saving the content');
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($this->filename, $yaml);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Failed to update ' . basename($this->filename) . ': ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isHandWritten(): bool
|
||||
{
|
||||
return !empty($this->comments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getComments(): array
|
||||
{
|
||||
$comments = [];
|
||||
foreach ($this->lines as $i => $line) {
|
||||
if ($this->isLineEmpty($line)) {
|
||||
$comments[$i+1] = $line;
|
||||
} elseif ($comment = $this->getInlineComment($line)) {
|
||||
$comments[$i+1] = $comment;
|
||||
}
|
||||
}
|
||||
|
||||
return $comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $variable
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function define(string $variable, $value)
|
||||
{
|
||||
// If variable has already value, we're good.
|
||||
if ($this->get($variable) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If one of the parents isn't array, we're good, too.
|
||||
if (!$this->canDefine($variable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set($variable, $value);
|
||||
if (!$this->isHandWritten()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parts = explode('.', $variable);
|
||||
|
||||
$lineNos = $this->findPath($this->lines, $parts);
|
||||
$count = count($lineNos);
|
||||
$last = array_key_last($lineNos);
|
||||
|
||||
$value = explode("\n", trim(Yaml::dump([$last => $this->get(implode('.', array_keys($lineNos)))], max(0, 5-$count), 2)));
|
||||
$currentLine = array_pop($lineNos) ?: 0;
|
||||
$parentLine = array_pop($lineNos);
|
||||
|
||||
if ($parentLine !== null) {
|
||||
$c = $this->getLineIndentation($this->lines[$parentLine] ?? 0);
|
||||
$n = $this->getLineIndentation($this->lines[$parentLine+1] ?? $this->lines[$parentLine] ?? 0);
|
||||
$indent = $n > $c ? $n : $c + 2;
|
||||
} else {
|
||||
$indent = 0;
|
||||
array_unshift($value, '');
|
||||
}
|
||||
$spaces = str_repeat(' ', $indent);
|
||||
foreach ($value as &$line) {
|
||||
$line = $spaces . $line;
|
||||
}
|
||||
unset($line);
|
||||
|
||||
array_splice($this->lines, abs($currentLine)+1, 0, $value);
|
||||
}
|
||||
|
||||
private function __construct(string $filename)
|
||||
{
|
||||
$content = is_file($filename) ? file_get_contents($filename) : '';
|
||||
$content = rtrim(str_replace(["\r\n", "\r"], "\n", $content));
|
||||
|
||||
$this->filename = $filename;
|
||||
$this->lines = explode("\n", $content);
|
||||
$this->comments = $this->getComments();
|
||||
$this->items = $content ? Yaml::parse($content) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of offsets for the parent nodes. Negative value means position, but not found.
|
||||
*
|
||||
* @param array $lines
|
||||
* @param array $parts
|
||||
* @return array
|
||||
*/
|
||||
private function findPath(array $lines, array $parts)
|
||||
{
|
||||
$test = true;
|
||||
$indent = -1;
|
||||
$current = array_shift($parts);
|
||||
|
||||
$j = 1;
|
||||
$found = [];
|
||||
$space = '';
|
||||
foreach ($lines as $i => $line) {
|
||||
if ($this->isLineEmpty($line)) {
|
||||
if ($this->isLineComment($line) && $this->getLineIndentation($line) > $indent) {
|
||||
$j = $i;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($test === true) {
|
||||
$test = false;
|
||||
$spaces = strlen($line) - strlen(ltrim($line, ' '));
|
||||
if ($spaces <= $indent) {
|
||||
$found[$current] = -$j;
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
$indent = $spaces;
|
||||
$space = $indent ? str_repeat(' ', $indent) : '';
|
||||
}
|
||||
|
||||
|
||||
if (0 === \strncmp($line, $space, strlen($space))) {
|
||||
$pattern = "/^{$space}(['\"]?){$current}\\1\:/";
|
||||
|
||||
if (preg_match($pattern, $line)) {
|
||||
$found[$current] = $i;
|
||||
$current = array_shift($parts);
|
||||
if ($current === null) {
|
||||
return $found;
|
||||
}
|
||||
$test = true;
|
||||
}
|
||||
} else {
|
||||
$found[$current] = -$j;
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
$j = $i;
|
||||
}
|
||||
|
||||
$found[$current] = -$j;
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is blank or if it is a comment line.
|
||||
*
|
||||
* @param string $line Contents of the line
|
||||
* @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
|
||||
*/
|
||||
private function isLineEmpty(string $line): bool
|
||||
{
|
||||
return $this->isLineBlank($line) || $this->isLineComment($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is blank.
|
||||
*
|
||||
* @param string $line Contents of the line
|
||||
* @return bool Returns true if the current line is blank, false otherwise
|
||||
*/
|
||||
private function isLineBlank(string $line): bool
|
||||
{
|
||||
return '' === trim($line, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current line is a comment line.
|
||||
*
|
||||
* @param string $line Contents of the line
|
||||
* @return bool Returns true if the current line is a comment line, false otherwise
|
||||
*/
|
||||
private function isLineComment(string $line): bool
|
||||
{
|
||||
//checking explicitly the first char of the trim is faster than loops or strpos
|
||||
$ltrimmedLine = ltrim($line, ' ');
|
||||
|
||||
return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $line
|
||||
* @return bool
|
||||
*/
|
||||
private function isInlineComment(string $line): bool
|
||||
{
|
||||
return $this->getInlineComment($line) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $line
|
||||
* @return string|null
|
||||
*/
|
||||
private function getInlineComment(string $line): ?string
|
||||
{
|
||||
$pos = strpos($line, ' #');
|
||||
if (false === $pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = explode(' #', $line);
|
||||
$part = '';
|
||||
while ($part .= array_shift($parts)) {
|
||||
// Remove quoted values.
|
||||
$part = preg_replace('/(([\'"])[^\2]*\2)/', '', $part);
|
||||
$part = preg_split('/[\'"]/', $part, 2);
|
||||
if (!isset($part[1])) {
|
||||
$part = $part[0];
|
||||
array_unshift($parts, str_repeat(' ', strlen($part) - strlen(trim($part, ' '))));
|
||||
break;
|
||||
}
|
||||
$part = $part[1];
|
||||
}
|
||||
|
||||
|
||||
return implode(' #', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current line indentation.
|
||||
*
|
||||
* @param string $line
|
||||
* @return int The current line indentation
|
||||
*/
|
||||
private function getLineIndentation(string $line): int
|
||||
{
|
||||
return \strlen($line) - \strlen(ltrim($line, ' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $default Default value (or null).
|
||||
* @return mixed Value.
|
||||
*/
|
||||
private function get(string $name, $default = null)
|
||||
{
|
||||
$path = explode('.', $name) ?: [];
|
||||
$current = $this->items;
|
||||
|
||||
foreach ($path as $field) {
|
||||
if (is_array($current) && isset($current[$field])) {
|
||||
$current = $current[$field];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @param mixed $value New value.
|
||||
*/
|
||||
private function set(string $name, $value): void
|
||||
{
|
||||
$path = explode('.', $name) ?: [];
|
||||
$current = &$this->items;
|
||||
|
||||
foreach ($path as $field) {
|
||||
// Handle arrays and scalars.
|
||||
if (!is_array($current)) {
|
||||
$current = [$field => []];
|
||||
} elseif (!isset($current[$field])) {
|
||||
$current[$field] = [];
|
||||
}
|
||||
$current = &$current[$field];
|
||||
}
|
||||
|
||||
$current = $value;
|
||||
$this->updated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
*/
|
||||
private function undef(string $name): void
|
||||
{
|
||||
$path = $name !== '' ? explode('.', $name) : [];
|
||||
if (!$path) {
|
||||
return;
|
||||
}
|
||||
|
||||
$var = array_pop($path);
|
||||
$current = &$this->items;
|
||||
|
||||
foreach ($path as $field) {
|
||||
if (!is_array($current) || !isset($current[$field])) {
|
||||
return;
|
||||
}
|
||||
$current = &$current[$field];
|
||||
}
|
||||
|
||||
unset($current[$var]);
|
||||
$this->updated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value by using dot notation for nested arrays/objects.
|
||||
*
|
||||
* @param string $name Dot separated path to the requested value.
|
||||
* @return bool
|
||||
*/
|
||||
private function canDefine(string $name): bool
|
||||
{
|
||||
$path = explode('.', $name) ?: [];
|
||||
$current = $this->items;
|
||||
|
||||
foreach ($path as $field) {
|
||||
if (is_array($current)) {
|
||||
if (!isset($current[$field])) {
|
||||
return true;
|
||||
}
|
||||
$current = $current[$field];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
24
system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php
Normal file
24
system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Grav\Installer\InstallException;
|
||||
use Grav\Installer\VersionUpdate;
|
||||
use Grav\Installer\YamlUpdater;
|
||||
|
||||
return [
|
||||
'preflight' =>
|
||||
function () {
|
||||
/** @var VersionUpdate $this */
|
||||
try {
|
||||
// Keep old defaults for backwards compatibility.
|
||||
$yaml = YamlUpdater::instance(GRAV_ROOT . '/user/config/system.yaml');
|
||||
$yaml->define('twig.autoescape', false);
|
||||
$yaml->define('strict_mode.yaml_compat', true);
|
||||
$yaml->define('strict_mode.twig_compat', true);
|
||||
$yaml->define('strict_mode.blueprint_compat', true);
|
||||
$yaml->save();
|
||||
} catch (\Exception $e) {
|
||||
throw new InstallException('Could not update system configuration to maintain backwards compatibility', $e);
|
||||
}
|
||||
},
|
||||
'postflight' => null
|
||||
];
|
||||
Reference in New Issue
Block a user