From bc4a09f80dac10646a3278690e33b66ca5a98882 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 13 Nov 2015 14:02:06 +0200 Subject: [PATCH 01/19] Fix undefined variable in Config class --- CHANGELOG.md | 6 ++ composer.json | 8 ++- composer.lock | 43 ++++++------ system/src/Grav/Common/Data/Blueprint.php | 3 +- system/src/Grav/Common/Data/Data.php | 4 +- .../src/Grav/Common/Data/DataMutatorTrait.php | 68 ------------------- 6 files changed, 41 insertions(+), 91 deletions(-) delete mode 100644 system/src/Grav/Common/Data/DataMutatorTrait.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4300bc74b..5fd5b48fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v1.0.0-refactor +## XX/XX/2015 + +1. [](#new) + * Refactor Data classes to use NestedArrayAccess instead of DataMutatorTrait + # v1.0.0-rc.4 ## 10/29/2015 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 d3a9d25b8..9328557aa 100644 --- a/composer.lock +++ b/composer.lock @@ -1,23 +1,24 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "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", + "hash": "3633e92a6e340a26229689d9b74031c2", + "content-hash": "9c314f997d29d7a78d3cf14f5976e6df", "packages": [ { "name": "doctrine/cache", - "version": "v1.5.0", + "version": "v1.5.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "eb8a73619af4f1c8711e2ce482f5de3643258a1f" + "reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/eb8a73619af4f1c8711e2ce482f5de3643258a1f", - "reference": "eb8a73619af4f1c8711e2ce482f5de3643258a1f", + "url": "https://api.github.com/repos/doctrine/cache/zipball/2b9cec5a5e722010cbebc91713d4c11eaa064d5e", + "reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e", "shasum": "" }, "require": { @@ -74,7 +75,7 @@ "cache", "caching" ], - "time": "2015-10-28 11:27:45" + "time": "2015-11-02 18:35:48" }, { "name": "donatj/phpuseragentparser", @@ -665,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": "0a7006b00880bd6873c49e2ed23939c785310417" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/ff677d8f66d1addd3590d0cb85bcbaff4174d9c9", - "reference": "ff677d8f66d1addd3590d0cb85bcbaff4174d9c9", + "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/0a7006b00880bd6873c49e2ed23939c785310417", + "reference": "0a7006b00880bd6873c49e2ed23939c785310417", "shasum": "" }, "require": { @@ -700,7 +701,6 @@ "RocketTheme\\Toolbox\\StreamWrapper\\": "StreamWrapper/src" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -710,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-13 11:39:58" }, { "name": "symfony/console", @@ -929,16 +933,16 @@ }, { "name": "twig/twig", - "version": "v1.23.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "5868cd822fd6cf626d5f805439575f9c323cee2a" + "reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/5868cd822fd6cf626d5f805439575f9c323cee2a", - "reference": "5868cd822fd6cf626d5f805439575f9c323cee2a", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/d9b6333ae8dd2c8e3fd256e127548def0bc614c6", + "reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6", "shasum": "" }, "require": { @@ -986,14 +990,15 @@ "keywords": [ "templating" ], - "time": "2015-10-29 23:29:01" + "time": "2015-11-05 12:49:06" } ], "packages-dev": [], "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/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php index 97ed5858a..defb978b3 100644 --- a/system/src/Grav/Common/Data/Blueprint.php +++ b/system/src/Grav/Common/Data/Blueprint.php @@ -3,6 +3,7 @@ namespace Grav\Common\Data; use Grav\Common\GravTrait; use RocketTheme\Toolbox\ArrayTraits\Export; +use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess; /** * Blueprint handles the inside logic of blueprints. @@ -12,7 +13,7 @@ use RocketTheme\Toolbox\ArrayTraits\Export; */ class Blueprint { - use Export, DataMutatorTrait, GravTrait; + use Export, NestedArrayAccess, GravTrait; public $name; diff --git a/system/src/Grav/Common/Data/Data.php b/system/src/Grav/Common/Data/Data.php index 6a35fbc9e..e09611478 100644 --- a/system/src/Grav/Common/Data/Data.php +++ b/system/src/Grav/Common/Data/Data.php @@ -1,9 +1,9 @@ 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; - } - -} From 997c772b7c469bd990b09335028f61e02550671a Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 13 Nov 2015 14:09:03 +0200 Subject: [PATCH 02/19] Make Data classes to implement proper interfaces --- system/src/Grav/Common/Data/Blueprint.php | 7 ++++--- system/src/Grav/Common/Data/Data.php | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php index defb978b3..6ebe8ae19 100644 --- a/system/src/Grav/Common/Data/Blueprint.php +++ b/system/src/Grav/Common/Data/Blueprint.php @@ -3,7 +3,8 @@ namespace Grav\Common\Data; use Grav\Common\GravTrait; use RocketTheme\Toolbox\ArrayTraits\Export; -use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess; +use RocketTheme\Toolbox\ArrayTraits\ExportInterface; +use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters; /** * Blueprint handles the inside logic of blueprints. @@ -11,9 +12,9 @@ use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess; * @author RocketTheme * @license MIT */ -class Blueprint +class Blueprint implements \ArrayAccess, ExportInterface { - use Export, NestedArrayAccess, 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 e09611478..4e38f1eaa 100644 --- a/system/src/Grav/Common/Data/Data.php +++ b/system/src/Grav/Common/Data/Data.php @@ -3,6 +3,7 @@ namespace Grav\Common\Data; use RocketTheme\Toolbox\ArrayTraits\Countable; use RocketTheme\Toolbox\ArrayTraits\Export; +use RocketTheme\Toolbox\ArrayTraits\ExportInterface; use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters; use RocketTheme\Toolbox\File\File; use RocketTheme\Toolbox\File\FileInterface; @@ -13,7 +14,7 @@ use RocketTheme\Toolbox\File\FileInterface; * @author RocketTheme * @license MIT */ -class Data implements DataInterface +class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface { use NestedArrayAccessWithGetters, Countable, Export; From ac3396e6c46f9bcd8d5cbb45370b5daadc84aadc Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Wed, 18 Nov 2015 14:43:32 +0200 Subject: [PATCH 03/19] Data objects: Allow function call chaining, lazy load blueprints --- CHANGELOG.md | 2 + system/src/Grav/Common/Data/Data.php | 171 +++++++++++++++++---------- 2 files changed, 108 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fd5b48fa..8590da83a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ 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 # v1.0.0-rc.4 ## 10/29/2015 diff --git a/system/src/Grav/Common/Data/Data.php b/system/src/Grav/Common/Data/Data.php index 4e38f1eaa..fe70efb1b 100644 --- a/system/src/Grav/Common/Data/Data.php +++ b/system/src/Grav/Common/Data/Data.php @@ -5,6 +5,7 @@ use RocketTheme\Toolbox\ArrayTraits\Countable; use RocketTheme\Toolbox\ArrayTraits\Export; use RocketTheme\Toolbox\ArrayTraits\ExportInterface; use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters; +use RocketTheme\Toolbox\Blueprints\Blueprints; use RocketTheme\Toolbox\File\File; use RocketTheme\Toolbox\File\FileInterface; @@ -33,12 +34,11 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface /** * @param array $items - * @param Blueprint $blueprints + * @param Blueprint|callable $blueprints */ - public function __construct(array $items = array(), Blueprint $blueprints = null) + public function __construct(array $items = array(), $blueprints = null) { $this->items = $items; - $this->blueprints = $blueprints; } @@ -58,126 +58,150 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface } /** - * 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; } /** @@ -187,7 +211,24 @@ class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface */ 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; } /** From dd2ddfeb409baafc0bf7fed2a5c1f74b2bf8cae2 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Wed, 18 Nov 2015 15:14:27 +0200 Subject: [PATCH 04/19] Refactor Config classes --- CHANGELOG.md | 3 +- system/src/Grav/Common/Config/Blueprints.php | 207 -------- .../src/Grav/Common/Config/CompiledBase.php | 237 +++++++++ .../Grav/Common/Config/CompiledBlueprints.php | 44 ++ .../src/Grav/Common/Config/CompiledConfig.php | 88 ++++ .../Grav/Common/Config/CompiledLanguages.php | 48 ++ system/src/Grav/Common/Config/Config.php | 470 ++---------------- .../Grav/Common/Config/ConfigFileFinder.php | 258 ++++++++++ .../src/Grav/Common/Config/ConfigFinder.php | 186 ------- system/src/Grav/Common/Config/Languages.php | 9 + system/src/Grav/Common/Config/Setup.php | 232 +++++++++ .../Common/Service/ConfigServiceProvider.php | 113 +++-- .../Common/Service/StreamsServiceProvider.php | 16 +- 13 files changed, 1041 insertions(+), 870 deletions(-) delete mode 100644 system/src/Grav/Common/Config/Blueprints.php create mode 100644 system/src/Grav/Common/Config/CompiledBase.php create mode 100644 system/src/Grav/Common/Config/CompiledBlueprints.php create mode 100644 system/src/Grav/Common/Config/CompiledConfig.php create mode 100644 system/src/Grav/Common/Config/CompiledLanguages.php create mode 100644 system/src/Grav/Common/Config/ConfigFileFinder.php delete mode 100644 system/src/Grav/Common/Config/ConfigFinder.php create mode 100644 system/src/Grav/Common/Config/Setup.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 8590da83a..617449b80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ 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 + * Data objects: Lazy load blueprints only if needed + * Refactor Config classes # v1.0.0-rc.4 ## 10/29/2015 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..d20223ef1 --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledBase.php @@ -0,0 +1,237 @@ +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; + } + + /** + * @return bool + */ + public function modified() + { + return $this->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 = []); + + /** + * 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']); + } + } + + 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 = true; + } +} diff --git a/system/src/Grav/Common/Config/CompiledBlueprints.php b/system/src/Grav/Common/Config/CompiledBlueprints.php new file mode 100644 index 000000000..b4b4594e5 --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledBlueprints.php @@ -0,0 +1,44 @@ +object = new Blueprints($data); + } + + /** + * 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..70593df33 --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledConfig.php @@ -0,0 +1,88 @@ +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); + $this->object->checksum($this->checksum()); + $this->object->modified($this->modified()); + + if (method_exists($this->object, 'prepare')) { + $this->object->prepare(); + } + } + + /** + * 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..29c1c81ef --- /dev/null +++ b/system/src/Grav/Common/Config/CompiledLanguages.php @@ -0,0 +1,48 @@ +object = new Languages($data); + $this->object->checksum($this->checksum()); + } + + /** + * 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..2fd3cc66d 100644 --- a/system/src/Grav/Common/Config/Config.php +++ b/system/src/Grav/Common/Config/Config.php @@ -1,12 +1,8 @@ [ - '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(); - } public function key() { return $this->checksum(); } + 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 reload() { + throw new \Exception('TODO'); $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); + $debugger = Grav::instance()['debugger']; + $debugger->addMessage('Environment Name: ' . $this->environment); + if ($this->modified()) { + $debugger->addMessage('Configuration reloaded and cached.'); } - $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') : []; + $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 { - // 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])); - } - - return $this->checksum; - } - - protected function autoDetectEnvironmentConfig($items) - { - $environment = $this->environment; - $env_stream = 'user://'.$environment.'/config'; - - if (file_exists(USER_DIR.$environment.'/config')) { - array_unshift($items['streams']['schemes']['config']['prefixes'][''], $env_stream); - } - - return $items; - } - - protected function loadCompiledBlueprints($blueprints, $plugins, $filename = null) - { - $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); - - // 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 blueprints. - $this->blueprints = new Blueprints; - foreach ($blueprintFiles as $files) { - $this->loadBlueprintFiles($files); - } - - $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']); - } - } - - protected function loadCompiledConfig($configs, $plugins, $filename = null) - { - $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); - } + $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..1ea8b3c87 100644 --- a/system/src/Grav/Common/Config/Languages.php +++ b/system/src/Grav/Common/Config/Languages.php @@ -12,6 +12,15 @@ 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 reformat() { if (isset($this->items['plugins'])) { 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/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)); }; } } From 7228b25393c1bcff22651669ab41837d39b08f61 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Thu, 19 Nov 2015 10:49:55 +0200 Subject: [PATCH 05/19] Fix configuration reload --- system/src/Grav/Common/Config/Config.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php index 2fd3cc66d..5bb1ff7c4 100644 --- a/system/src/Grav/Common/Config/Config.php +++ b/system/src/Grav/Common/Config/Config.php @@ -3,6 +3,7 @@ namespace Grav\Common\Config; use Grav\Common\Grav; use Grav\Common\Data\Data; +use Grav\Common\Service\ConfigServiceProvider; /** * The Config class contains configuration information. @@ -39,10 +40,17 @@ class Config extends Data public function reload() { - throw new \Exception('TODO'); - $this->items = $this->setup; - $this->init(); - $this->debug(); + $grav = Grav::instance(); + + // Load new configuration. + $config = ConfigServiceProvider::load($grav); + + // Update current configuration if needed. + if ($config->modified()) { + $this->items = $config->toArray(); + $this->checksum($config->checksum()); + $this->modified($config->modified()); + } return $this; } From 5dd1554e5d3117621292aba4ef9363e689101ddf Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Thu, 19 Nov 2015 12:02:08 +0200 Subject: [PATCH 06/19] Fix modified function in config --- .../src/Grav/Common/Config/CompiledBase.php | 21 +++++++++---------- .../Grav/Common/Config/CompiledBlueprints.php | 5 +++++ .../src/Grav/Common/Config/CompiledConfig.php | 20 +++++++++++++----- .../Grav/Common/Config/CompiledLanguages.php | 16 ++++++++++++++ system/src/Grav/Common/Config/Config.php | 13 ++++++++++-- system/src/Grav/Common/Config/Languages.php | 10 ++++++++- 6 files changed, 66 insertions(+), 19 deletions(-) diff --git a/system/src/Grav/Common/Config/CompiledBase.php b/system/src/Grav/Common/Config/CompiledBase.php index d20223ef1..f861cef34 100644 --- a/system/src/Grav/Common/Config/CompiledBase.php +++ b/system/src/Grav/Common/Config/CompiledBase.php @@ -43,11 +43,6 @@ abstract class CompiledBase */ protected $object; - /** - * @var bool - */ - protected $modified = false; - /** * @param string $cacheFolder Cache folder to be used. * @param array $files List of files as returned from ConfigFileFinder class. @@ -81,12 +76,9 @@ abstract class CompiledBase } /** - * @return bool + * Function gets called when cached configuration is saved. */ - public function modified() - { - return $this->modified; - } + public function modified() {} /** * Load the configuration. @@ -135,6 +127,11 @@ abstract class CompiledBase */ abstract protected function createObject(array $data = []); + /** + * Finalize configuration object. + */ + abstract protected function finalizeObject(); + /** * Load single configuration file and append it to the correct position. * @@ -160,6 +157,8 @@ abstract class CompiledBase } } + $this->finalizeObject(); + return true; } @@ -232,6 +231,6 @@ abstract class CompiledBase $file->unlock(); $file->free(); - $this->modified = true; + $this->modified(); } } diff --git a/system/src/Grav/Common/Config/CompiledBlueprints.php b/system/src/Grav/Common/Config/CompiledBlueprints.php index b4b4594e5..d78a0e04e 100644 --- a/system/src/Grav/Common/Config/CompiledBlueprints.php +++ b/system/src/Grav/Common/Config/CompiledBlueprints.php @@ -29,6 +29,11 @@ class CompiledBlueprints extends CompiledBase $this->object = new Blueprints($data); } + /** + * Finalize configuration object. + */ + protected function finalizeObject() {} + /** * Load single configuration file and append it to the correct position. * diff --git a/system/src/Grav/Common/Config/CompiledConfig.php b/system/src/Grav/Common/Config/CompiledConfig.php index 70593df33..cb9e59de2 100644 --- a/system/src/Grav/Common/Config/CompiledConfig.php +++ b/system/src/Grav/Common/Config/CompiledConfig.php @@ -65,12 +65,22 @@ class CompiledConfig extends CompiledBase } $this->object = new Config($data, $this->callable); - $this->object->checksum($this->checksum()); - $this->object->modified($this->modified()); + } - if (method_exists($this->object, 'prepare')) { - $this->object->prepare(); - } + /** + * 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); } /** diff --git a/system/src/Grav/Common/Config/CompiledLanguages.php b/system/src/Grav/Common/Config/CompiledLanguages.php index 29c1c81ef..ce4eb59dc 100644 --- a/system/src/Grav/Common/Config/CompiledLanguages.php +++ b/system/src/Grav/Common/Config/CompiledLanguages.php @@ -26,9 +26,25 @@ class CompiledLanguages extends CompiledBase protected function createObject(array $data = []) { $this->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. * diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php index 5bb1ff7c4..dc5169750 100644 --- a/system/src/Grav/Common/Config/Config.php +++ b/system/src/Grav/Common/Config/Config.php @@ -1,6 +1,7 @@ modified()) { + // Update current configuration. $this->items = $config->toArray(); $this->checksum($config->checksum()); - $this->modified($config->modified()); + $this->modified(true); + + $debugger->addMessage('Configuration was changed and saved.'); } return $this; @@ -57,7 +64,9 @@ class Config extends Data 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.'); diff --git a/system/src/Grav/Common/Config/Languages.php b/system/src/Grav/Common/Config/Languages.php index 1ea8b3c87..4d92d87e9 100644 --- a/system/src/Grav/Common/Config/Languages.php +++ b/system/src/Grav/Common/Config/Languages.php @@ -11,7 +11,6 @@ use Grav\Common\Data\Data; */ class Languages extends Data { - public function checksum($checksum = null) { if ($checksum !== null) { @@ -21,6 +20,15 @@ class Languages extends Data return $this->checksum; } + public function modified($modified = null) + { + if ($modified !== null) { + $this->modified = $modified; + } + + return $this->modified; + } + public function reformat() { if (isset($this->items['plugins'])) { From 09ed480628dd17ec8db168d3a013c05b8faf66bf Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Thu, 19 Nov 2015 17:05:21 +0200 Subject: [PATCH 07/19] Composer update --- composer.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 9328557aa..9b04239b3 100644 --- a/composer.lock +++ b/composer.lock @@ -217,12 +217,12 @@ "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "9a393ceb80f7497b6513feb574638e87048fed55" + "reference": "50a288b51058fa94cf5b37cfa4277535983cc9d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/9a393ceb80f7497b6513feb574638e87048fed55", - "reference": "9a393ceb80f7497b6513feb574638e87048fed55", + "url": "https://api.github.com/repos/filp/whoops/zipball/50a288b51058fa94cf5b37cfa4277535983cc9d5", + "reference": "50a288b51058fa94cf5b37cfa4277535983cc9d5", "shasum": "" }, "require": { @@ -267,7 +267,7 @@ "whoops", "zf2" ], - "time": "2015-09-27 09:47:06" + "time": "2015-11-14 20:08:27" }, { "name": "gregwar/cache", @@ -670,12 +670,12 @@ "source": { "type": "git", "url": "https://github.com/rockettheme/toolbox.git", - "reference": "0a7006b00880bd6873c49e2ed23939c785310417" + "reference": "defdf78a4c2f537067c6548c99560b78ed92745d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/0a7006b00880bd6873c49e2ed23939c785310417", - "reference": "0a7006b00880bd6873c49e2ed23939c785310417", + "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/defdf78a4c2f537067c6548c99560b78ed92745d", + "reference": "defdf78a4c2f537067c6548c99560b78ed92745d", "shasum": "" }, "require": { @@ -714,7 +714,7 @@ "source": "https://github.com/rockettheme/toolbox/tree/develop", "issues": "https://github.com/rockettheme/toolbox/issues" }, - "time": "2015-11-13 11:39:58" + "time": "2015-11-19 15:03:27" }, { "name": "symfony/console", From 3033818589b87f1257b49e297a47e720f00ebc78 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Fri, 20 Nov 2015 09:22:07 +0200 Subject: [PATCH 08/19] On plaintext authentication verify, use default hash even if its not set in configuration --- system/src/Grav/Common/User/User.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php index 2dc37fd7c..b79e94c01 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 From 2a02c8bc4f00b9acb48a8ca485decb93d6813cdb Mon Sep 17 00:00:00 2001 From: Tamas Csizmadia Date: Sat, 21 Nov 2015 21:53:03 +0100 Subject: [PATCH 09/19] Hungarian translation in system/languages/hu.yaml --- system/languages/hu.yaml | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 system/languages/hu.yaml diff --git a/system/languages/hu.yaml b/system/languages/hu.yaml new file mode 100644 index 000000000..1a77d9e65 --- /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: óta + 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: From 745b418cd7557c1f87a4df3782c895b26a9a73f6 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Thevenet Date: Sat, 21 Nov 2015 21:47:21 -0400 Subject: [PATCH 10/19] Update fr.yaml update based on the version: en Nov 17, 2015 --- system/languages/fr.yaml | 67 +++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 15 deletions(-) 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 From 37035a488dcbb24b6e98faf472130a50770c0336 Mon Sep 17 00:00:00 2001 From: Djamil Legato Date: Sat, 21 Nov 2015 20:53:11 -0800 Subject: [PATCH 11/19] Fixed `bin/plugin` help output --- CHANGELOG.md | 10 ++++++++-- bin/plugin | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 764aff8f5..8d82757aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 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 +12,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 +84,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; From b29d79738b04897dc1aaed37f3d8f27226361745 Mon Sep 17 00:00:00 2001 From: Tamas Csizmadia Date: Sun, 22 Nov 2015 12:34:44 +0100 Subject: [PATCH 12/19] Updated system/languages/hu.yaml Tweaked translation for 'ago' - sounds less weird. --- system/languages/hu.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/languages/hu.yaml b/system/languages/hu.yaml index 1a77d9e65..71c0ee91e 100644 --- a/system/languages/hu.yaml +++ b/system/languages/hu.yaml @@ -13,7 +13,7 @@ INFLECTOR_ORDINALS: NICETIME: NO_DATE_PROVIDED: Nincs dátum megadva BAD_DATE: Hibás dátum - AGO: óta + AGO: elteltével FROM_NOW: mostantól SECOND: másodperc MINUTE: perc From 1f5df814966fd13dc9afe64d8e057ffd5de9136b Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 23 Nov 2015 11:00:03 +0200 Subject: [PATCH 13/19] Sync Folder class with Gantry --- system/src/Grav/Common/Filesystem/Folder.php | 140 ++++++++++++++----- 1 file changed, 106 insertions(+), 34 deletions(-) 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; } } From 9764cf3f65fd1708cf242d886ed78342cbc82f07 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 23 Nov 2015 11:00:28 +0200 Subject: [PATCH 14/19] Sync CompiledFile class with Gantry --- system/src/Grav/Common/File/CompiledFile.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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']; } From 1d97f98515d5d8a283c8350269580c9a1f1ebe5d Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 23 Nov 2015 17:41:16 -0700 Subject: [PATCH 15/19] Made hash() public for future usage --- system/src/Grav/Common/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 515d05851..12c3d1026 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -474,7 +474,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; From b8f00243e6c18b0305e8ef1ba0e888043dc09b8b Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 23 Nov 2015 19:07:12 -0700 Subject: [PATCH 16/19] Make hash() public for future use --- system/src/Grav/Common/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 515d05851..12c3d1026 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -474,7 +474,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; From 965c8cfbe9dfafbabe12036032aef5a543713a9c Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 23 Nov 2015 19:07:46 -0700 Subject: [PATCH 17/19] Only generate one nonce per process --- system/src/Grav/Common/Utils.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 12c3d1026..71a706791 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 $nonce; + /** * @param string $haystack * @param string $needle @@ -491,9 +493,15 @@ abstract class Utils */ public static function getNonce($action, $plusOneTick = false) { + // Don't regenerate this again if not needed + if (isset(static::$nonce)) { + return static::$nonce; + } $nonce = self::hash(self::generateNonceString($action, $plusOneTick)); - $nonce = str_replace('/', 'SLASH', $nonce); - return $nonce; + + static::$nonce = str_replace('/', 'SLASH', $nonce); + + return static::$nonce; } /** From 583156d2f3fd54c073f17e5af771da7e2a0c55cf Mon Sep 17 00:00:00 2001 From: Flavio Copes Date: Tue, 24 Nov 2015 09:03:48 +0100 Subject: [PATCH 18/19] Handle multiple nonce action types per page --- system/src/Grav/Common/Utils.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 71a706791..39528220d 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -15,7 +15,7 @@ abstract class Utils { use GravTrait; - protected static $nonce; + protected static $nonces = []; /** * @param string $haystack @@ -494,14 +494,14 @@ abstract class Utils public static function getNonce($action, $plusOneTick = false) { // Don't regenerate this again if not needed - if (isset(static::$nonce)) { - return static::$nonce; + if (isset(static::$nonces[$action])) { + return static::$nonces[$action]; } $nonce = self::hash(self::generateNonceString($action, $plusOneTick)); - static::$nonce = str_replace('/', 'SLASH', $nonce); + static::$nonces[$action] = str_replace('/', 'SLASH', $nonce); - return static::$nonce; + return static::$nonces[$action]; } /** From 20f17130a2ec5af50866ee86975e3d48d04cc26f Mon Sep 17 00:00:00 2001 From: Flavio Copes Date: Tue, 24 Nov 2015 15:17:08 +0100 Subject: [PATCH 19/19] Add the IP address of the user to the nonce string calculation even if it's logged in --- system/src/Grav/Common/Utils.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 39528220d..759d3f949 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -437,11 +437,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'] : ''; }