diff --git a/CHANGELOG.md b/CHANGELOG.md
index 35fdff1f3..14070c11e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/composer.json b/composer.json
index a055e5aa3..02c12c3de 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml
index 573fe2012..9fa9f0194 100644
--- a/system/blueprints/config/system.yaml
+++ b/system/blueprints/config/system.yaml
@@ -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
diff --git a/system/config/system.yaml b/system/config/system.yaml
index c8d8b04bc..6484bc95d 100644
--- a/system/config/system.yaml
+++ b/system/config/system.yaml
@@ -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
diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php
index d61bad8a9..65a2a3ff0 100644
--- a/system/src/Grav/Common/Page/Pages.php
+++ b/system/src/Grav/Common/Page/Pages.php
@@ -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();
diff --git a/system/src/Grav/Installer/Install.php b/system/src/Grav/Installer/Install.php
index 7d4ca4eea..ba8b22434 100644
--- a/system/src/Grav/Installer/Install.php
+++ b/system/src/Grav/Installer/Install.php
@@ -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']} >= v{$req['minimum']} required, you have v{$req['installed']}";
}
- throw new \RuntimeException(implode("
\n", $error));
+ $errors = implode("
\n", $error);
+ if (\defined('GRAV_CLI') && GRAV_CLI) {
+ $errors = "\n\n" . strip_tags($errors) . "\n\n";
+ $errors .= <<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);
+ }
}
diff --git a/system/src/Grav/Installer/InstallException.php b/system/src/Grav/Installer/InstallException.php
new file mode 100644
index 000000000..4471107d9
--- /dev/null
+++ b/system/src/Grav/Installer/InstallException.php
@@ -0,0 +1,29 @@
+getCode(), $previous);
+ }
+}
diff --git a/system/src/Grav/Installer/VersionUpdate.php b/system/src/Grav/Installer/VersionUpdate.php
new file mode 100644
index 000000000..faaf2a2d9
--- /dev/null
+++ b/system/src/Grav/Installer/VersionUpdate.php
@@ -0,0 +1,82 @@
+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);
+ }
+ }
+}
diff --git a/system/src/Grav/Installer/VersionUpdater.php b/system/src/Grav/Installer/VersionUpdater.php
new file mode 100644
index 000000000..e90ba4f42
--- /dev/null
+++ b/system/src/Grav/Installer/VersionUpdater.php
@@ -0,0 +1,114 @@
+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');
+ }
+}
diff --git a/system/src/Grav/Installer/Versions.php b/system/src/Grav/Installer/Versions.php
new file mode 100644
index 000000000..76a6ecfd9
--- /dev/null
+++ b/system/src/Grav/Installer/Versions.php
@@ -0,0 +1,308 @@
+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) : [];
+ }
+}
diff --git a/system/src/Grav/Installer/YamlUpdater.php b/system/src/Grav/Installer/YamlUpdater.php
new file mode 100644
index 000000000..628167a9a
--- /dev/null
+++ b/system/src/Grav/Installer/YamlUpdater.php
@@ -0,0 +1,407 @@
+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;
+ }
+}
diff --git a/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php b/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php
new file mode 100644
index 000000000..554bfd8b1
--- /dev/null
+++ b/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php
@@ -0,0 +1,24 @@
+
+ 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
+];