diff --git a/CHANGELOG.md b/CHANGELOG.md
index 764aff8f5..fb86aede2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+# v1.0.0-refactor
+## XX/XX/2015
+
+1. [](#new)
+ * Refactor Data classes to use NestedArrayAccess instead of DataMutatorTrait
+ * Data objects: Allow function call chaining
+ * Data objects: Lazy load blueprints only if needed
+ * Refactor Config classes
+
+# v1.0.0-rc.6
+## XX/XX/2015
+
+1. [](#bugfix)
+ * Fixed help output for `bin/plugin`
+
# v1.0.0-rc.5
## 11/20/2015
@@ -6,7 +21,7 @@
* Implemented the ability for Plugins to provide their own CLI commands through `bin/plugin`
* Added Croatian translation
* Added missing `umask_fix` property to `system.yaml`
- * Added current theme's config to global config. E.g. `config.theme.dropdown_enabled`
+ * Added current theme's config to global config. E.g. `config.theme.dropdown_enabled`
* Added `append_url_extension` option to system config & page headers
* Users have a new `state` property to allow disabling/banning
* Added new `Page.relativePagePath()` helper method
@@ -78,7 +93,7 @@
* German language improvements
* Updated bundled composer
1. [](#bugfix)
- * Accept variety of `true` values in `User.authorize()` method
+ * Accept variety of `true` values in `User.authorize()` method
* Fix for `Validation` throwing an error if no label set
# v1.0.0-rc.1
diff --git a/bin/plugin b/bin/plugin
index dc63f4a9d..ffe2dd81f 100755
--- a/bin/plugin
+++ b/bin/plugin
@@ -44,7 +44,7 @@ $grav['plugins']->init();
$grav['themes']->init();
$app = new Application('Grav Plugins Commands', GRAV_VERSION);
-$pattern = '/([A-Z]\w+Command\.php)$/usm';
+$pattern = '([A-Z]\w+Command\.php)';
// get arguments and strip the application name
if (null === $argv) {
@@ -70,7 +70,7 @@ if (!$name) {
$output->writeln('');
$output->writeln("Example:");
$output->writeln(" {$bin} error log -l 1 --trace");
- $list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '\/cli\/' . $pattern]);
+ $list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm']);
if (count($list)) {
$available = [];
@@ -101,7 +101,7 @@ if ($plugin === null) {
$path = 'plugins://' . $name . '/cli';
try {
- $commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => $pattern]);
+ $commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm']);
} catch (\RuntimeException $e) {
$output->writeln("No Console Commands for '{$name}' where found in '{$path}'");
exit;
diff --git a/composer.json b/composer.json
index 724cff2f8..a4a62a98e 100644
--- a/composer.json
+++ b/composer.json
@@ -21,9 +21,15 @@
"mrclay/minify": "~2.2",
"donatj/phpuseragentparser": "~0.3",
"pimple/pimple": "~3.0",
- "rockettheme/toolbox": "1.1.*",
+ "rockettheme/toolbox": "dev-develop",
"maximebf/debugbar": "~1.10"
},
+ "repositories": [
+ {
+ "type": "vcs",
+ "url": "https://github.com/rockettheme/toolbox"
+ }
+ ],
"autoload": {
"psr-4": {
"Grav\\": "system/src/Grav"
diff --git a/composer.lock b/composer.lock
index c6c4fe6d9..c19bcb526 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "e1db721096772d41f16003b39b47c85a",
- "content-hash": "294dd2282a332d96b19d163ad08e7ba7",
+ "hash": "3633e92a6e340a26229689d9b74031c2",
+ "content-hash": "9c314f997d29d7a78d3cf14f5976e6df",
"packages": [
{
"name": "doctrine/cache",
@@ -226,7 +226,7 @@
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": ">=5.3.3"
},
"require-dev": {
"mockery/mockery": "0.9.*"
@@ -666,16 +666,16 @@
},
{
"name": "rockettheme/toolbox",
- "version": "1.1.4",
+ "version": "dev-develop",
"source": {
"type": "git",
"url": "https://github.com/rockettheme/toolbox.git",
- "reference": "ff677d8f66d1addd3590d0cb85bcbaff4174d9c9"
+ "reference": "defdf78a4c2f537067c6548c99560b78ed92745d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/ff677d8f66d1addd3590d0cb85bcbaff4174d9c9",
- "reference": "ff677d8f66d1addd3590d0cb85bcbaff4174d9c9",
+ "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/defdf78a4c2f537067c6548c99560b78ed92745d",
+ "reference": "defdf78a4c2f537067c6548c99560b78ed92745d",
"shasum": ""
},
"require": {
@@ -701,7 +701,6 @@
"RocketTheme\\Toolbox\\StreamWrapper\\": "StreamWrapper/src"
}
},
- "notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
@@ -711,7 +710,11 @@
"php",
"rockettheme"
],
- "time": "2015-10-15 23:27:40"
+ "support": {
+ "source": "https://github.com/rockettheme/toolbox/tree/develop",
+ "issues": "https://github.com/rockettheme/toolbox/issues"
+ },
+ "time": "2015-11-19 15:03:27"
},
{
"name": "symfony/console",
@@ -994,7 +997,8 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
- "filp/whoops": 20
+ "filp/whoops": 20,
+ "rockettheme/toolbox": 20
},
"prefer-stable": false,
"prefer-lowest": false,
diff --git a/system/languages/fr.yaml b/system/languages/fr.yaml
index bfc1c117f..dbed71270 100644
--- a/system/languages/fr.yaml
+++ b/system/languages/fr.yaml
@@ -1,26 +1,60 @@
+FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Erreur : Frontmatter invalide\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
INFLECTOR_PLURALS:
- '/$/': 's'
- '/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)$/': '\1x'
- '/(bleu|émeu|landau|lieu|pneu|sarrau)$/': '\1s'
- '/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/': '\1aux'
- '/(s|x|z)$/': '\1'
- '/ail$/': 'ails'
- '/al$/': 'aux'
+ '/(quiz)$/i': '\1zes'
+ '/^(ox)$/i': '\1en'
+ '/([m|l])ouse$/i': '\1ice'
+ '/(matr|vert|ind)ix|ex$/i': '\1ices'
+ '/(x|ch|ss|sh)$/i': '\1es'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([^aeiouy]|qu)y$/i': '\1ies'
+ '/(hive)$/i': '\1s'
+ '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
+ '/sis$/i': 'ses'
+ '/([ti])um$/i': '\1a'
+ '/(buffal|tomat)o$/i': '\1oes'
+ '/(bu)s$/i': '\1ses'
+ '/(alias|status)/i': '\1es'
+ '/(octop|vir)us$/i': '\1i'
+ '/(ax|test)is$/i': '\1es'
'/s$/i': 's'
+ '/$/': 's'
INFLECTOR_SINGULAR:
- '/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/': '\1'
- '/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/': '\1ail'
- '/(journ|chev)aux$/': '\1al'
- '/ails$/': 'ail'
+ '/(quiz)zes$/i': '\1'
+ '/(matr)ices$/i': '\1ix'
+ '/(vert|ind)ices$/i': '\1ex'
+ '/^(ox)en/i': '\1'
+ '/(alias|status)es$/i': '\1'
+ '/([octop|vir])i$/i': '\1us'
+ '/(cris|ax|test)es$/i': '\1is'
+ '/(shoe)s$/i': '\1'
+ '/(o)es$/i': '\1'
+ '/(bus)es$/i': '\1'
+ '/([m|l])ice$/i': '\1ouse'
+ '/(x|ch|ss|sh)es$/i': '\1'
+ '/(m)ovies$/i': '\1ovie'
+ '/(s)eries$/i': '\1eries'
+ '/([^aeiouy]|qu)ies$/i': '\1y'
+ '/([lr])ves$/i': '\1f'
+ '/(tive)s$/i': '\1'
+ '/(hive)s$/i': '\1'
+ '/([^f])ves$/i': '\1fe'
+ '/(^analy)ses$/i': '\1sis'
+ '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
+ '/([ti])a$/i': '\1um'
+ '/(n)ews$/i': '\1ews'
'/s$/i': ''
+INFLECTOR_UNCOUNTABLE: ['équipment', 'information', 'riz', 'argent', 'espèces', 'séries', 'poisson', 'mouton']
INFLECTOR_IRREGULAR:
- 'madame': 'mesdames'
- 'mademoiselle': 'mesdemoiselles'
- 'monsieur': 'messieurs'
+ 'person': 'personnes'
+ 'man': 'Hommes'
+ 'child': 'enfants'
+ 'sex': 'sexes'
+ 'move': 'déplacemements'
INFLECTOR_ORDINALS:
'default': 'ème'
'first': 'er'
'second': 'nd'
+ 'third': 'ème'
NICETIME:
NO_DATE_PROVIDED: Aucune date
BAD_DATE: Date erronée
@@ -48,7 +82,7 @@ NICETIME:
DAY_PLURAL: jours
WEEK_PLURAL: semaines
MONTH_PLURAL: mois
- YEAR_PLURAL: ans
+ YEAR_PLURAL: années
DECADE_PLURAL: décennies
SEC_PLURAL: s
MIN_PLURAL: m
@@ -58,3 +92,6 @@ NICETIME:
MO_PLURAL: m
YR_PLURAL: a
DEC_PLURAL: d
+FORM:
+ VALIDATION_FAIL: La validation a échoué :
+ INVALID_INPUT: Saisie non valide
diff --git a/system/languages/hu.yaml b/system/languages/hu.yaml
new file mode 100644
index 000000000..71c0ee91e
--- /dev/null
+++ b/system/languages/hu.yaml
@@ -0,0 +1,52 @@
+FRONTMATTER_ERROR_PAGE: "---\ncím: %1$s\n---\n\n# Hiba: Érvénytelen Frontmatter\n\nElérési út: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+INFLECTOR_IRREGULAR:
+ 'person': 'személyek'
+ 'man': 'férfiak'
+ 'child': 'gyerekek'
+ 'sex': 'nemek'
+ 'move': 'lépések'
+INFLECTOR_ORDINALS:
+ 'default': '.'
+ 'first': '.'
+ 'second': '.'
+ 'third': '.'
+NICETIME:
+ NO_DATE_PROVIDED: Nincs dátum megadva
+ BAD_DATE: Hibás dátum
+ AGO: elteltével
+ FROM_NOW: mostantól
+ SECOND: másodperc
+ MINUTE: perc
+ HOUR: óra
+ DAY: nap
+ WEEK: hét
+ MONTH: hónap
+ YEAR: év
+ DECADE: évtized
+ SEC: mp
+ MIN: p
+ HR: ó
+ DAY: nap
+ WK: hét
+ MO: hó
+ YR: év
+ DEC: évt
+ SECOND_PLURAL: másodperc
+ MINUTE_PLURAL: perc
+ HOUR_PLURAL: óra
+ DAY_PLURAL: nap
+ WEEK_PLURAL: hét
+ MONTH_PLURAL: hónap
+ YEAR_PLURAL: év
+ DECADE_PLURAL: évtized
+ SEC_PLURAL: mp
+ MIN_PLURAL: perc
+ HR_PLURAL: ó
+ DAY_PLURAL: nap
+ WK_PLURAL: hét
+ MO_PLURAL: hó
+ YR_PLURAL: év
+ DEC_PLURAL: évt
+FORM:
+ VALIDATION_FAIL: A validáció hibát talált:
+ INVALID_INPUT: Az itt megadott érték érvénytelen:
diff --git a/system/src/Grav/Common/Config/Blueprints.php b/system/src/Grav/Common/Config/Blueprints.php
deleted file mode 100644
index 1af61f47c..000000000
--- a/system/src/Grav/Common/Config/Blueprints.php
+++ /dev/null
@@ -1,207 +0,0 @@
-grav = $grav ?: Grav::instance();
- }
-
- public function init()
- {
- /** @var UniformResourceLocator $locator */
- $locator = $this->grav['locator'];
-
- $blueprints = $locator->findResources('blueprints://config');
- $plugins = $locator->findResources('plugins://');
-
- $blueprintFiles = $this->getBlueprintFiles($blueprints, $plugins);
-
- $this->loadCompiledBlueprints($plugins + $blueprints, $blueprintFiles);
- }
-
- protected function loadCompiledBlueprints($blueprints, $blueprintFiles)
- {
- $checksum = md5(serialize($blueprints));
- $filename = CACHE_DIR . 'compiled/blueprints/' . $checksum .'.php';
- $checksum .= ':'.md5(serialize($blueprintFiles));
- $class = get_class($this);
- $file = PhpFile::instance($filename);
-
- if ($file->exists()) {
- $cache = $file->exists() ? $file->content() : null;
- } else {
- $cache = null;
- }
-
-
- // Load real file if cache isn't up to date (or is invalid).
- if (
- !is_array($cache)
- || empty($cache['checksum'])
- || empty($cache['$class'])
- || $cache['checksum'] != $checksum
- || $cache['@class'] != $class
- ) {
- // Attempt to lock the file for writing.
- $file->lock(false);
-
- // Load blueprints.
- $this->blueprints = new Blueprints();
- foreach ($blueprintFiles as $key => $files) {
- $this->loadBlueprints($key);
- }
-
- $cache = [
- '@class' => $class,
- 'checksum' => $checksum,
- 'files' => $blueprintFiles,
- 'data' => $this->blueprints->toArray()
- ];
-
- // If compiled file wasn't already locked by another process, save it.
- if ($file->locked() !== false) {
- $file->save($cache);
- $file->unlock();
- }
- } else {
- $this->blueprints = new Blueprints($cache['data']);
- }
- }
-
- /**
- * Load global blueprints.
- *
- * @param string $key
- * @param array $files
- */
- public function loadBlueprints($key, array $files = null)
- {
- if (is_null($files)) {
- $files = $this->files[$key];
- }
- foreach ($files as $name => $item) {
- $file = CompiledYamlFile::instance($item['file']);
- $this->blueprints->embed($name, $file->content(), '/');
- }
- }
-
- /**
- * Get all blueprint files (including plugins).
- *
- * @param array $blueprints
- * @param array $plugins
- * @return array
- */
- protected function getBlueprintFiles(array $blueprints, array $plugins)
- {
- $list = [];
- foreach (array_reverse($plugins) as $folder) {
- $list += $this->detectPlugins($folder, true);
- }
- foreach (array_reverse($blueprints) as $folder) {
- $list += $this->detectConfig($folder, true);
- }
- return $list;
- }
-
- /**
- * Detects all plugins with a configuration file and returns last modification time.
- *
- * @param string $lookup Location to look up from.
- * @param bool $blueprints
- * @return array
- * @internal
- */
- protected function detectPlugins($lookup = SYSTEM_DIR, $blueprints = false)
- {
- $find = $blueprints ? 'blueprints.yaml' : '.yaml';
- $location = $blueprints ? 'blueprintFiles' : 'configFiles';
- $path = trim(Folder::getRelativePath($lookup), '/');
- if (isset($this->{$location}[$path])) {
- return [$path => $this->{$location}[$path]];
- }
-
- $list = [];
-
- if (is_dir($lookup)) {
- $iterator = new \DirectoryIterator($lookup);
-
- /** @var \DirectoryIterator $directory */
- foreach ($iterator as $directory) {
- if (!$directory->isDir() || $directory->isDot()) {
- continue;
- }
-
- $name = $directory->getBasename();
- $filename = "{$path}/{$name}/" . ($find && $find[0] != '.' ? $find : $name . $find);
-
- if (is_file($filename)) {
- $list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
- }
- }
- }
-
- $this->{$location}[$path] = $list;
-
- return [$path => $list];
- }
-
- /**
- * Detects all plugins with a configuration file and returns last modification time.
- *
- * @param string $lookup Location to look up from.
- * @param bool $blueprints
- * @return array
- * @internal
- */
- protected function detectConfig($lookup = SYSTEM_DIR, $blueprints = false)
- {
- $location = $blueprints ? 'blueprintFiles' : 'configFiles';
- $path = trim(Folder::getRelativePath($lookup), '/');
- if (isset($this->{$location}[$path])) {
- return [$path => $this->{$location}[$path]];
- }
-
- if (is_dir($lookup)) {
- // Find all system and user configuration files.
- $options = [
- 'compare' => 'Filename',
- 'pattern' => '|\.yaml$|',
- 'filters' => [
- 'key' => '|\.yaml$|',
- 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
- return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
- }],
- 'key' => 'SubPathname'
- ];
-
- $list = Folder::all($lookup, $options);
- } else {
- $list = [];
- }
-
- $this->{$location}[$path] = $list;
-
- return [$path => $list];
- }
-}
diff --git a/system/src/Grav/Common/Config/CompiledBase.php b/system/src/Grav/Common/Config/CompiledBase.php
new file mode 100644
index 000000000..f861cef34
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledBase.php
@@ -0,0 +1,236 @@
+cacheFolder = $cacheFolder;
+ $this->files = $files;
+ $this->path = $path ? rtrim($path, '\\/') . '/' : '';
+ }
+
+ /**
+ * Get filename for the compiled PHP file.
+ *
+ * @param string $name
+ * @return $this
+ */
+ public function name($name = null)
+ {
+ if (!$this->name) {
+ $this->name = $name ?: md5(json_encode(array_keys($this->files)));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Function gets called when cached configuration is saved.
+ */
+ public function modified() {}
+
+ /**
+ * Load the configuration.
+ *
+ * @return mixed
+ */
+ public function load()
+ {
+ if ($this->object) {
+ return $this->object;
+ }
+
+ $filename = $this->createFilename();
+ if (!$this->loadCompiledFile($filename) && $this->loadFiles()) {
+ $this->saveCompiledFile($filename);
+ }
+
+ return $this->object;
+ }
+
+ /**
+ * Returns checksum from the configuration files.
+ *
+ * You can set $this->checksum = false to disable this check.
+ *
+ * @return bool|string
+ */
+ public function checksum()
+ {
+ if (!isset($this->checksum)) {
+ $this->checksum = md5(json_encode($this->files) . $this->version);
+ }
+
+ return $this->checksum;
+ }
+
+ protected function createFilename()
+ {
+ return "{$this->cacheFolder}/{$this->name()->name}.php";
+ }
+
+ /**
+ * Create configuration object.
+ *
+ * @param array $data
+ */
+ abstract protected function createObject(array $data = []);
+
+ /**
+ * Finalize configuration object.
+ */
+ abstract protected function finalizeObject();
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ abstract protected function loadFile($name, $filename);
+
+ /**
+ * Load and join all configuration files.
+ *
+ * @return bool
+ * @internal
+ */
+ protected function loadFiles()
+ {
+ $this->createObject();
+
+ $list = array_reverse($this->files);
+ foreach ($list as $files) {
+ foreach ($files as $name => $item) {
+ $this->loadFile($name, $this->path . $item['file']);
+ }
+ }
+
+ $this->finalizeObject();
+
+ return true;
+ }
+
+ /**
+ * Load compiled file.
+ *
+ * @param string $filename
+ * @return bool
+ * @internal
+ */
+ protected function loadCompiledFile($filename)
+ {
+ if (!file_exists($filename)) {
+ return false;
+ }
+
+ $cache = include $filename;
+ if (
+ !is_array($cache)
+ || !isset($cache['checksum'])
+ || !isset($cache['data'])
+ || !isset($cache['@class'])
+ || $cache['@class'] != get_class($this)
+ ) {
+ return false;
+ }
+
+ // Load real file if cache isn't up to date (or is invalid).
+ if ($cache['checksum'] !== $this->checksum()) {
+ return false;
+ }
+
+ $this->createObject($cache['data']);
+
+ return true;
+ }
+
+ /**
+ * Save compiled file.
+ *
+ * @param string $filename
+ * @throws \RuntimeException
+ * @internal
+ */
+ protected function saveCompiledFile($filename)
+ {
+ $file = PhpFile::instance($filename);
+
+ // Attempt to lock the file for writing.
+ try {
+ $file->lock(false);
+ } catch (\Exception $e) {
+ // Another process has locked the file; we will check this in a bit.
+ }
+
+ if ($file->locked() === false) {
+ // File was already locked by another process.
+ return;
+ }
+
+ $cache = [
+ '@class' => get_class($this),
+ 'timestamp' => time(),
+ 'checksum' => $this->checksum(),
+ 'files' => $this->files,
+ 'data' => $this->object->toArray()
+ ];
+
+ $file->save($cache);
+ $file->unlock();
+ $file->free();
+
+ $this->modified();
+ }
+}
diff --git a/system/src/Grav/Common/Config/CompiledBlueprints.php b/system/src/Grav/Common/Config/CompiledBlueprints.php
new file mode 100644
index 000000000..d78a0e04e
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledBlueprints.php
@@ -0,0 +1,49 @@
+object = new Blueprints($data);
+ }
+
+ /**
+ * Finalize configuration object.
+ */
+ protected function finalizeObject() {}
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ protected function loadFile($name, $filename)
+ {
+ $file = CompiledYamlFile::instance($filename);
+ $this->object->embed($name, $file->content(), '/');
+ $file->free();
+ }
+}
diff --git a/system/src/Grav/Common/Config/CompiledConfig.php b/system/src/Grav/Common/Config/CompiledConfig.php
new file mode 100644
index 000000000..cb9e59de2
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledConfig.php
@@ -0,0 +1,98 @@
+callable = $blueprints;
+
+ return $this;
+ }
+
+ /**
+ * @param bool $withDefaults
+ * @return mixed
+ */
+ public function load($withDefaults = false)
+ {
+ $this->withDefaults = $withDefaults;
+
+ return parent::load();
+ }
+
+ /**
+ * Create configuration object.
+ *
+ * @param array $data
+ */
+ protected function createObject(array $data = [])
+ {
+ if ($this->withDefaults && empty($data) && is_callable($this->callable)) {
+ $blueprints = $this->callable;
+ $data = $blueprints()->getDefaults();
+ }
+
+ $this->object = new Config($data, $this->callable);
+ }
+
+ /**
+ * Finalize configuration object.
+ */
+ protected function finalizeObject()
+ {
+ $this->object->checksum($this->checksum());
+ }
+
+ /**
+ * Function gets called when cached configuration is saved.
+ */
+ public function modified()
+ {
+ $this->object->modified(true);
+ }
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ protected function loadFile($name, $filename)
+ {
+ $file = CompiledYamlFile::instance($filename);
+ $this->object->join($name, $file->content(), '/');
+ $file->free();
+ }
+}
diff --git a/system/src/Grav/Common/Config/CompiledLanguages.php b/system/src/Grav/Common/Config/CompiledLanguages.php
new file mode 100644
index 000000000..ce4eb59dc
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledLanguages.php
@@ -0,0 +1,64 @@
+object = new Languages($data);
+ }
+
+ /**
+ * Finalize configuration object.
+ */
+ protected function finalizeObject()
+ {
+ $this->object->checksum($this->checksum());
+ }
+
+
+ /**
+ * Function gets called when cached configuration is saved.
+ */
+ public function modified()
+ {
+ $this->object->modified(true);
+ }
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ protected function loadFile($name, $filename)
+ {
+ $file = CompiledYamlFile::instance($filename);
+ if (preg_match('|languages\.yaml$|', $filename)) {
+ $this->object->mergeRecursive($file->content());
+ } else {
+ $this->object->join($name, $file->content(), '/');
+ }
+ $file->free();
+ }
+}
diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php
index ed13dbcbd..dc5169750 100644
--- a/system/src/Grav/Common/Config/Config.php
+++ b/system/src/Grav/Common/Config/Config.php
@@ -1,12 +1,10 @@
[
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['system'],
- ]
- ],
- 'user' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user'],
- ]
- ],
- 'asset' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['assets'],
- ]
- ],
- 'blueprints' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://blueprints', 'system/blueprints'],
- ]
- ],
- 'config' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://config', 'system/config'],
- ]
- ],
- 'plugins' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://plugins'],
- ]
- ],
- 'plugin' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://plugins'],
- ]
- ],
- 'themes' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://themes'],
- ]
- ],
- 'languages' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://languages', 'system/languages'],
- ]
- ],
- 'cache' => [
- 'type' => 'Stream',
- 'prefixes' => [
- '' => ['cache'],
- 'images' => ['images']
- ]
- ],
- 'log' => [
- 'type' => 'Stream',
- 'prefixes' => [
- '' => ['logs']
- ]
- ],
- 'backup' => [
- 'type' => 'Stream',
- 'prefixes' => [
- '' => ['backup']
- ]
- ]
- ];
-
- protected $setup = [];
-
- protected $blueprintFiles = [];
- protected $configFiles = [];
- protected $languageFiles = [];
protected $checksum;
- protected $timestamp;
-
- protected $configLookup;
- protected $blueprintLookup;
- protected $pluginLookup;
- protected $languagesLookup;
-
- protected $finder;
- protected $environment;
- protected $messages = [];
-
- protected $languages;
-
- public function __construct(array $setup = array(), Grav $grav = null, $environment = null)
- {
- $this->grav = $grav ?: Grav::instance();
- $this->finder = new ConfigFinder;
- $this->environment = $environment ?: 'localhost';
- $this->messages[] = 'Environment Name: ' . $this->environment;
-
- // Make sure that
- if (!isset($setup['streams']['schemes'])) {
- $setup['streams']['schemes'] = [];
- }
- $setup['streams']['schemes'] += $this->streams;
-
- $setup = $this->autoDetectEnvironmentConfig($setup);
-
- $this->setup = $setup;
- parent::__construct($setup);
-
- $this->check();
- }
+ protected $modified = false;
public function key()
{
return $this->checksum();
}
- public function reload()
+ public function checksum($checksum = null)
{
- $this->items = $this->setup;
- $this->check();
- $this->init();
- $this->debug();
-
- return $this;
- }
-
- protected function check()
- {
- $streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
- if (!is_array($streams)) {
- throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
- }
- $diff = array_keys(array_diff_key($this->streams, $streams));
- if ($diff) {
- throw new \InvalidArgumentException(
- sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
- );
- }
- }
-
- public function debug()
- {
- foreach ($this->messages as $message) {
- $this->grav['debugger']->addMessage($message);
- }
- $this->messages = [];
- }
-
- public function init()
- {
- /** @var UniformResourceLocator $locator */
- $locator = $this->grav['locator'];
-
- $this->configLookup = $locator->findResources('config://');
- $this->blueprintLookup = $locator->findResources('blueprints://config');
- $this->pluginLookup = $locator->findResources('plugins://');
-
-
- $this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master');
- $this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master');
-
- // process languages if supported
- if ($this->get('system.languages.translations', true)) {
- $this->languagesLookup = $locator->findResources('languages://');
- $this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master');
- }
-
- $this->initializeLocator($locator);
- }
-
- public function checksum()
- {
- if (empty($this->checksum)) {
- $checkBlueprints = $this->get('system.cache.check.blueprints', false);
- $checkLanguages = $this->get('system.cache.check.languages', false);
- $checkConfig = $this->get('system.cache.check.config', true);
- $checkSystem = $this->get('system.cache.check.system', true);
-
- if (!$checkBlueprints && !$checkLanguages && !$checkConfig && !$checkSystem) {
- $this->messages[] = 'Skip configuration timestamp check.';
- return false;
- }
-
- // Generate checksum according to the configuration settings.
- if (!$checkConfig) {
- // Just check changes in system.yaml files and ignore all the other files.
- $cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
- } else {
- // Check changes in all configuration files.
- $cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
- }
-
- if ($checkBlueprints) {
- $cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
- } else {
- $cb = [];
- }
-
- if ($checkLanguages) {
- $cl = $this->finder->locateLanguageFiles($this->languagesLookup, $this->pluginLookup);
- } else {
- $cl = [];
- }
-
- $this->checksum = md5(json_encode([$cc, $cb, $cl]));
+ if ($checksum !== null) {
+ $this->checksum = $checksum;
}
return $this->checksum;
}
- protected function autoDetectEnvironmentConfig($items)
+ public function modified($modified = null)
{
- $environment = $this->environment;
- $env_stream = 'user://'.$environment.'/config';
-
- if (file_exists(USER_DIR.$environment.'/config')) {
- array_unshift($items['streams']['schemes']['config']['prefixes'][''], $env_stream);
+ if ($modified !== null) {
+ $this->modified = $modified;
}
- return $items;
+ return $this->modified;
}
- protected function loadCompiledBlueprints($blueprints, $plugins, $filename = null)
+ public function reload()
{
- $checksum = md5(json_encode($blueprints));
- $filename = $filename
- ? CACHE_DIR . 'compiled/blueprints/' . $filename . '-' . $this->environment . '.php'
- : CACHE_DIR . 'compiled/blueprints/' . $checksum . '-' . $this->environment . '.php';
- $file = PhpFile::instance($filename);
- $cache = $file->exists() ? $file->content() : null;
- $blueprintFiles = $this->finder->locateBlueprintFiles($blueprints, $plugins);
- $checksum .= ':'.md5(json_encode($blueprintFiles));
- $class = get_class($this);
+ $grav = Grav::instance();
- // Load real file if cache isn't up to date (or is invalid).
- if (
- !is_array($cache)
- || !isset($cache['checksum'])
- || !isset($cache['@class'])
- || $cache['checksum'] != $checksum
- || $cache['@class'] != $class
- ) {
- // Attempt to lock the file for writing.
- $file->lock(false);
+ // Load new configuration.
+ $config = ConfigServiceProvider::load($grav);
- // Load blueprints.
- $this->blueprints = new Blueprints;
- foreach ($blueprintFiles as $files) {
- $this->loadBlueprintFiles($files);
- }
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
- $cache = [
- '@class' => $class,
- 'checksum' => $checksum,
- 'files' => $blueprintFiles,
- 'data' => $this->blueprints->toArray()
- ];
- // If compiled file wasn't already locked by another process, save it.
- if ($file->locked() !== false) {
- $this->messages[] = 'Saving compiled blueprints.';
- $file->save($cache);
- $file->unlock();
- }
- } else {
- $this->blueprints = new Blueprints($cache['data']);
+ if ($config->modified()) {
+ // Update current configuration.
+ $this->items = $config->toArray();
+ $this->checksum($config->checksum());
+ $this->modified(true);
+
+ $debugger->addMessage('Configuration was changed and saved.');
+ }
+
+ return $this;
+ }
+
+ public function debug()
+ {
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
+
+ $debugger->addMessage('Environment Name: ' . $this->environment);
+ if ($this->modified()) {
+ $debugger->addMessage('Configuration reloaded and cached.');
}
}
- protected function loadCompiledConfig($configs, $plugins, $filename = null)
+ public function init()
{
- $checksum = md5(json_encode($configs));
- $filename = $filename
- ? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
- : CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
- $file = PhpFile::instance($filename);
- $cache = $file->exists() ? $file->content() : null;
- $class = get_class($this);
- $checksum = $this->checksum();
-
- if (
- !is_array($cache)
- || !isset($cache['checksum'])
- || !isset($cache['@class'])
- || $cache['@class'] != $class
- ) {
- $this->messages[] = 'No cached configuration, compiling new configuration..';
- } else if ($cache['checksum'] !== $checksum) {
- $this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
- } else {
- $this->messages[] = 'Configuration checksum matches, using cached version.';
-
- $this->items = $cache['data'];
- return;
- }
-
- $configFiles = $this->finder->locateConfigFiles($configs, $plugins);
-
- // Attempt to lock the file for writing.
- $file->lock(false);
-
- // Load configuration.
- foreach ($configFiles as $files) {
- $this->loadConfigFiles($files);
- }
- $cache = [
- '@class' => $class,
- 'timestamp' => time(),
- 'checksum' => $checksum,
- 'data' => $this->toArray()
- ];
-
- // If compiled file wasn't already locked by another process, save it.
- if ($file->locked() !== false) {
- $this->messages[] = 'Saving compiled configuration.';
- $file->save($cache);
- $file->unlock();
- }
-
- $this->items = $cache['data'];
- }
-
- /**
- * @param $languages
- * @param $plugins
- * @param null $filename
- */
- protected function loadCompiledLanguages($languages, $plugins, $filename = null)
- {
- $checksum = md5(json_encode($languages));
- $filename = $filename
- ? CACHE_DIR . 'compiled/languages/' . $filename . '-' . $this->environment . '.php'
- : CACHE_DIR . 'compiled/languages/' . $checksum . '-' . $this->environment . '.php';
- $file = PhpFile::instance($filename);
- $cache = $file->exists() ? $file->content() : null;
- $languageFiles = $this->finder->locateLanguageFiles($languages, $plugins);
- $checksum .= ':' . md5(json_encode($languageFiles));
- $class = get_class($this);
-
- // Load real file if cache isn't up to date (or is invalid).
- if (
- !is_array($cache)
- || !isset($cache['checksum'])
- || !isset($cache['@class'])
- || $cache['checksum'] != $checksum
- || $cache['@class'] != $class
- ) {
- // Attempt to lock the file for writing.
- $file->lock(false);
-
- // Load languages.
- $this->languages = new Languages;
- $pluginPaths = str_ireplace(GRAV_ROOT . '/', '', array_reverse($plugins));
- foreach ($pluginPaths as $path) {
- if (isset($languageFiles[$path])) {
- foreach ((array) $languageFiles[$path] as $plugin => $item) {
- $lang_file = CompiledYamlFile::instance($item['file']);
- $content = $lang_file->content();
- $this->languages->mergeRecursive($content);
- }
- unset($languageFiles[$path]);
- }
- }
-
- foreach ($languageFiles as $location) {
- foreach ($location as $lang => $item) {
- $lang_file = CompiledYamlFile::instance($item['file']);
- $content = $lang_file->content();
- $this->languages->join($lang, $content, '/');
- }
- }
-
- $cache = [
- '@class' => $class,
- 'checksum' => $checksum,
- 'files' => $languageFiles,
- 'data' => $this->languages->toArray()
- ];
- // If compiled file wasn't already locked by another process, save it.
- if ($file->locked() !== false) {
- $this->messages[] = 'Saving compiled languages.';
- $file->save($cache);
- $file->unlock();
- }
- } else {
- $this->languages = new Languages($cache['data']);
- }
- }
-
- /**
- * Load blueprints.
- *
- * @param array $files
- */
- public function loadBlueprintFiles(array $files)
- {
- foreach ($files as $name => $item) {
- $file = CompiledYamlFile::instance($item['file']);
- $this->blueprints->embed($name, $file->content(), '/');
- }
- }
-
- /**
- * Load configuration.
- *
- * @param array $files
- */
- public function loadConfigFiles(array $files)
- {
- foreach ($files as $name => $item) {
- $file = CompiledYamlFile::instance($item['file']);
- $this->join($name, $file->content(), '/');
- }
- }
-
- /**
- * Initialize resource locator by using the configuration.
- *
- * @param UniformResourceLocator $locator
- */
- public function initializeLocator(UniformResourceLocator $locator)
- {
- $locator->reset();
-
- $schemes = (array) $this->get('streams.schemes', []);
-
- foreach ($schemes as $scheme => $config) {
- if (isset($config['paths'])) {
- $locator->addPath($scheme, '', $config['paths']);
- }
- if (isset($config['prefixes'])) {
- foreach ($config['prefixes'] as $prefix => $paths) {
- $locator->addPath($scheme, $prefix, $paths);
- }
+ $setup = Grav::instance()['setup']->toArray();
+ foreach ($setup as $key => $value) {
+ if ($key === 'streams' || !is_array($value)) {
+ // Optimized as streams and simple values are fully defined in setup.
+ $this->items[$key] = $value;
+ } else {
+ $this->joinDefaults($key, $value);
}
}
}
/**
- * Get available streams and their types from the configuration.
- *
- * @return array
+ * @return mixed
+ * @deprecated
*/
- public function getStreams()
- {
- $schemes = [];
- foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
- $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
- if ($type[0] != '\\') {
- $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
- }
-
- $schemes[$scheme] = $type;
- }
-
- return $schemes;
- }
-
public function getLanguages()
{
- return $this->languages;
+ return Grav::instance()['languages'];
}
}
diff --git a/system/src/Grav/Common/Config/ConfigFileFinder.php b/system/src/Grav/Common/Config/ConfigFileFinder.php
new file mode 100644
index 000000000..887575a95
--- /dev/null
+++ b/system/src/Grav/Common/Config/ConfigFileFinder.php
@@ -0,0 +1,258 @@
+base = $base ? "{$base}/" : '';
+
+ return $this;
+ }
+
+ /**
+ * Return all locations for all the files with a timestamp.
+ *
+ * @param array $paths List of folders to look from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ */
+ public function locateFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1)
+ {
+ $list = [];
+ foreach ($paths as $folder) {
+ $list += $this->detectRecursive($folder, $pattern, $levels);
+ }
+ return $list;
+ }
+
+ /**
+ * Return all locations for all the files with a timestamp.
+ *
+ * @param array $paths List of folders to look from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ */
+ public function getFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1)
+ {
+ $list = [];
+ foreach ($paths as $folder) {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ $files = $this->detectRecursive($folder, $pattern, $levels);
+
+ $list += $files[trim($path, '/')];
+ }
+ return $list;
+ }
+
+ /**
+ * Return all paths for all the files with a timestamp.
+ *
+ * @param array $paths List of folders to look from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ */
+ public function listFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1)
+ {
+ $list = [];
+ foreach ($paths as $folder) {
+ $list = array_merge_recursive($list, $this->detectAll($folder, $pattern, $levels));
+ }
+ return $list;
+ }
+
+ /**
+ * Find filename from a list of folders.
+ *
+ * Note: Only finds the last override.
+ *
+ * @param string $filename
+ * @param array $folders
+ * @return array
+ */
+ public function locateFileInFolder($filename, array $folders)
+ {
+ $list = [];
+ foreach ($folders as $folder) {
+ $list += $this->detectInFolder($folder, $filename);
+ }
+ return $list;
+ }
+
+ /**
+ * Find filename from a list of folders.
+ *
+ * @param array $folders
+ * @param string $filename
+ * @return array
+ */
+ public function locateInFolders(array $folders, $filename = null)
+ {
+ $list = [];
+ foreach ($folders as $folder) {
+ $path = trim(Folder::getRelativePath($folder), '/');
+ $list[$path] = $this->detectInFolder($folder, $filename);
+ }
+ return $list;
+ }
+
+ /**
+ * Return all existing locations for a single file with a timestamp.
+ *
+ * @param array $paths Filesystem paths to look up from.
+ * @param string $name Configuration file to be located.
+ * @param string $ext File extension (optional, defaults to .yaml).
+ * @return array
+ */
+ public function locateFile(array $paths, $name, $ext = '.yaml')
+ {
+ $filename = preg_replace('|[.\/]+|', '/', $name) . $ext;
+
+ $list = [];
+ foreach ($paths as $folder) {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ if (is_file("{$folder}/{$filename}")) {
+ $modified = filemtime("{$folder}/{$filename}");
+ } else {
+ $modified = 0;
+ }
+ $basename = $this->base . $name;
+ $list[$path] = [$basename => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
+ }
+
+ return $list;
+ }
+
+ /**
+ * Detects all directories with a configuration file and returns them with last modification time.
+ *
+ * @param string $folder Location to look up from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ * @internal
+ */
+ protected function detectRecursive($folder, $pattern, $levels)
+ {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ if (is_dir($folder)) {
+ // Find all system and user configuration files.
+ $options = [
+ 'levels' => $levels,
+ 'compare' => 'Filename',
+ 'pattern' => $pattern,
+ 'filters' => [
+ 'pre-key' => $this->base,
+ 'key' => $pattern,
+ 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
+ return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
+ }
+ ],
+ 'key' => 'SubPathname'
+ ];
+
+ $list = Folder::all($folder, $options);
+
+ ksort($list);
+ } else {
+ $list = [];
+ }
+
+ return [$path => $list];
+ }
+
+ /**
+ * Detects all directories with the lookup file and returns them with last modification time.
+ *
+ * @param string $folder Location to look up from.
+ * @param string $lookup Filename to be located (defaults to directory name).
+ * @return array
+ * @internal
+ */
+ protected function detectInFolder($folder, $lookup = null)
+ {
+ $folder = rtrim($folder, '/');
+ $path = trim(Folder::getRelativePath($folder), '/');
+ $base = $path === $folder ? '' : ($path ? substr($folder, 0, -strlen($path)) : $folder . '/');
+
+ $list = [];
+
+ if (is_dir($folder)) {
+ $iterator = new \DirectoryIterator($folder);
+
+ /** @var \DirectoryIterator $directory */
+ foreach ($iterator as $directory) {
+ if (!$directory->isDir() || $directory->isDot()) {
+ continue;
+ }
+
+ $name = $directory->getBasename();
+ $find = ($lookup ?: $name) . '.yaml';
+ $filename = "{$path}/{$name}/{$find}";
+
+ if (file_exists($base . $filename)) {
+ $basename = $this->base . $name;
+ $list[$basename] = ['file' => $filename, 'modified' => filemtime($base . $filename)];
+ }
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Detects all plugins with a configuration file and returns them with last modification time.
+ *
+ * @param string $folder Location to look up from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ * @internal
+ */
+ protected function detectAll($folder, $pattern, $levels)
+ {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ if (is_dir($folder)) {
+ // Find all system and user configuration files.
+ $options = [
+ 'levels' => $levels,
+ 'compare' => 'Filename',
+ 'pattern' => $pattern,
+ 'filters' => [
+ 'pre-key' => $this->base,
+ 'key' => $pattern,
+ 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
+ return ["{$path}/{$file->getSubPathname()}" => $file->getMTime()];
+ }
+ ],
+ 'key' => 'SubPathname'
+ ];
+
+ $list = Folder::all($folder, $options);
+
+ ksort($list);
+ } else {
+ $list = [];
+ }
+
+ return $list;
+ }
+}
diff --git a/system/src/Grav/Common/Config/ConfigFinder.php b/system/src/Grav/Common/Config/ConfigFinder.php
deleted file mode 100644
index 46069410d..000000000
--- a/system/src/Grav/Common/Config/ConfigFinder.php
+++ /dev/null
@@ -1,186 +0,0 @@
-detectInFolder($folder, 'blueprints');
- }
- foreach (array_reverse($blueprints) as $folder) {
- $list += $this->detectRecursive($folder);
- }
- return $list;
- }
-
- /**
- * Get all locations for configuration files (including plugins).
- *
- * @param array $configs
- * @param array $plugins
- * @return array
- */
- public function locateConfigFiles(array $configs, array $plugins)
- {
- $list = [];
- foreach (array_reverse($plugins) as $folder) {
- $list += $this->detectInFolder($folder);
- }
- foreach (array_reverse($configs) as $folder) {
- $list += $this->detectRecursive($folder);
- }
- return $list;
- }
-
- public function locateLanguageFiles(array $languages, array $plugins)
- {
- $list = [];
- foreach (array_reverse($plugins) as $folder) {
- $list += $this->detectLanguagesInFolder($folder, 'languages');
- }
- foreach (array_reverse($languages) as $folder) {
- $list += $this->detectRecursive($folder);
- }
- return $list;
- }
-
- /**
- * Get all locations for a single configuration file.
- *
- * @param array $folders Locations to look up from.
- * @param string $name Filename to be located.
- * @return array
- */
- public function locateConfigFile(array $folders, $name)
- {
- $filename = "{$name}.yaml";
-
- $list = [];
- foreach ($folders as $folder) {
- $path = trim(Folder::getRelativePath($folder), '/');
-
- if (is_file("{$folder}/{$filename}")) {
- $modified = filemtime("{$folder}/{$filename}");
- } else {
- $modified = 0;
- }
- $list[$path] = [$name => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
- }
-
- return $list;
- }
-
- /**
- * Detects all plugins with a configuration file and returns them with last modification time.
- *
- * @param string $folder Location to look up from.
- * @param string $lookup Filename to be located.
- * @return array
- * @internal
- */
- protected function detectInFolder($folder, $lookup = null)
- {
- $path = trim(Folder::getRelativePath($folder), '/');
-
- $list = [];
-
- if (is_dir($folder)) {
- $iterator = new \FilesystemIterator($folder);
-
- /** @var \DirectoryIterator $directory */
- foreach ($iterator as $directory) {
- if (!$directory->isDir()) {
- continue;
- }
-
- $name = $directory->getBasename();
- $find = ($lookup ?: $name) . '.yaml';
- $filename = "{$path}/{$name}/$find";
-
- if (file_exists($filename)) {
- $list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
- }
- }
- }
-
- return [$path => $list];
- }
-
- protected function detectLanguagesInFolder($folder, $lookup = null)
- {
- $path = trim(Folder::getRelativePath($folder), '/');
-
- $list = [];
-
- if (is_dir($folder)) {
- $iterator = new \FilesystemIterator($folder);
-
- /** @var \DirectoryIterator $directory */
- foreach ($iterator as $directory) {
- if (!$directory->isDir()) {
- continue;
- }
-
- $name = $directory->getBasename();
- $find = ($lookup ?: $name) . '.yaml';
- $filename = "{$path}/{$name}/$find";
-
- if (file_exists($filename)) {
- $list[$name] = ['file' => $filename, 'modified' => filemtime($filename)];
- }
- }
- }
-
- return [$path => $list];
- }
-
- /**
- * Detects all plugins with a configuration file and returns them with last modification time.
- *
- * @param string $folder Location to look up from.
- * @return array
- * @internal
- */
- protected function detectRecursive($folder)
- {
- $path = trim(Folder::getRelativePath($folder), '/');
-
- if (is_dir($folder)) {
- // Find all system and user configuration files.
- $options = [
- 'compare' => 'Filename',
- 'pattern' => '|\.yaml$|',
- 'filters' => [
- 'key' => '|\.yaml$|',
- 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
- return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
- }
- ],
- 'key' => 'SubPathname'
- ];
-
- $list = Folder::all($folder, $options);
- } else {
- $list = [];
- }
-
- return [$path => $list];
- }
-}
diff --git a/system/src/Grav/Common/Config/Languages.php b/system/src/Grav/Common/Config/Languages.php
index c0141292e..4d92d87e9 100644
--- a/system/src/Grav/Common/Config/Languages.php
+++ b/system/src/Grav/Common/Config/Languages.php
@@ -11,6 +11,23 @@ use Grav\Common\Data\Data;
*/
class Languages extends Data
{
+ public function checksum($checksum = null)
+ {
+ if ($checksum !== null) {
+ $this->checksum = $checksum;
+ }
+
+ return $this->checksum;
+ }
+
+ public function modified($modified = null)
+ {
+ if ($modified !== null) {
+ $this->modified = $modified;
+ }
+
+ return $this->modified;
+ }
public function reformat()
{
diff --git a/system/src/Grav/Common/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php
new file mode 100644
index 000000000..ef89df015
--- /dev/null
+++ b/system/src/Grav/Common/Config/Setup.php
@@ -0,0 +1,232 @@
+ [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['system'],
+ ]
+ ],
+ 'user' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user'],
+ ]
+ ],
+ 'environment' => [
+ 'type' => 'ReadOnlyStream'
+ // If not defined, environment will be set up in the constructor.
+ ],
+ 'asset' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['assets'],
+ ]
+ ],
+ 'blueprints' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['environment://blueprints', 'user://blueprints', 'system/blueprints'],
+ ]
+ ],
+ 'config' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['environment://config', 'user://config', 'system/config'],
+ ]
+ ],
+ 'plugins' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://plugins'],
+ ]
+ ],
+ 'plugin' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://plugins'],
+ ]
+ ],
+ 'themes' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://themes'],
+ ]
+ ],
+ 'languages' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['environment://languages', 'user://languages', 'system/languages'],
+ ]
+ ],
+ 'cache' => [
+ 'type' => 'Stream',
+ 'prefixes' => [
+ '' => ['cache'],
+ 'images' => ['images']
+ ]
+ ],
+ 'log' => [
+ 'type' => 'Stream',
+ 'prefixes' => [
+ '' => ['logs']
+ ]
+ ],
+ 'backup' => [
+ 'type' => 'Stream',
+ 'prefixes' => [
+ '' => ['backup']
+ ]
+ ],
+ 'image' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://images', 'system://images']
+ ]
+ ],
+ 'page' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://pages']
+ ]
+ ],
+ 'account' => [
+ 'type' => 'ReadOnlyStream',
+ 'prefixes' => [
+ '' => ['user://accounts']
+ ]
+ ],
+ ];
+
+ public function __construct($environment = 'localhost')
+ {
+ // Pre-load setup.php which contains our initial configuration.
+ // Configuration may contain dynamic parts, which is why we need to always load it.
+ $file = GRAV_ROOT . '/setup.php';
+ $setup = is_file($file) ? (array) include $file : [];
+
+ // Add default streams defined in beginning of the class.
+ if (!isset($setup['streams']['schemes'])) {
+ $setup['streams']['schemes'] = [];
+ }
+ $setup['streams']['schemes'] += $this->streams;
+
+ // Initialize class.
+ parent::__construct($setup);
+
+ // Set up environment.
+ $this->def('environment', $environment);
+ $this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->environment}"]]);
+ }
+
+ /**
+ * @return $this
+ */
+ public function init()
+ {
+ $locator = new UniformResourceLocator(GRAV_ROOT);
+ $files = [];
+
+ $guard = 5;
+ do {
+ $check = $files;
+ $this->initializeLocator($locator);
+ $files = $locator->findResources('config://streams.yaml');
+
+ if ($check === $files) {
+ break;
+ }
+
+ // Update streams.
+ foreach ($files as $path) {
+ $file = CompiledYamlFile::instance($path);
+ $content = $file->content();
+ if (!empty($content['schemes'])) {
+ $this->items['streams']['schemes'] = $content['schemes'] + $this->items['streams']['schemes'];
+ }
+ }
+ } while (--$guard);
+
+ if (!$guard) {
+ throw new \RuntimeException('Setup: Configuration reload loop detected!');
+ }
+
+ // Make sure we have valid setup.
+ $this->check();
+
+ return $this;
+ }
+
+ /**
+ * Initialize resource locator by using the configuration.
+ *
+ * @param UniformResourceLocator $locator
+ */
+ public function initializeLocator(UniformResourceLocator $locator)
+ {
+ $locator->reset();
+
+ $schemes = (array) $this->get('streams.schemes', []);
+
+ foreach ($schemes as $scheme => $config) {
+ if (isset($config['paths'])) {
+ $locator->addPath($scheme, '', $config['paths']);
+ }
+ if (isset($config['prefixes'])) {
+ foreach ($config['prefixes'] as $prefix => $paths) {
+ $locator->addPath($scheme, $prefix, $paths);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get available streams and their types from the configuration.
+ *
+ * @return array
+ */
+ public function getStreams()
+ {
+ $schemes = [];
+ foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
+ $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
+ if ($type[0] != '\\') {
+ $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
+ }
+
+ $schemes[$scheme] = $type;
+ }
+
+ return $schemes;
+ }
+
+ /**
+ * @throws \InvalidArgumentException
+ */
+ protected function check()
+ {
+ $streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
+ if (!is_array($streams)) {
+ throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
+ }
+ $diff = array_keys(array_diff_key($this->streams, $streams));
+ if ($diff) {
+ throw new \InvalidArgumentException(
+ sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
+ );
+ }
+ }
+}
diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php
index 9bfc32303..6f0c71e50 100644
--- a/system/src/Grav/Common/Data/Blueprint.php
+++ b/system/src/Grav/Common/Data/Blueprint.php
@@ -3,6 +3,8 @@ namespace Grav\Common\Data;
use Grav\Common\GravTrait;
use RocketTheme\Toolbox\ArrayTraits\Export;
+use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
+use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
/**
* Blueprint handles the inside logic of blueprints.
@@ -10,9 +12,9 @@ use RocketTheme\Toolbox\ArrayTraits\Export;
* @author RocketTheme
* @license MIT
*/
-class Blueprint
+class Blueprint implements \ArrayAccess, ExportInterface
{
- use Export, DataMutatorTrait, GravTrait;
+ use Export, NestedArrayAccessWithGetters, GravTrait;
public $name;
diff --git a/system/src/Grav/Common/Data/Data.php b/system/src/Grav/Common/Data/Data.php
index 6a35fbc9e..fe70efb1b 100644
--- a/system/src/Grav/Common/Data/Data.php
+++ b/system/src/Grav/Common/Data/Data.php
@@ -1,9 +1,11 @@
items = $items;
-
$this->blueprints = $blueprints;
}
@@ -57,126 +58,150 @@ class Data implements DataInterface
}
/**
- * Set default value by using dot notation for nested arrays/objects.
- *
- * @example $data->def('this.is.my.nested.variable', 'default');
- *
- * @param string $name Dot separated path to the requested value.
- * @param mixed $default Default value (or null).
- * @param string $separator Separator, defaults to '.'
- */
- public function def($name, $default = null, $separator = '.')
- {
- $this->set($name, $this->get($name, $default, $separator), $separator);
- }
-
- /**
- * Join two values together by using blueprints if available.
+ * Join nested values together by using blueprints.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
+ * @return $this
+ * @throws \RuntimeException
*/
public function join($name, $value, $separator = '.')
{
$old = $this->get($name, null, $separator);
- if ($old === null) {
- // Variable does not exist yet: just use the incoming value.
- } elseif ($this->blueprints) {
- // Blueprints: join values by using blueprints.
- $value = $this->blueprints->mergeData($old, $value, $name, $separator);
- } else {
- // No blueprints: replace existing top level variables with the new ones.
- $value = array_merge($old, $value);
+ if ($old !== null) {
+ if (!is_array($old)) {
+ throw new \RuntimeException('Value ' . $old);
+ }
+ if (is_object($value)) {
+ $value = (array) $value;
+ } elseif (!is_array($value)) {
+ throw new \RuntimeException('Value ' . $value);
+ }
+ $value = $this->blueprints()->mergeData($old, $value, $name, $separator);
}
$this->set($name, $value, $separator);
+
+ return $this;
}
/**
- * Join two values together by using blueprints if available.
+ * Get nested structure containing default values defined in the blueprints.
+ *
+ * Fields without default value are ignored in the list.
+
+ * @return array
+ */
+ public function getDefaults()
+ {
+ return $this->blueprints()->getDefaults();
+ }
+
+ /**
+ * Set default values by using blueprints.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
+ * @return $this
*/
public function joinDefaults($name, $value, $separator = '.')
{
+ if (is_object($value)) {
+ $value = (array) $value;
+ }
$old = $this->get($name, null, $separator);
- if ($old === null) {
- // Variable does not exist yet: just use the incoming value.
- } elseif ($this->blueprints) {
- // Blueprints: join values by using blueprints.
- $value = $this->blueprints->mergeData($value, $old, $name, $separator);
- } else {
- // No blueprints: replace existing top level variables with the new ones.
- $value = array_merge($value, $old);
+ if ($old !== null) {
+ $value = $this->blueprints()->mergeData($value, $old, $name, $separator);
}
$this->set($name, $value, $separator);
+
+ return $this;
+ }
+
+ /**
+ * Get value from the configuration and join it with given data.
+ *
+ * @param string $name Dot separated path to the requested value.
+ * @param array $value Value to be joined.
+ * @param string $separator Separator, defaults to '.'
+ * @return array
+ * @throws \RuntimeException
+ */
+ public function getJoined($name, $value, $separator = '.')
+ {
+ if (is_object($value)) {
+ $value = (array) $value;
+ } elseif (!is_array($value)) {
+ throw new \RuntimeException('Value ' . $value);
+ }
+
+ $old = $this->get($name, null, $separator);
+
+ if ($old === null) {
+ // No value set; no need to join data.
+ return $value;
+ }
+
+ if (!is_array($old)) {
+ throw new \RuntimeException('Value ' . $old);
+ }
+
+ // Return joined data.
+ return $this->blueprints()->mergeData($old, $value, $name, $separator);
}
/**
- * Merge two sets of data together.
+ * Merge two configurations together.
*
* @param array $data
- * @return void
+ * @return $this
*/
public function merge(array $data)
{
- if ($this->blueprints) {
- $this->items = $this->blueprints->mergeData($this->items, $data);
- } else {
- $this->items = array_merge($this->items, $data);
- }
+ $this->items = $this->blueprints()->mergeData($this->items, $data);
+
+ return $this;
}
/**
- * Add default data to the set.
+ * Set default values to the configuration if variables were not set.
*
* @param array $data
- * @return void
+ * @return $this
*/
public function setDefaults(array $data)
{
- if ($this->blueprints) {
- $this->items = $this->blueprints->mergeData($data, $this->items);
- } else {
- $this->items = array_merge($data, $this->items);
- }
- }
+ $this->items = $this->blueprints()->mergeData($data, $this->items);
- /**
- * Return blueprints.
- *
- * @return Blueprint
- */
- public function blueprints()
- {
- return $this->blueprints;
+ return $this;
}
/**
* Validate by blueprints.
*
+ * @return $this
* @throws \Exception
*/
public function validate()
{
- if ($this->blueprints) {
- $this->blueprints->validate($this->items);
- }
+ $this->blueprints()->validate($this->items);
+
+ return $this;
}
/**
+ * @return $this
* Filter all items by using blueprints.
*/
public function filter()
{
- if ($this->blueprints) {
- $this->items = $this->blueprints->filter($this->items);
- }
+ $this->items = $this->blueprints()->filter($this->items);
+
+ return $this;
}
/**
@@ -186,7 +211,24 @@ class Data implements DataInterface
*/
public function extra()
{
- return $this->blueprints ? $this->blueprints->extra($this->items) : array();
+ return $this->blueprints()->extra($this->items);
+ }
+
+ /**
+ * Return blueprints.
+ *
+ * @return Blueprints
+ */
+ public function blueprints()
+ {
+ if (!$this->blueprints){
+ $this->blueprints = new Blueprints;
+ } elseif (is_callable($this->blueprints)) {
+ // Lazy load blueprints.
+ $blueprints = $this->blueprints;
+ $this->blueprints = $blueprints();
+ }
+ return $this->blueprints;
}
/**
diff --git a/system/src/Grav/Common/Data/DataMutatorTrait.php b/system/src/Grav/Common/Data/DataMutatorTrait.php
deleted file mode 100644
index 707b5078a..000000000
--- a/system/src/Grav/Common/Data/DataMutatorTrait.php
+++ /dev/null
@@ -1,68 +0,0 @@
-get('this.is.my.nested.variable');
- *
- * @param string $name Dot separated path to the requested value.
- * @param mixed $default Default value (or null).
- * @param string $separator Separator, defaults to '.'
- * @return mixed Value.
- */
- public function get($name, $default = null, $separator = '.')
- {
- $path = explode($separator, $name);
- $current = $this->items;
- foreach ($path as $field) {
- if (is_object($current) && isset($current->{$field})) {
- $current = $current->{$field};
- } elseif (is_array($current) && isset($current[$field])) {
- $current = $current[$field];
- } else {
- return $default;
- }
- }
-
- return $current;
- }
-
- /**
- * Set value by using dot notation for nested arrays/objects.
- *
- * @example $value = $data->set('this.is.my.nested.variable', true);
- *
- * @param string $name Dot separated path to the requested value.
- * @param mixed $value New value.
- * @param string $separator Separator, defaults to '.'
- */
- public function set($name, $value, $separator = '.')
- {
- $path = explode($separator, $name);
- $current = &$this->items;
- foreach ($path as $field) {
- if (is_object($current)) {
- // Handle objects.
- if (!isset($current->{$field})) {
- $current->{$field} = array();
- }
- $current = &$current->{$field};
- } else {
- // Handle arrays and scalars.
- if (!is_array($current)) {
- $current = array($field => array());
- } elseif (!isset($current[$field])) {
- $current[$field] = array();
- }
- $current = &$current[$field];
- }
- }
-
- $current = $value;
- }
-
-}
diff --git a/system/src/Grav/Common/File/CompiledFile.php b/system/src/Grav/Common/File/CompiledFile.php
index 7120c9c30..6b6b76989 100644
--- a/system/src/Grav/Common/File/CompiledFile.php
+++ b/system/src/Grav/Common/File/CompiledFile.php
@@ -47,7 +47,11 @@ trait CompiledFile
|| $cache['filename'] != $this->filename
) {
// Attempt to lock the file for writing.
- $file->lock(false);
+ try {
+ $file->lock(false);
+ } catch (\Exception $e) {
+ // Another process has locked the file; we will check this in a bit.
+ }
// Decode RAW file into compiled array.
$data = (array) $this->decode($this->raw());
@@ -64,6 +68,7 @@ trait CompiledFile
$file->unlock();
}
}
+ $file->free();
$this->content = $cache['data'];
}
diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php
index af1b93352..62d610b65 100644
--- a/system/src/Grav/Common/Filesystem/Folder.php
+++ b/system/src/Grav/Common/Filesystem/Folder.php
@@ -19,12 +19,12 @@ abstract class Folder
{
$last_modified = 0;
- $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
- $filterItr = new RecursiveFolderFilterIterator($dirItr);
- $itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
+ $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
+ $filter = new RecursiveFolderFilterIterator($directory);
+ $iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
/** @var \RecursiveDirectoryIterator $file */
- foreach ($itr as $dir) {
+ foreach ($iterator as $dir) {
$dir_modified = $dir->getMTime();
if ($dir_modified > $last_modified) {
$last_modified = $dir_modified;
@@ -46,12 +46,12 @@ abstract class Folder
{
$last_modified = 0;
- $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
- $itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST);
- $itr = new \RegexIterator($itrItr, '/^.+\.'.$extensions.'$/i');
+ $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
+ $recursive = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
+ $iterator = new \RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
/** @var \RecursiveDirectoryIterator $file */
- foreach ($itr as $filepath => $file) {
+ foreach ($iterator as $filepath => $file) {
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
@@ -64,8 +64,8 @@ abstract class Folder
/**
* Get relative path between target and base path. If path isn't relative, return full path.
*
- * @param string $path
- * @param mixed|string $base
+ * @param string $path
+ * @param string $base
* @return string
*/
public static function getRelativePath($path, $base = GRAV_ROOT)
@@ -81,6 +81,43 @@ abstract class Folder
return $path;
}
+ /**
+ * Get relative path between target and base path. If path isn't relative, return full path.
+ *
+ * @param string $path
+ * @param string $base
+ * @return string
+ */
+ public static function getRelativePathDotDot($path, $base)
+ {
+ $base = preg_replace('![\\\/]+!', '/', $base);
+ $path = preg_replace('![\\\/]+!', '/', $path);
+
+ if ($path === $base) {
+ return '';
+ }
+
+ $baseParts = explode('/', isset($base[0]) && '/' === $base[0] ? substr($base, 1) : $base);
+ $pathParts = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path);
+
+ array_pop($baseParts);
+ $lastPart = array_pop($pathParts);
+ foreach ($baseParts as $i => $directory) {
+ if (isset($pathParts[$i]) && $pathParts[$i] === $directory) {
+ unset($baseParts[$i], $pathParts[$i]);
+ } else {
+ break;
+ }
+ }
+ $pathParts[] = $lastPart;
+ $path = str_repeat('../', count($baseParts)) . implode('/', $pathParts);
+
+ return '' === $path
+ || '/' === $path[0]
+ || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
+ ? "./$path" : $path;
+ }
+
/**
* Shift first directory out of the path.
*
@@ -96,8 +133,6 @@ abstract class Folder
return $result ?: null;
}
-
-
/**
* Return recursive list of all files and directories under given path.
*
@@ -116,13 +151,17 @@ abstract class Folder
$pattern = isset($params['pattern']) ? $params['pattern'] : null;
$filters = isset($params['filters']) ? $params['filters'] : null;
$recursive = isset($params['recursive']) ? $params['recursive'] : true;
+ $levels = isset($params['levels']) ? $params['levels'] : -1;
$key = isset($params['key']) ? 'get' . $params['key'] : null;
$value = isset($params['value']) ? 'get' . $params['value'] : ($recursive ? 'getSubPathname' : 'getFilename');
+ $folders = isset($params['folders']) ? $params['folders'] : true;
+ $files = isset($params['files']) ? $params['files'] : true;
if ($recursive) {
$directory = new \RecursiveDirectoryIterator($path,
\RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
+ $iterator->setMaxDepth(max($levels, -1));
} else {
$iterator = new \FilesystemIterator($path);
}
@@ -131,6 +170,16 @@ abstract class Folder
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
+ // Ignore hidden files.
+ if ($file->getFilename()[0] == '.') {
+ continue;
+ }
+ if (!$folders && $file->isDir()) {
+ continue;
+ }
+ if (!$files && $file->isFile()) {
+ continue;
+ }
if ($compare && $pattern && !preg_match($pattern, $file->{$compare}())) {
continue;
}
@@ -138,7 +187,8 @@ abstract class Folder
$filePath = $file->{$value}();
if ($filters) {
if (isset($filters['key'])) {
- $fileKey = preg_replace($filters['key'], '', $fileKey);
+ $pre = !empty($filters['pre-key']) ? $filters['pre-key'] : '';
+ $fileKey = $pre . preg_replace($filters['key'], '', $fileKey);
}
if (isset($filters['value'])) {
$filter = $filters['value'];
@@ -146,12 +196,12 @@ abstract class Folder
$filePath = call_user_func($filter, $file);
} else {
$filePath = preg_replace($filter, '', $filePath);
+ }
}
}
- }
if ($fileKey !== null) {
- $results[$fileKey] = $filePath;
+ $results[$fileKey] = $filePath;
} else {
$results[] = $filePath;
}
@@ -163,11 +213,12 @@ abstract class Folder
/**
* Recursively copy directory in filesystem.
*
- * @param string $source
- * @param string $target
+ * @param string $source
+ * @param string $target
+ * @param string $ignore Ignore files matching pattern (regular expression).
* @throws \RuntimeException
*/
- public static function copy($source, $target)
+ public static function copy($source, $target, $ignore = null)
{
$source = rtrim($source, '\\/');
$target = rtrim($target, '\\/');
@@ -177,19 +228,24 @@ abstract class Folder
}
// Make sure that path to the target exists before copying.
- self::mkdir($target);
+ self::create($target);
$success = true;
// Go through all sub-directories and copy everything.
$files = self::all($source);
foreach ($files as $file) {
+ if ($ignore && preg_match($ignore, $file)) {
+ continue;
+ }
$src = $source .'/'. $file;
$dst = $target .'/'. $file;
if (is_dir($src)) {
- // Create current directory.
- $success &= @mkdir($dst);
+ // Create current directory (if it doesn't exist).
+ if (!is_dir($dst)) {
+ $success &= @mkdir($dst, 0777, true);
+ }
} else {
// Or copy current file.
$success &= @copy($src, $dst);
@@ -208,8 +264,8 @@ abstract class Folder
/**
* Move directory in filesystem.
*
- * @param string $source
- * @param string $target
+ * @param string $source
+ * @param string $target
* @throws \RuntimeException
*/
public static function move($source, $target)
@@ -219,7 +275,7 @@ abstract class Folder
}
// Make sure that path to the target exists before moving.
- self::mkdir(dirname($target));
+ self::create(dirname($target));
// Just rename the directory.
$success = @rename($source, $target);
@@ -238,16 +294,16 @@ abstract class Folder
* Recursively delete directory from filesystem.
*
* @param string $target
- * @throws \RuntimeException
+ * @param bool $include_target
* @return bool
*/
- public static function delete($target)
+ public static function delete($target, $include_target = true)
{
if (!is_dir($target)) {
- return;
+ return false;
}
- $success = self::doDelete($target);
+ $success = self::doDelete($target, $include_target);
if (!$success) {
$error = error_get_last();
@@ -255,16 +311,31 @@ abstract class Folder
}
// Make sure that the change will be detected when caching.
- @touch(dirname($target));
+ if ($include_target) {
+ @touch(dirname($target));
+ } else {
+ @touch($target);
+ }
+
return $success;
}
/**
- * @param string $folder
+ * @param string $folder
* @throws \RuntimeException
* @internal
*/
public static function mkdir($folder)
+ {
+ self::create($folder);
+ }
+
+ /**
+ * @param string $folder
+ * @throws \RuntimeException
+ * @internal
+ */
+ public static function create($folder)
{
if (is_dir($folder)) {
return;
@@ -320,10 +391,11 @@ abstract class Folder
/**
* @param string $folder
+ * @param bool $include_target
* @return bool
* @internal
*/
- protected static function doDelete($folder)
+ protected static function doDelete($folder, $include_target = true)
{
// Special case for symbolic links.
if (is_link($folder)) {
@@ -338,16 +410,16 @@ abstract class Folder
/** @var \DirectoryIterator $fileinfo */
foreach ($files as $fileinfo) {
if ($fileinfo->isDir()) {
- if (false === rmdir($fileinfo->getRealPath())) {
+ if (false === @rmdir($fileinfo->getRealPath())) {
return false;
}
} else {
- if (false === unlink($fileinfo->getRealPath())) {
+ if (false === @unlink($fileinfo->getRealPath())) {
return false;
}
}
}
- return rmdir($folder);
+ return $include_target ? @rmdir($folder) : true;
}
}
diff --git a/system/src/Grav/Common/Service/ConfigServiceProvider.php b/system/src/Grav/Common/Service/ConfigServiceProvider.php
index 28a6ac4d9..febdcd6a7 100644
--- a/system/src/Grav/Common/Service/ConfigServiceProvider.php
+++ b/system/src/Grav/Common/Service/ConfigServiceProvider.php
@@ -1,10 +1,15 @@
setup = is_file($file) ? (array) include $file : [];
- $this->environment = isset($this->setup['environment']) ? $this->setup['environment'] : null;
-
- $container['blueprints'] = function ($c) use ($self) {
- return $self->loadMasterBlueprints($c);
+ $container['setup'] = function ($c) {
+ return static::setup($c)->init();
};
- $container['config'] = function ($c) use ($self) {
- return $self->loadMasterConfig($c);
+ $container['blueprints'] = function ($c) {
+ return static::blueprints($c);
+ };
+
+ $container['config'] = function ($c) {
+ return static::load($c);
+ };
+
+ $container['languages'] = function ($c) {
+ return static::languages($c);
};
}
- public function loadMasterConfig(Container $container)
+ public static function setup(Container $container)
{
- $environment = $this->getEnvironment($container);
-
- $config = new Config($this->setup, $container, $environment);
-
- return $config;
+ return new Setup($container['uri']->environment());
}
- public function loadMasterBlueprints(Container $container)
+ public static function blueprints(Container $container)
{
- $environment = $this->getEnvironment($container);
- $file = CACHE_DIR . 'compiled/blueprints/master-'.$environment.'.php';
- $data = is_file($file) ? (array) include $file : [];
+ /** Setup $setup */
+ $setup = $container['setup'];
- return new Blueprints($data, $container);
+ /** @var UniformResourceLocator $locator */
+ $locator = $container['locator'];
+
+ $cache = $locator->findResource('cache://compiled/blueprints', true, true);
+
+ $files = [];
+ $paths = $locator->findResources('blueprints://config');
+ $files += (new ConfigFileFinder)->locateFiles($paths);
+ $paths = $locator->findResources('plugins://');
+ $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'blueprints');
+
+ $blueprints = new CompiledBlueprints($cache, $files, GRAV_ROOT);
+
+ return $blueprints->name("master-{$setup->environment}")->load();
}
- public function getEnvironment(Container $container)
+ public static function load(Container $container)
{
- if (!isset($this->environment)) {
- $this->environment = $container['uri']->environment();
+ /** Setup $setup */
+ $setup = $container['setup'];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $container['locator'];
+
+ $cache = $locator->findResource('cache://compiled/config', true, true);
+
+ $files = [];
+ $paths = $locator->findResources('config://');
+ $files += (new ConfigFileFinder)->locateFiles($paths);
+ $paths = $locator->findResources('plugins://');
+ $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths);
+
+ $config = new CompiledConfig($cache, $files, GRAV_ROOT);
+ $config->setBlueprints(function() use ($container) {
+ return $container['blueprints'];
+ });
+
+ return $config->name("master-{$setup->environment}")->load();
+ }
+
+ public static function languages(Container $container)
+ {
+ /** Setup $setup */
+ $setup = $container['setup'];
+
+ /** @var Config $config */
+ $config = $container['config'];
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $container['locator'];
+
+ $cache = $locator->findResource('cache://compiled/languages', true, true);
+ $files = [];
+
+ // Process languages only if enabled in configuration.
+ if ($config->get('system.languages.translations', true)) {
+ $paths = $locator->findResources('languages://');
+ $files += (new ConfigFileFinder)->locateFiles($paths);
+ $paths = $locator->findResources('plugins://');
+ $files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'languages');
}
- return $this->environment;
+ $languages = new CompiledLanguages($cache, $files, GRAV_ROOT);
+
+ return $languages->name("master-{$setup->environment}")->load();
}
}
diff --git a/system/src/Grav/Common/Service/StreamsServiceProvider.php b/system/src/Grav/Common/Service/StreamsServiceProvider.php
index d8f7dffbb..ec7f9cd85 100644
--- a/system/src/Grav/Common/Service/StreamsServiceProvider.php
+++ b/system/src/Grav/Common/Service/StreamsServiceProvider.php
@@ -1,7 +1,7 @@
initializeLocator($locator);
+ /** @var Setup $setup */
+ $setup = $c['setup'];
+ $setup->initializeLocator($locator);
return $locator;
};
$container['streams'] = function($c) {
- /** @var Config $config */
- $config = $c['config'];
+ /** @var Setup $setup */
+ $setup = $c['setup'];
/** @var UniformResourceLocator $locator */
$locator = $c['locator'];
@@ -34,7 +34,7 @@ class StreamsServiceProvider implements ServiceProviderInterface
Stream::setLocator($locator);
ReadOnlyStream::setLocator($locator);
- return new StreamBuilder($config->getStreams($c));
+ return new StreamBuilder($setup->getStreams($c));
};
}
}
diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php
index 101027c4a..9caad3741 100644
--- a/system/src/Grav/Common/User/User.php
+++ b/system/src/Grav/Common/User/User.php
@@ -83,7 +83,10 @@ class User extends Data
// Plain-text passwords do not match, we know we should fail but execute
// verify to protect us from timing attacks and return false regardless of
// the result
- Authentication::verify($password, self::getGrav()['config']->get('system.security.default_hash'));
+ Authentication::verify(
+ $password,
+ self::getGrav()['config']->get('system.security.default_hash', '$2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK')
+ );
return false;
} else {
// Plain-text does match, we can update the hash and proceed
diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php
index b3bdc7af4..db1162d51 100644
--- a/system/src/Grav/Common/Utils.php
+++ b/system/src/Grav/Common/Utils.php
@@ -15,6 +15,8 @@ abstract class Utils
{
use GravTrait;
+ protected static $nonces = [];
+
/**
* @param string $haystack
* @param string $needle
@@ -454,11 +456,10 @@ abstract class Utils
if (isset(self::getGrav()['user'])) {
$user = self::getGrav()['user'];
$username = $user->username;
+ if (isset($_SERVER['REMOTE_ADDR'])) {
+ $username .= $_SERVER['REMOTE_ADDR'];
+ }
} else {
- $username = false;
- }
-
- if (!$username) {
$username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
}
@@ -493,7 +494,7 @@ abstract class Utils
*
* @return string hashed value of $data, cut to 10 characters
*/
- private static function hash($data)
+ public static function hash($data)
{
$hash = password_hash($data, PASSWORD_DEFAULT);
return $hash;
@@ -510,9 +511,15 @@ abstract class Utils
*/
public static function getNonce($action, $plusOneTick = false)
{
+ // Don't regenerate this again if not needed
+ if (isset(static::$nonces[$action])) {
+ return static::$nonces[$action];
+ }
$nonce = self::hash(self::generateNonceString($action, $plusOneTick));
- $nonce = str_replace('/', 'SLASH', $nonce);
- return $nonce;
+
+ static::$nonces[$action] = str_replace('/', 'SLASH', $nonce);
+
+ return static::$nonces[$action];
}
/**