diff --git a/.gitignore b/.gitignore
index 01b701cf8..755814004 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ user/plugins/*
!user/plugins/.*
user/themes/*
!user/themes/.*
+user/localhost/config/security.yaml
# OS Generated
.DS_Store*
diff --git a/.htaccess b/.htaccess
index 55bcd08a7..4017987a9 100644
--- a/.htaccess
+++ b/.htaccess
@@ -54,7 +54,7 @@ RewriteRule \.md$ error [F]
# Block all direct access to files and folders beginning with a dot
RewriteRule (^\.|/\.) - [F]
# Block access to specific files in the root folder
-RewriteRule ^(LICENSE|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess)$ error [F]
+RewriteRule ^(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess)$ error [F]
## End - Security
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 764aff8f5..2fc9ca4b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,28 @@
+# v1.0.0-rc.6
+## 12/01/2015
+
+1. [](#new)
+ * Refactor Config classes for improved performance!
+ * Refactor Data classes to use `NestedArrayAccess` instead of `DataMutatorTrait`
+ * Added support for `classes` and `id` on medium objects to set CSS values
+ * Data objects: Allow function call chaining
+ * Data objects: Lazy load blueprints only if needed
+ * Automatically create unique security salt for each configuration
+ * Added Hungarian translation
+ * Added support for User groups
+1. [](#improved)
+ * Improved robots.txt to disallow crawling of non-user folders
+ * Nonces only generated once per action and process
+ * Added IP into Nonce string calculation
+ * Nonces now use random string with random salt to improve performance
+ * Improved list form handling #475
+ * Vendor library updates
+1. [](#bugfix)
+ * Fixed help output for `bin/plugin`
+ * Fix for nested logic for lists and form parsing #273
+ * Fix for array form fields and last entry not getting deleted
+ * Should not be able to set parent to self #308
+
# v1.0.0-rc.5
## 11/20/2015
@@ -6,7 +31,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 +103,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/LICENSE b/LICENSE.txt
similarity index 100%
rename from LICENSE
rename to LICENSE.txt
diff --git a/bin/plugin b/bin/plugin
index dc63f4a9d..ffe2dd81f 100755
--- a/bin/plugin
+++ b/bin/plugin
@@ -44,7 +44,7 @@ $grav['plugins']->init();
$grav['themes']->init();
$app = new Application('Grav Plugins Commands', GRAV_VERSION);
-$pattern = '/([A-Z]\w+Command\.php)$/usm';
+$pattern = '([A-Z]\w+Command\.php)';
// get arguments and strip the application name
if (null === $argv) {
@@ -70,7 +70,7 @@ if (!$name) {
$output->writeln('');
$output->writeln("Example:");
$output->writeln(" {$bin} error log -l 1 --trace");
- $list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '\/cli\/' . $pattern]);
+ $list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm']);
if (count($list)) {
$available = [];
@@ -101,7 +101,7 @@ if ($plugin === null) {
$path = 'plugins://' . $name . '/cli';
try {
- $commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => $pattern]);
+ $commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm']);
} catch (\RuntimeException $e) {
$output->writeln("No Console Commands for '{$name}' where found in '{$path}'");
exit;
diff --git a/composer.json b/composer.json
index 724cff2f8..86116ed1f 100644
--- a/composer.json
+++ b/composer.json
@@ -21,7 +21,7 @@
"mrclay/minify": "~2.2",
"donatj/phpuseragentparser": "~0.3",
"pimple/pimple": "~3.0",
- "rockettheme/toolbox": "1.1.*",
+ "rockettheme/toolbox": "~1.2",
"maximebf/debugbar": "~1.10"
},
"autoload": {
diff --git a/composer.lock b/composer.lock
index c6c4fe6d9..d19e9a3f8 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1,11 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "e1db721096772d41f16003b39b47c85a",
- "content-hash": "294dd2282a332d96b19d163ad08e7ba7",
+ "hash": "b1323e540382de7390663756b3a87de7",
"packages": [
{
"name": "doctrine/cache",
@@ -169,16 +168,16 @@
},
{
"name": "erusev/parsedown-extra",
- "version": "0.7.0",
+ "version": "0.7.1",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown-extra.git",
- "reference": "11a44e076d02ffcc4021713398a60cd73f78b6f5"
+ "reference": "0db5cce7354e4b76f155d092ab5eb3981c21258c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/11a44e076d02ffcc4021713398a60cd73f78b6f5",
- "reference": "11a44e076d02ffcc4021713398a60cd73f78b6f5",
+ "url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/0db5cce7354e4b76f155d092ab5eb3981c21258c",
+ "reference": "0db5cce7354e4b76f155d092ab5eb3981c21258c",
"shasum": ""
},
"require": {
@@ -209,7 +208,7 @@
"parsedown",
"parser"
],
- "time": "2015-01-25 14:52:34"
+ "time": "2015-11-01 10:19:22"
},
{
"name": "filp/whoops",
@@ -217,16 +216,16 @@
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
- "reference": "50a288b51058fa94cf5b37cfa4277535983cc9d5"
+ "reference": "9a393ceb80f7497b6513feb574638e87048fed55"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/50a288b51058fa94cf5b37cfa4277535983cc9d5",
- "reference": "50a288b51058fa94cf5b37cfa4277535983cc9d5",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/9a393ceb80f7497b6513feb574638e87048fed55",
+ "reference": "9a393ceb80f7497b6513feb574638e87048fed55",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": ">=5.3.3"
},
"require-dev": {
"mockery/mockery": "0.9.*"
@@ -267,7 +266,7 @@
"whoops",
"zf2"
],
- "time": "2015-11-14 20:08:27"
+ "time": "2015-09-27 09:47:06"
},
{
"name": "gregwar/cache",
@@ -666,16 +665,16 @@
},
{
"name": "rockettheme/toolbox",
- "version": "1.1.4",
+ "version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/rockettheme/toolbox.git",
- "reference": "ff677d8f66d1addd3590d0cb85bcbaff4174d9c9"
+ "reference": "0c7a3b4b6e4d73be8512e89f7acde6899334b7f2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/ff677d8f66d1addd3590d0cb85bcbaff4174d9c9",
- "reference": "ff677d8f66d1addd3590d0cb85bcbaff4174d9c9",
+ "url": "https://api.github.com/repos/rockettheme/toolbox/zipball/0c7a3b4b6e4d73be8512e89f7acde6899334b7f2",
+ "reference": "0c7a3b4b6e4d73be8512e89f7acde6899334b7f2",
"shasum": ""
},
"require": {
@@ -711,20 +710,20 @@
"php",
"rockettheme"
],
- "time": "2015-10-15 23:27:40"
+ "time": "2015-11-24 17:04:24"
},
{
"name": "symfony/console",
- "version": "v2.7.6",
+ "version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "5efd632294c8320ea52492db22292ff853a43766"
+ "reference": "16bb1cb86df43c90931df65f529e7ebd79636750"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/5efd632294c8320ea52492db22292ff853a43766",
- "reference": "5efd632294c8320ea52492db22292ff853a43766",
+ "url": "https://api.github.com/repos/symfony/console/zipball/16bb1cb86df43c90931df65f529e7ebd79636750",
+ "reference": "16bb1cb86df43c90931df65f529e7ebd79636750",
"shasum": ""
},
"require": {
@@ -749,7 +748,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -767,20 +769,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2015-10-20 14:38:46"
+ "time": "2015-11-18 09:54:26"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.7.6",
+ "version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8"
+ "reference": "7e2f9c31645680026c2372edf66f863fc7757af5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87a5db5ea887763fa3a31a5471b512ff1596d9b8",
- "reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7e2f9c31645680026c2372edf66f863fc7757af5",
+ "reference": "7e2f9c31645680026c2372edf66f863fc7757af5",
"shasum": ""
},
"require": {
@@ -806,7 +808,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -824,20 +829,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2015-10-11 09:39:48"
+ "time": "2015-10-30 20:10:21"
},
{
"name": "symfony/var-dumper",
- "version": "v2.7.6",
+ "version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "eb033050050916b6bfa51be71009ef67b16046c9"
+ "reference": "72bcb27411780eaee9469729aace73c0d46fb2b8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/eb033050050916b6bfa51be71009ef67b16046c9",
- "reference": "eb033050050916b6bfa51be71009ef67b16046c9",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72bcb27411780eaee9469729aace73c0d46fb2b8",
+ "reference": "72bcb27411780eaee9469729aace73c0d46fb2b8",
"shasum": ""
},
"require": {
@@ -858,7 +863,10 @@
],
"psr-4": {
"Symfony\\Component\\VarDumper\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -880,20 +888,20 @@
"debug",
"dump"
],
- "time": "2015-10-25 17:17:38"
+ "time": "2015-11-18 13:41:01"
},
{
"name": "symfony/yaml",
- "version": "v2.7.6",
+ "version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "eca9019c88fbe250164affd107bc8057771f3f4d"
+ "reference": "4cfcd7a9fceba662b3c036b7d9a91f6197af046c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/eca9019c88fbe250164affd107bc8057771f3f4d",
- "reference": "eca9019c88fbe250164affd107bc8057771f3f4d",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/4cfcd7a9fceba662b3c036b7d9a91f6197af046c",
+ "reference": "4cfcd7a9fceba662b3c036b7d9a91f6197af046c",
"shasum": ""
},
"require": {
@@ -908,7 +916,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -926,7 +937,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2015-10-11 09:39:48"
+ "time": "2015-11-18 13:41:01"
},
{
"name": "twig/twig",
diff --git a/htaccess.txt b/htaccess.txt
index 55bcd08a7..4017987a9 100644
--- a/htaccess.txt
+++ b/htaccess.txt
@@ -54,7 +54,7 @@ RewriteRule \.md$ error [F]
# Block all direct access to files and folders beginning with a dot
RewriteRule (^\.|/\.) - [F]
# Block access to specific files in the root folder
-RewriteRule ^(LICENSE|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess)$ error [F]
+RewriteRule ^(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess)$ error [F]
## End - Security
diff --git a/lighttpd.conf b/lighttpd.conf
index 69acbe484..8429b865f 100644
--- a/lighttpd.conf
+++ b/lighttpd.conf
@@ -27,7 +27,7 @@ url.rewrite-if-not-file = (
)
#IMPROVING SECURITY
-$HTTP["url"] =~ "^/grav_path/(LICENSE|composer.json|composer.lock|nginx.conf|web.config)$" {
+$HTTP["url"] =~ "^/grav_path/(LICENSE.txt|composer.json|composer.lock|nginx.conf|web.config)$" {
url.access-deny = ("")
}
$HTTP["url"] =~ "^/grav_path/(.git|cache|bin|logs|backup)/(.*)" {
diff --git a/nginx.conf b/nginx.conf
index d325ff308..dbc581fee 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -38,7 +38,7 @@ server {
# deny running scripts inside user folder
location ~* /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny access to specific files in the root folder
- location ~ /(LICENSE|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess) { return 403; }
+ location ~ /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess) { return 403; }
## End - Security
}
diff --git a/robots.txt b/robots.txt
index eb0536286..3b558d635 100644
--- a/robots.txt
+++ b/robots.txt
@@ -1,2 +1,11 @@
User-agent: *
-Disallow:
+Disallow: /backup/
+Disallow: /bin/
+Disallow: /cache/
+Disallow: /grav/
+Disallow: /logs/
+Disallow: /system/
+Disallow: /vendor/
+Disallow: /user/
+Allow: /user/pages/
+Allow: /user/themes/
diff --git a/system/blueprints/media/meta.yaml b/system/blueprints/media/meta.yaml
new file mode 100644
index 000000000..7d242e0b5
--- /dev/null
+++ b/system/blueprints/media/meta.yaml
@@ -0,0 +1,7 @@
+form:
+ validation: loose
+ fields:
+
+ alt_text:
+ type: string
+ label: Alt Text
diff --git a/system/blueprints/media/move.yaml b/system/blueprints/media/move.yaml
new file mode 100644
index 000000000..6287e0595
--- /dev/null
+++ b/system/blueprints/media/move.yaml
@@ -0,0 +1,8 @@
+form:
+ validation: loose
+ fields:
+ route:
+ type: select
+ label: PLUGIN_ADMIN.PAGE
+ classes: fancy
+ '@data-options': '\Grav\Common\Page\Pages::parents'
diff --git a/system/blueprints/media/rename.yaml b/system/blueprints/media/rename.yaml
new file mode 100644
index 000000000..529349cac
--- /dev/null
+++ b/system/blueprints/media/rename.yaml
@@ -0,0 +1,8 @@
+form:
+ validation: loose
+ fields:
+ new_file_name:
+ type: text
+ label: PLUGIN_ADMIN_PRO.NEW_FILE_NAME
+ validate:
+ required: true
diff --git a/system/blueprints/user/account.yaml b/system/blueprints/user/account.yaml
index efe5bda74..83674c721 100644
--- a/system/blueprints/user/account.yaml
+++ b/system/blueprints/user/account.yaml
@@ -54,3 +54,26 @@ form:
default: 'en'
help: PLUGIN_ADMIN.LANGUAGE_HELP
+ groups:
+ type: selectize
+ size: large
+ label: PLUGIN_ADMIN.GROUPS
+ '@data-options': '\Grav\User\Groups::groups'
+ classes: fancy
+ help: PLUGIN_ADMIN.GROUPS_HELP
+ validate:
+ type: commalist
+
+ access.admin:
+ type: array
+ label: PLUGIN_ADMIN.ADMIN_ACCESS
+ multiple: false
+ validate:
+ type: array
+
+ access.site:
+ type: array
+ label: PLUGIN_ADMIN.SITE_ACCESS
+ multiple: false
+ validate:
+ type: array
\ No newline at end of file
diff --git a/system/blueprints/user/group.yaml b/system/blueprints/user/group.yaml
new file mode 100644
index 000000000..e627a7c9e
--- /dev/null
+++ b/system/blueprints/user/group.yaml
@@ -0,0 +1,44 @@
+title: Group
+form:
+ validation: loose
+
+ fields:
+ spacer:
+ type: spacer
+ text: '
'
+
+ groupname:
+ type: text
+ size: large
+ label: PLUGIN_ADMIN.NAME
+ disabled: true
+ readonly: true
+
+ readableName:
+ type: text
+ size: large
+ label: PLUGIN_ADMIN_PRO.READABLE_NAME
+
+ description:
+ type: text
+ size: large
+ label: PLUGIN_ADMIN.DESCRIPTION
+
+ icon:
+ type: text
+ size: small
+ label: PLUGIN_ADMIN_PRO.ICON
+
+ access.admin:
+ type: array
+ label: PLUGIN_ADMIN.ADMIN_ACCESS
+ multiple: false
+ validate:
+ type: array
+
+ access.site:
+ type: array
+ label: PLUGIN_ADMIN.SITE_ACCESS
+ multiple: false
+ validate:
+ type: array
\ No newline at end of file
diff --git a/system/blueprints/user/group_new.yaml b/system/blueprints/user/group_new.yaml
new file mode 100644
index 000000000..dd816a954
--- /dev/null
+++ b/system/blueprints/user/group_new.yaml
@@ -0,0 +1,16 @@
+title: PLUGIN_ADMIN_PRO.ADD_GROUP
+
+form:
+ validation: loose
+ fields:
+
+ content:
+ type: section
+ title: PLUGIN_ADMIN_PRO.ADD_GROUP
+
+ groupname:
+ type: text
+ label: PLUGIN_ADMIN_PRO.GROUP_NAME
+ help: PLUGIN_ADMIN_PRO.GROUP_NAME_HELP
+ validate:
+ required: true
diff --git a/system/config/system.yaml b/system/config/system.yaml
index 8f09cf599..f64620cbf 100644
--- a/system/config/system.yaml
+++ b/system/config/system.yaml
@@ -108,6 +108,6 @@ media:
session:
enabled: true # Enable Session support
timeout: 1800 # Timeout in seconds
- name: grav-site # Name prefix of the session cookie
+ name: grav-site # Name prefix of the session cookie. Use alphanumeric, dashes or underscores only. Do not use dots in the session name
diff --git a/system/defines.php b/system/defines.php
index f3aacc286..2e0e01442 100644
--- a/system/defines.php
+++ b/system/defines.php
@@ -2,7 +2,7 @@
// Some standard defines
define('GRAV', true);
-define('GRAV_VERSION', '1.0.0-rc.5');
+define('GRAV_VERSION', '1.0.0-rc.6');
define('DS', '/');
// Directories and Paths
diff --git a/system/languages/en.yaml b/system/languages/en.yaml
index a1eab6f19..654c0d3e5 100644
--- a/system/languages/en.yaml
+++ b/system/languages/en.yaml
@@ -95,3 +95,4 @@ NICETIME:
FORM:
VALIDATION_FAIL: Validation failed:
INVALID_INPUT: Invalid input in
+ MISSING_REQUIRED_FIELD: Missing required field:
diff --git a/system/languages/es.yaml b/system/languages/es.yaml
new file mode 100644
index 000000000..41df5d55b
--- /dev/null
+++ b/system/languages/es.yaml
@@ -0,0 +1,42 @@
+NICETIME:
+ NO_DATE_PROVIDED: No se proporcionó fecha
+ BAD_DATE: Fecha erronea
+ AGO: antes
+ FROM_NOW: desde ahora
+ SECOND: segundo
+ MINUTE: minuto
+ HOUR: hora
+ DAY: dia
+ WEEK: semana
+ MONTH: mes
+ YEAR: año
+ DECADE: decada
+ SEC: seg
+ MIN: min
+ HR: hr
+ DAY: dia
+ WK: sem
+ MO: mes
+ YR: yr
+ DEC: dec
+ SECOND_PLURAL: segundos
+ MINUTE_PLURAL: minutos
+ HOUR_PLURAL: horas
+ DAY_PLURAL: días
+ WEEK_PLURAL: semanas
+ MONTH_PLURAL: meses
+ YEAR_PLURAL: años
+ DECADE_PLURAL: decadas
+ SEC_PLURAL: segs
+ MIN_PLURAL: mins
+ HR_PLURAL: hrs
+ DAY_PLURAL: dias
+ WK_PLURAL: sem
+ MO_PLURAL: mes
+ YR_PLURAL: años
+ DEC_PLURAL: decs
+FORM:
+ VALIDATION_FAIL: Falló la validación.
+ INVALID_INPUT: "Dato inválido en: "
+ MISSING_REQUIRED_FIELD: "Falta el campo requerido: "
+
diff --git a/system/languages/fr.yaml b/system/languages/fr.yaml
index bfc1c117f..4dcafc8a8 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,7 @@ NICETIME:
MO_PLURAL: m
YR_PLURAL: a
DEC_PLURAL: d
+FORM:
+ VALIDATION_FAIL: La validation a échoué :
+ INVALID_INPUT: Saisie non valide
+ MISSING_REQUIRED_FIELD: Champ obligatoire manquant :
diff --git a/system/languages/hr.yaml b/system/languages/hr.yaml
index 0d84281c5..3dbd80218 100644
--- a/system/languages/hr.yaml
+++ b/system/languages/hr.yaml
@@ -26,12 +26,18 @@ NICETIME:
YR: g
DEC: des
SECOND_PLURAL: sekundi
+ SECOND_PLURAL_MORE_THAN_TWO: sekunde
MINUTE_PLURAL: minuta
+ MINUTE_PLURAL_MORE_THAN_TWO: minute
HOUR_PLURAL: sati
+ HOUR_PLURAL_MORE_THAN_TWO: sata
DAY_PLURAL: dana
WEEK_PLURAL: tjedana
+ WEEK_PLURAL_MORE_THAN_TWO: tjedna
MONTH_PLURAL: mjeseci
+ MONTH_PLURAL_MORE_THAN_TWO: mjeseca
YEAR_PLURAL: godina
+ YEAR_PLURAL_MORE_THAN_TWO: godine
DECADE_PLURAL: desetljeća
SEC_PLURAL: sek
MIN_PLURAL: min
diff --git a/system/languages/hu.yaml b/system/languages/hu.yaml
new file mode 100644
index 000000000..71c0ee91e
--- /dev/null
+++ b/system/languages/hu.yaml
@@ -0,0 +1,52 @@
+FRONTMATTER_ERROR_PAGE: "---\ncím: %1$s\n---\n\n# Hiba: Érvénytelen Frontmatter\n\nElérési út: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
+INFLECTOR_IRREGULAR:
+ 'person': 'személyek'
+ 'man': 'férfiak'
+ 'child': 'gyerekek'
+ 'sex': 'nemek'
+ 'move': 'lépések'
+INFLECTOR_ORDINALS:
+ 'default': '.'
+ 'first': '.'
+ 'second': '.'
+ 'third': '.'
+NICETIME:
+ NO_DATE_PROVIDED: Nincs dátum megadva
+ BAD_DATE: Hibás dátum
+ AGO: elteltével
+ FROM_NOW: mostantól
+ SECOND: másodperc
+ MINUTE: perc
+ HOUR: óra
+ DAY: nap
+ WEEK: hét
+ MONTH: hónap
+ YEAR: év
+ DECADE: évtized
+ SEC: mp
+ MIN: p
+ HR: ó
+ DAY: nap
+ WK: hét
+ MO: hó
+ YR: év
+ DEC: évt
+ SECOND_PLURAL: másodperc
+ MINUTE_PLURAL: perc
+ HOUR_PLURAL: óra
+ DAY_PLURAL: nap
+ WEEK_PLURAL: hét
+ MONTH_PLURAL: hónap
+ YEAR_PLURAL: év
+ DECADE_PLURAL: évtized
+ SEC_PLURAL: mp
+ MIN_PLURAL: perc
+ HR_PLURAL: ó
+ DAY_PLURAL: nap
+ WK_PLURAL: hét
+ MO_PLURAL: hó
+ YR_PLURAL: év
+ DEC_PLURAL: évt
+FORM:
+ VALIDATION_FAIL: A validáció hibát talált:
+ INVALID_INPUT: Az itt megadott érték érvénytelen:
diff --git a/system/languages/it.yaml b/system/languages/it.yaml
index 5f61ad3c1..c6a92ec9d 100644
--- a/system/languages/it.yaml
+++ b/system/languages/it.yaml
@@ -19,3 +19,7 @@ NICETIME:
MONTH_PLURAL: mesi
YEAR_PLURAL: anni
DECADE_PLURAL: decadi
+FORM:
+ VALIDATION_FAIL: Validazione fallita:
+ INVALID_INPUT: Input invalido in
+ MISSING_REQUIRED_FIELD: Campo richiesto mancante:
diff --git a/system/src/Grav/Common/Config/Blueprints.php b/system/src/Grav/Common/Config/Blueprints.php
deleted file mode 100644
index 1af61f47c..000000000
--- a/system/src/Grav/Common/Config/Blueprints.php
+++ /dev/null
@@ -1,207 +0,0 @@
-grav = $grav ?: Grav::instance();
- }
-
- public function init()
- {
- /** @var UniformResourceLocator $locator */
- $locator = $this->grav['locator'];
-
- $blueprints = $locator->findResources('blueprints://config');
- $plugins = $locator->findResources('plugins://');
-
- $blueprintFiles = $this->getBlueprintFiles($blueprints, $plugins);
-
- $this->loadCompiledBlueprints($plugins + $blueprints, $blueprintFiles);
- }
-
- protected function loadCompiledBlueprints($blueprints, $blueprintFiles)
- {
- $checksum = md5(serialize($blueprints));
- $filename = CACHE_DIR . 'compiled/blueprints/' . $checksum .'.php';
- $checksum .= ':'.md5(serialize($blueprintFiles));
- $class = get_class($this);
- $file = PhpFile::instance($filename);
-
- if ($file->exists()) {
- $cache = $file->exists() ? $file->content() : null;
- } else {
- $cache = null;
- }
-
-
- // Load real file if cache isn't up to date (or is invalid).
- if (
- !is_array($cache)
- || empty($cache['checksum'])
- || empty($cache['$class'])
- || $cache['checksum'] != $checksum
- || $cache['@class'] != $class
- ) {
- // Attempt to lock the file for writing.
- $file->lock(false);
-
- // Load blueprints.
- $this->blueprints = new Blueprints();
- foreach ($blueprintFiles as $key => $files) {
- $this->loadBlueprints($key);
- }
-
- $cache = [
- '@class' => $class,
- 'checksum' => $checksum,
- 'files' => $blueprintFiles,
- 'data' => $this->blueprints->toArray()
- ];
-
- // If compiled file wasn't already locked by another process, save it.
- if ($file->locked() !== false) {
- $file->save($cache);
- $file->unlock();
- }
- } else {
- $this->blueprints = new Blueprints($cache['data']);
- }
- }
-
- /**
- * Load global blueprints.
- *
- * @param string $key
- * @param array $files
- */
- public function loadBlueprints($key, array $files = null)
- {
- if (is_null($files)) {
- $files = $this->files[$key];
- }
- foreach ($files as $name => $item) {
- $file = CompiledYamlFile::instance($item['file']);
- $this->blueprints->embed($name, $file->content(), '/');
- }
- }
-
- /**
- * Get all blueprint files (including plugins).
- *
- * @param array $blueprints
- * @param array $plugins
- * @return array
- */
- protected function getBlueprintFiles(array $blueprints, array $plugins)
- {
- $list = [];
- foreach (array_reverse($plugins) as $folder) {
- $list += $this->detectPlugins($folder, true);
- }
- foreach (array_reverse($blueprints) as $folder) {
- $list += $this->detectConfig($folder, true);
- }
- return $list;
- }
-
- /**
- * Detects all plugins with a configuration file and returns last modification time.
- *
- * @param string $lookup Location to look up from.
- * @param bool $blueprints
- * @return array
- * @internal
- */
- protected function detectPlugins($lookup = SYSTEM_DIR, $blueprints = false)
- {
- $find = $blueprints ? 'blueprints.yaml' : '.yaml';
- $location = $blueprints ? 'blueprintFiles' : 'configFiles';
- $path = trim(Folder::getRelativePath($lookup), '/');
- if (isset($this->{$location}[$path])) {
- return [$path => $this->{$location}[$path]];
- }
-
- $list = [];
-
- if (is_dir($lookup)) {
- $iterator = new \DirectoryIterator($lookup);
-
- /** @var \DirectoryIterator $directory */
- foreach ($iterator as $directory) {
- if (!$directory->isDir() || $directory->isDot()) {
- continue;
- }
-
- $name = $directory->getBasename();
- $filename = "{$path}/{$name}/" . ($find && $find[0] != '.' ? $find : $name . $find);
-
- if (is_file($filename)) {
- $list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
- }
- }
- }
-
- $this->{$location}[$path] = $list;
-
- return [$path => $list];
- }
-
- /**
- * Detects all plugins with a configuration file and returns last modification time.
- *
- * @param string $lookup Location to look up from.
- * @param bool $blueprints
- * @return array
- * @internal
- */
- protected function detectConfig($lookup = SYSTEM_DIR, $blueprints = false)
- {
- $location = $blueprints ? 'blueprintFiles' : 'configFiles';
- $path = trim(Folder::getRelativePath($lookup), '/');
- if (isset($this->{$location}[$path])) {
- return [$path => $this->{$location}[$path]];
- }
-
- if (is_dir($lookup)) {
- // Find all system and user configuration files.
- $options = [
- 'compare' => 'Filename',
- 'pattern' => '|\.yaml$|',
- 'filters' => [
- 'key' => '|\.yaml$|',
- 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
- return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
- }],
- 'key' => 'SubPathname'
- ];
-
- $list = Folder::all($lookup, $options);
- } else {
- $list = [];
- }
-
- $this->{$location}[$path] = $list;
-
- return [$path => $list];
- }
-}
diff --git a/system/src/Grav/Common/Config/CompiledBase.php b/system/src/Grav/Common/Config/CompiledBase.php
new file mode 100644
index 000000000..f861cef34
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledBase.php
@@ -0,0 +1,236 @@
+cacheFolder = $cacheFolder;
+ $this->files = $files;
+ $this->path = $path ? rtrim($path, '\\/') . '/' : '';
+ }
+
+ /**
+ * Get filename for the compiled PHP file.
+ *
+ * @param string $name
+ * @return $this
+ */
+ public function name($name = null)
+ {
+ if (!$this->name) {
+ $this->name = $name ?: md5(json_encode(array_keys($this->files)));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Function gets called when cached configuration is saved.
+ */
+ public function modified() {}
+
+ /**
+ * Load the configuration.
+ *
+ * @return mixed
+ */
+ public function load()
+ {
+ if ($this->object) {
+ return $this->object;
+ }
+
+ $filename = $this->createFilename();
+ if (!$this->loadCompiledFile($filename) && $this->loadFiles()) {
+ $this->saveCompiledFile($filename);
+ }
+
+ return $this->object;
+ }
+
+ /**
+ * Returns checksum from the configuration files.
+ *
+ * You can set $this->checksum = false to disable this check.
+ *
+ * @return bool|string
+ */
+ public function checksum()
+ {
+ if (!isset($this->checksum)) {
+ $this->checksum = md5(json_encode($this->files) . $this->version);
+ }
+
+ return $this->checksum;
+ }
+
+ protected function createFilename()
+ {
+ return "{$this->cacheFolder}/{$this->name()->name}.php";
+ }
+
+ /**
+ * Create configuration object.
+ *
+ * @param array $data
+ */
+ abstract protected function createObject(array $data = []);
+
+ /**
+ * Finalize configuration object.
+ */
+ abstract protected function finalizeObject();
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ abstract protected function loadFile($name, $filename);
+
+ /**
+ * Load and join all configuration files.
+ *
+ * @return bool
+ * @internal
+ */
+ protected function loadFiles()
+ {
+ $this->createObject();
+
+ $list = array_reverse($this->files);
+ foreach ($list as $files) {
+ foreach ($files as $name => $item) {
+ $this->loadFile($name, $this->path . $item['file']);
+ }
+ }
+
+ $this->finalizeObject();
+
+ return true;
+ }
+
+ /**
+ * Load compiled file.
+ *
+ * @param string $filename
+ * @return bool
+ * @internal
+ */
+ protected function loadCompiledFile($filename)
+ {
+ if (!file_exists($filename)) {
+ return false;
+ }
+
+ $cache = include $filename;
+ if (
+ !is_array($cache)
+ || !isset($cache['checksum'])
+ || !isset($cache['data'])
+ || !isset($cache['@class'])
+ || $cache['@class'] != get_class($this)
+ ) {
+ return false;
+ }
+
+ // Load real file if cache isn't up to date (or is invalid).
+ if ($cache['checksum'] !== $this->checksum()) {
+ return false;
+ }
+
+ $this->createObject($cache['data']);
+
+ return true;
+ }
+
+ /**
+ * Save compiled file.
+ *
+ * @param string $filename
+ * @throws \RuntimeException
+ * @internal
+ */
+ protected function saveCompiledFile($filename)
+ {
+ $file = PhpFile::instance($filename);
+
+ // Attempt to lock the file for writing.
+ try {
+ $file->lock(false);
+ } catch (\Exception $e) {
+ // Another process has locked the file; we will check this in a bit.
+ }
+
+ if ($file->locked() === false) {
+ // File was already locked by another process.
+ return;
+ }
+
+ $cache = [
+ '@class' => get_class($this),
+ 'timestamp' => time(),
+ 'checksum' => $this->checksum(),
+ 'files' => $this->files,
+ 'data' => $this->object->toArray()
+ ];
+
+ $file->save($cache);
+ $file->unlock();
+ $file->free();
+
+ $this->modified();
+ }
+}
diff --git a/system/src/Grav/Common/Config/CompiledBlueprints.php b/system/src/Grav/Common/Config/CompiledBlueprints.php
new file mode 100644
index 000000000..d78a0e04e
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledBlueprints.php
@@ -0,0 +1,49 @@
+object = new Blueprints($data);
+ }
+
+ /**
+ * Finalize configuration object.
+ */
+ protected function finalizeObject() {}
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ protected function loadFile($name, $filename)
+ {
+ $file = CompiledYamlFile::instance($filename);
+ $this->object->embed($name, $file->content(), '/');
+ $file->free();
+ }
+}
diff --git a/system/src/Grav/Common/Config/CompiledConfig.php b/system/src/Grav/Common/Config/CompiledConfig.php
new file mode 100644
index 000000000..cb9e59de2
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledConfig.php
@@ -0,0 +1,98 @@
+callable = $blueprints;
+
+ return $this;
+ }
+
+ /**
+ * @param bool $withDefaults
+ * @return mixed
+ */
+ public function load($withDefaults = false)
+ {
+ $this->withDefaults = $withDefaults;
+
+ return parent::load();
+ }
+
+ /**
+ * Create configuration object.
+ *
+ * @param array $data
+ */
+ protected function createObject(array $data = [])
+ {
+ if ($this->withDefaults && empty($data) && is_callable($this->callable)) {
+ $blueprints = $this->callable;
+ $data = $blueprints()->getDefaults();
+ }
+
+ $this->object = new Config($data, $this->callable);
+ }
+
+ /**
+ * Finalize configuration object.
+ */
+ protected function finalizeObject()
+ {
+ $this->object->checksum($this->checksum());
+ }
+
+ /**
+ * Function gets called when cached configuration is saved.
+ */
+ public function modified()
+ {
+ $this->object->modified(true);
+ }
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ protected function loadFile($name, $filename)
+ {
+ $file = CompiledYamlFile::instance($filename);
+ $this->object->join($name, $file->content(), '/');
+ $file->free();
+ }
+}
diff --git a/system/src/Grav/Common/Config/CompiledLanguages.php b/system/src/Grav/Common/Config/CompiledLanguages.php
new file mode 100644
index 000000000..ce4eb59dc
--- /dev/null
+++ b/system/src/Grav/Common/Config/CompiledLanguages.php
@@ -0,0 +1,64 @@
+object = new Languages($data);
+ }
+
+ /**
+ * Finalize configuration object.
+ */
+ protected function finalizeObject()
+ {
+ $this->object->checksum($this->checksum());
+ }
+
+
+ /**
+ * Function gets called when cached configuration is saved.
+ */
+ public function modified()
+ {
+ $this->object->modified(true);
+ }
+
+ /**
+ * Load single configuration file and append it to the correct position.
+ *
+ * @param string $name Name of the position.
+ * @param string $filename File to be loaded.
+ */
+ protected function loadFile($name, $filename)
+ {
+ $file = CompiledYamlFile::instance($filename);
+ if (preg_match('|languages\.yaml$|', $filename)) {
+ $this->object->mergeRecursive($file->content());
+ } else {
+ $this->object->join($name, $file->content(), '/');
+ }
+ $file->free();
+ }
+}
diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php
index ed13dbcbd..dc5169750 100644
--- a/system/src/Grav/Common/Config/Config.php
+++ b/system/src/Grav/Common/Config/Config.php
@@ -1,12 +1,10 @@
[
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['system'],
- ]
- ],
- 'user' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user'],
- ]
- ],
- 'asset' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['assets'],
- ]
- ],
- 'blueprints' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://blueprints', 'system/blueprints'],
- ]
- ],
- 'config' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://config', 'system/config'],
- ]
- ],
- 'plugins' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://plugins'],
- ]
- ],
- 'plugin' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://plugins'],
- ]
- ],
- 'themes' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://themes'],
- ]
- ],
- 'languages' => [
- 'type' => 'ReadOnlyStream',
- 'prefixes' => [
- '' => ['user://languages', 'system/languages'],
- ]
- ],
- 'cache' => [
- 'type' => 'Stream',
- 'prefixes' => [
- '' => ['cache'],
- 'images' => ['images']
- ]
- ],
- 'log' => [
- 'type' => 'Stream',
- 'prefixes' => [
- '' => ['logs']
- ]
- ],
- 'backup' => [
- 'type' => 'Stream',
- 'prefixes' => [
- '' => ['backup']
- ]
- ]
- ];
-
- protected $setup = [];
-
- protected $blueprintFiles = [];
- protected $configFiles = [];
- protected $languageFiles = [];
protected $checksum;
- protected $timestamp;
-
- protected $configLookup;
- protected $blueprintLookup;
- protected $pluginLookup;
- protected $languagesLookup;
-
- protected $finder;
- protected $environment;
- protected $messages = [];
-
- protected $languages;
-
- public function __construct(array $setup = array(), Grav $grav = null, $environment = null)
- {
- $this->grav = $grav ?: Grav::instance();
- $this->finder = new ConfigFinder;
- $this->environment = $environment ?: 'localhost';
- $this->messages[] = 'Environment Name: ' . $this->environment;
-
- // Make sure that
- if (!isset($setup['streams']['schemes'])) {
- $setup['streams']['schemes'] = [];
- }
- $setup['streams']['schemes'] += $this->streams;
-
- $setup = $this->autoDetectEnvironmentConfig($setup);
-
- $this->setup = $setup;
- parent::__construct($setup);
-
- $this->check();
- }
+ protected $modified = false;
public function key()
{
return $this->checksum();
}
- public function reload()
+ public function checksum($checksum = null)
{
- $this->items = $this->setup;
- $this->check();
- $this->init();
- $this->debug();
-
- return $this;
- }
-
- protected function check()
- {
- $streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
- if (!is_array($streams)) {
- throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
- }
- $diff = array_keys(array_diff_key($this->streams, $streams));
- if ($diff) {
- throw new \InvalidArgumentException(
- sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
- );
- }
- }
-
- public function debug()
- {
- foreach ($this->messages as $message) {
- $this->grav['debugger']->addMessage($message);
- }
- $this->messages = [];
- }
-
- public function init()
- {
- /** @var UniformResourceLocator $locator */
- $locator = $this->grav['locator'];
-
- $this->configLookup = $locator->findResources('config://');
- $this->blueprintLookup = $locator->findResources('blueprints://config');
- $this->pluginLookup = $locator->findResources('plugins://');
-
-
- $this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master');
- $this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master');
-
- // process languages if supported
- if ($this->get('system.languages.translations', true)) {
- $this->languagesLookup = $locator->findResources('languages://');
- $this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master');
- }
-
- $this->initializeLocator($locator);
- }
-
- public function checksum()
- {
- if (empty($this->checksum)) {
- $checkBlueprints = $this->get('system.cache.check.blueprints', false);
- $checkLanguages = $this->get('system.cache.check.languages', false);
- $checkConfig = $this->get('system.cache.check.config', true);
- $checkSystem = $this->get('system.cache.check.system', true);
-
- if (!$checkBlueprints && !$checkLanguages && !$checkConfig && !$checkSystem) {
- $this->messages[] = 'Skip configuration timestamp check.';
- return false;
- }
-
- // Generate checksum according to the configuration settings.
- if (!$checkConfig) {
- // Just check changes in system.yaml files and ignore all the other files.
- $cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
- } else {
- // Check changes in all configuration files.
- $cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
- }
-
- if ($checkBlueprints) {
- $cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
- } else {
- $cb = [];
- }
-
- if ($checkLanguages) {
- $cl = $this->finder->locateLanguageFiles($this->languagesLookup, $this->pluginLookup);
- } else {
- $cl = [];
- }
-
- $this->checksum = md5(json_encode([$cc, $cb, $cl]));
+ if ($checksum !== null) {
+ $this->checksum = $checksum;
}
return $this->checksum;
}
- protected function autoDetectEnvironmentConfig($items)
+ public function modified($modified = null)
{
- $environment = $this->environment;
- $env_stream = 'user://'.$environment.'/config';
-
- if (file_exists(USER_DIR.$environment.'/config')) {
- array_unshift($items['streams']['schemes']['config']['prefixes'][''], $env_stream);
+ if ($modified !== null) {
+ $this->modified = $modified;
}
- return $items;
+ return $this->modified;
}
- protected function loadCompiledBlueprints($blueprints, $plugins, $filename = null)
- {
- $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)
+ public function reload()
{
- $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;
- }
+ $grav = Grav::instance();
- $configFiles = $this->finder->locateConfigFiles($configs, $plugins);
+ // Load new configuration.
+ $config = ConfigServiceProvider::load($grav);
- // Attempt to lock the file for writing.
- $file->lock(false);
+ /** @var Debugger $debugger */
+ $debugger = $grav['debugger'];
- // Load configuration.
- foreach ($configFiles as $files) {
- $this->loadConfigFiles($files);
- }
- $cache = [
- '@class' => $class,
- 'timestamp' => time(),
- 'checksum' => $checksum,
- 'data' => $this->toArray()
- ];
+ if ($config->modified()) {
+ // Update current configuration.
+ $this->items = $config->toArray();
+ $this->checksum($config->checksum());
+ $this->modified(true);
- // 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();
+ $debugger->addMessage('Configuration was changed and saved.');
}
- $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']);
- }
+ return $this;
}
- /**
- * Load blueprints.
- *
- * @param array $files
- */
- public function loadBlueprintFiles(array $files)
+ public function debug()
{
- foreach ($files as $name => $item) {
- $file = CompiledYamlFile::instance($item['file']);
- $this->blueprints->embed($name, $file->content(), '/');
- }
- }
+ /** @var Debugger $debugger */
+ $debugger = Grav::instance()['debugger'];
- /**
- * 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(), '/');
+ $debugger->addMessage('Environment Name: ' . $this->environment);
+ if ($this->modified()) {
+ $debugger->addMessage('Configuration reloaded and cached.');
}
}
- /**
- * Initialize resource locator by using the configuration.
- *
- * @param UniformResourceLocator $locator
- */
- public function initializeLocator(UniformResourceLocator $locator)
+ public function init()
{
- $locator->reset();
-
- $schemes = (array) $this->get('streams.schemes', []);
-
- foreach ($schemes as $scheme => $config) {
- if (isset($config['paths'])) {
- $locator->addPath($scheme, '', $config['paths']);
- }
- if (isset($config['prefixes'])) {
- foreach ($config['prefixes'] as $prefix => $paths) {
- $locator->addPath($scheme, $prefix, $paths);
- }
+ $setup = Grav::instance()['setup']->toArray();
+ foreach ($setup as $key => $value) {
+ if ($key === 'streams' || !is_array($value)) {
+ // Optimized as streams and simple values are fully defined in setup.
+ $this->items[$key] = $value;
+ } else {
+ $this->joinDefaults($key, $value);
}
}
}
/**
- * Get available streams and their types from the configuration.
- *
- * @return array
+ * @return mixed
+ * @deprecated
*/
- public function getStreams()
- {
- $schemes = [];
- foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
- $type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
- if ($type[0] != '\\') {
- $type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
- }
-
- $schemes[$scheme] = $type;
- }
-
- return $schemes;
- }
-
public function getLanguages()
{
- return $this->languages;
+ return Grav::instance()['languages'];
}
}
diff --git a/system/src/Grav/Common/Config/ConfigFileFinder.php b/system/src/Grav/Common/Config/ConfigFileFinder.php
new file mode 100644
index 000000000..887575a95
--- /dev/null
+++ b/system/src/Grav/Common/Config/ConfigFileFinder.php
@@ -0,0 +1,258 @@
+base = $base ? "{$base}/" : '';
+
+ return $this;
+ }
+
+ /**
+ * Return all locations for all the files with a timestamp.
+ *
+ * @param array $paths List of folders to look from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ */
+ public function locateFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1)
+ {
+ $list = [];
+ foreach ($paths as $folder) {
+ $list += $this->detectRecursive($folder, $pattern, $levels);
+ }
+ return $list;
+ }
+
+ /**
+ * Return all locations for all the files with a timestamp.
+ *
+ * @param array $paths List of folders to look from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ */
+ public function getFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1)
+ {
+ $list = [];
+ foreach ($paths as $folder) {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ $files = $this->detectRecursive($folder, $pattern, $levels);
+
+ $list += $files[trim($path, '/')];
+ }
+ return $list;
+ }
+
+ /**
+ * Return all paths for all the files with a timestamp.
+ *
+ * @param array $paths List of folders to look from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ */
+ public function listFiles(array $paths, $pattern = '|\.yaml$|', $levels = -1)
+ {
+ $list = [];
+ foreach ($paths as $folder) {
+ $list = array_merge_recursive($list, $this->detectAll($folder, $pattern, $levels));
+ }
+ return $list;
+ }
+
+ /**
+ * Find filename from a list of folders.
+ *
+ * Note: Only finds the last override.
+ *
+ * @param string $filename
+ * @param array $folders
+ * @return array
+ */
+ public function locateFileInFolder($filename, array $folders)
+ {
+ $list = [];
+ foreach ($folders as $folder) {
+ $list += $this->detectInFolder($folder, $filename);
+ }
+ return $list;
+ }
+
+ /**
+ * Find filename from a list of folders.
+ *
+ * @param array $folders
+ * @param string $filename
+ * @return array
+ */
+ public function locateInFolders(array $folders, $filename = null)
+ {
+ $list = [];
+ foreach ($folders as $folder) {
+ $path = trim(Folder::getRelativePath($folder), '/');
+ $list[$path] = $this->detectInFolder($folder, $filename);
+ }
+ return $list;
+ }
+
+ /**
+ * Return all existing locations for a single file with a timestamp.
+ *
+ * @param array $paths Filesystem paths to look up from.
+ * @param string $name Configuration file to be located.
+ * @param string $ext File extension (optional, defaults to .yaml).
+ * @return array
+ */
+ public function locateFile(array $paths, $name, $ext = '.yaml')
+ {
+ $filename = preg_replace('|[.\/]+|', '/', $name) . $ext;
+
+ $list = [];
+ foreach ($paths as $folder) {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ if (is_file("{$folder}/{$filename}")) {
+ $modified = filemtime("{$folder}/{$filename}");
+ } else {
+ $modified = 0;
+ }
+ $basename = $this->base . $name;
+ $list[$path] = [$basename => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
+ }
+
+ return $list;
+ }
+
+ /**
+ * Detects all directories with a configuration file and returns them with last modification time.
+ *
+ * @param string $folder Location to look up from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ * @internal
+ */
+ protected function detectRecursive($folder, $pattern, $levels)
+ {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ if (is_dir($folder)) {
+ // Find all system and user configuration files.
+ $options = [
+ 'levels' => $levels,
+ 'compare' => 'Filename',
+ 'pattern' => $pattern,
+ 'filters' => [
+ 'pre-key' => $this->base,
+ 'key' => $pattern,
+ 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
+ return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
+ }
+ ],
+ 'key' => 'SubPathname'
+ ];
+
+ $list = Folder::all($folder, $options);
+
+ ksort($list);
+ } else {
+ $list = [];
+ }
+
+ return [$path => $list];
+ }
+
+ /**
+ * Detects all directories with the lookup file and returns them with last modification time.
+ *
+ * @param string $folder Location to look up from.
+ * @param string $lookup Filename to be located (defaults to directory name).
+ * @return array
+ * @internal
+ */
+ protected function detectInFolder($folder, $lookup = null)
+ {
+ $folder = rtrim($folder, '/');
+ $path = trim(Folder::getRelativePath($folder), '/');
+ $base = $path === $folder ? '' : ($path ? substr($folder, 0, -strlen($path)) : $folder . '/');
+
+ $list = [];
+
+ if (is_dir($folder)) {
+ $iterator = new \DirectoryIterator($folder);
+
+ /** @var \DirectoryIterator $directory */
+ foreach ($iterator as $directory) {
+ if (!$directory->isDir() || $directory->isDot()) {
+ continue;
+ }
+
+ $name = $directory->getBasename();
+ $find = ($lookup ?: $name) . '.yaml';
+ $filename = "{$path}/{$name}/{$find}";
+
+ if (file_exists($base . $filename)) {
+ $basename = $this->base . $name;
+ $list[$basename] = ['file' => $filename, 'modified' => filemtime($base . $filename)];
+ }
+ }
+ }
+
+ return $list;
+ }
+
+ /**
+ * Detects all plugins with a configuration file and returns them with last modification time.
+ *
+ * @param string $folder Location to look up from.
+ * @param string $pattern Pattern to match the file. Pattern will also be removed from the key.
+ * @param int $levels Maximum number of recursive directories.
+ * @return array
+ * @internal
+ */
+ protected function detectAll($folder, $pattern, $levels)
+ {
+ $path = trim(Folder::getRelativePath($folder), '/');
+
+ if (is_dir($folder)) {
+ // Find all system and user configuration files.
+ $options = [
+ 'levels' => $levels,
+ 'compare' => 'Filename',
+ 'pattern' => $pattern,
+ 'filters' => [
+ 'pre-key' => $this->base,
+ 'key' => $pattern,
+ 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
+ return ["{$path}/{$file->getSubPathname()}" => $file->getMTime()];
+ }
+ ],
+ 'key' => 'SubPathname'
+ ];
+
+ $list = Folder::all($folder, $options);
+
+ ksort($list);
+ } else {
+ $list = [];
+ }
+
+ return $list;
+ }
+}
diff --git a/system/src/Grav/Common/Config/ConfigFinder.php b/system/src/Grav/Common/Config/ConfigFinder.php
deleted file mode 100644
index 46069410d..000000000
--- a/system/src/Grav/Common/Config/ConfigFinder.php
+++ /dev/null
@@ -1,186 +0,0 @@
-detectInFolder($folder, 'blueprints');
- }
- foreach (array_reverse($blueprints) as $folder) {
- $list += $this->detectRecursive($folder);
- }
- return $list;
- }
-
- /**
- * Get all locations for configuration files (including plugins).
- *
- * @param array $configs
- * @param array $plugins
- * @return array
- */
- public function locateConfigFiles(array $configs, array $plugins)
- {
- $list = [];
- foreach (array_reverse($plugins) as $folder) {
- $list += $this->detectInFolder($folder);
- }
- foreach (array_reverse($configs) as $folder) {
- $list += $this->detectRecursive($folder);
- }
- return $list;
- }
-
- public function locateLanguageFiles(array $languages, array $plugins)
- {
- $list = [];
- foreach (array_reverse($plugins) as $folder) {
- $list += $this->detectLanguagesInFolder($folder, 'languages');
- }
- foreach (array_reverse($languages) as $folder) {
- $list += $this->detectRecursive($folder);
- }
- return $list;
- }
-
- /**
- * Get all locations for a single configuration file.
- *
- * @param array $folders Locations to look up from.
- * @param string $name Filename to be located.
- * @return array
- */
- public function locateConfigFile(array $folders, $name)
- {
- $filename = "{$name}.yaml";
-
- $list = [];
- foreach ($folders as $folder) {
- $path = trim(Folder::getRelativePath($folder), '/');
-
- if (is_file("{$folder}/{$filename}")) {
- $modified = filemtime("{$folder}/{$filename}");
- } else {
- $modified = 0;
- }
- $list[$path] = [$name => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
- }
-
- return $list;
- }
-
- /**
- * Detects all plugins with a configuration file and returns them with last modification time.
- *
- * @param string $folder Location to look up from.
- * @param string $lookup Filename to be located.
- * @return array
- * @internal
- */
- protected function detectInFolder($folder, $lookup = null)
- {
- $path = trim(Folder::getRelativePath($folder), '/');
-
- $list = [];
-
- if (is_dir($folder)) {
- $iterator = new \FilesystemIterator($folder);
-
- /** @var \DirectoryIterator $directory */
- foreach ($iterator as $directory) {
- if (!$directory->isDir()) {
- continue;
- }
-
- $name = $directory->getBasename();
- $find = ($lookup ?: $name) . '.yaml';
- $filename = "{$path}/{$name}/$find";
-
- if (file_exists($filename)) {
- $list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
- }
- }
- }
-
- return [$path => $list];
- }
-
- protected function detectLanguagesInFolder($folder, $lookup = null)
- {
- $path = trim(Folder::getRelativePath($folder), '/');
-
- $list = [];
-
- if (is_dir($folder)) {
- $iterator = new \FilesystemIterator($folder);
-
- /** @var \DirectoryIterator $directory */
- foreach ($iterator as $directory) {
- if (!$directory->isDir()) {
- continue;
- }
-
- $name = $directory->getBasename();
- $find = ($lookup ?: $name) . '.yaml';
- $filename = "{$path}/{$name}/$find";
-
- if (file_exists($filename)) {
- $list[$name] = ['file' => $filename, 'modified' => filemtime($filename)];
- }
- }
- }
-
- return [$path => $list];
- }
-
- /**
- * Detects all plugins with a configuration file and returns them with last modification time.
- *
- * @param string $folder Location to look up from.
- * @return array
- * @internal
- */
- protected function detectRecursive($folder)
- {
- $path = trim(Folder::getRelativePath($folder), '/');
-
- if (is_dir($folder)) {
- // Find all system and user configuration files.
- $options = [
- 'compare' => 'Filename',
- 'pattern' => '|\.yaml$|',
- 'filters' => [
- 'key' => '|\.yaml$|',
- 'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
- return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
- }
- ],
- 'key' => 'SubPathname'
- ];
-
- $list = Folder::all($folder, $options);
- } else {
- $list = [];
- }
-
- return [$path => $list];
- }
-}
diff --git a/system/src/Grav/Common/Config/Languages.php b/system/src/Grav/Common/Config/Languages.php
index c0141292e..4d92d87e9 100644
--- a/system/src/Grav/Common/Config/Languages.php
+++ b/system/src/Grav/Common/Config/Languages.php
@@ -11,6 +11,23 @@
*/
class Languages extends Data
{
+ public function checksum($checksum = null)
+ {
+ if ($checksum !== null) {
+ $this->checksum = $checksum;
+ }
+
+ return $this->checksum;
+ }
+
+ public function modified($modified = null)
+ {
+ if ($modified !== null) {
+ $this->modified = $modified;
+ }
+
+ return $this->modified;
+ }
public function reformat()
{
diff --git a/system/src/Grav/Common/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php
new file mode 100644
index 000000000..90178d6b2
--- /dev/null
+++ b/system/src/Grav/Common/Config/Setup.php
@@ -0,0 +1,249 @@
+ [
+ '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($locator);
+
+ 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;
+ }
+
+ /**
+ * @param UniformResourceLocator $locator
+ * @throws \InvalidArgumentException
+ */
+ protected function check(UniformResourceLocator $locator)
+ {
+ $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))
+ );
+ }
+
+ if (!$locator->findResource('environment://config', true)) {
+ // If environment does not have its own directory, remove it from the lookup.
+ $this->set('streams.schemes.environment.prefixes', ['config' => []]);
+ $this->initializeLocator($locator);
+ }
+
+ // Create security.yaml if it doesn't exist.
+ $filename = $locator->findResource('config://security.yaml', true, true);
+ $file = YamlFile::instance($filename);
+ if (!$file->exists()) {
+ $file->save(['salt' => Utils::generateRandomString(14)]);
+ $file->free();
+ }
+ }
+}
diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php
index 9bfc32303..8be6a8ea8 100644
--- a/system/src/Grav/Common/Data/Blueprint.php
+++ b/system/src/Grav/Common/Data/Blueprint.php
@@ -3,6 +3,8 @@
use Grav\Common\GravTrait;
use RocketTheme\Toolbox\ArrayTraits\Export;
+use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
+use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
/**
* Blueprint handles the inside logic of blueprints.
@@ -10,9 +12,9 @@
* @author RocketTheme
* @license MIT
*/
-class Blueprint
+class Blueprint implements \ArrayAccess, ExportInterface
{
- use Export, DataMutatorTrait, GravTrait;
+ use Export, NestedArrayAccessWithGetters, GravTrait;
public $name;
@@ -240,9 +242,6 @@ protected function filterArray(array $data, array $rules)
if ($rule) {
// Item has been defined in blueprints.
- if (is_array($field) && count($field) == 1 && reset($field) == '') {
- continue;
- }
$field = Validation::filter($field, $rule);
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
@@ -312,91 +311,105 @@ protected function extraArray(array $data, array $rules, $prefix)
}
/**
- * Gets all field definitions from the blueprints.
- *
- * @param array $fields
- * @param array $params
- * @param string $prefix
- * @param array $current
- * @internal
- */
- protected function parseFormFields(array &$fields, $params, $prefix, array &$current)
- {
- // Go though all the fields in current level.
- foreach ($fields as $key => &$field) {
- $current[$key] = &$field;
- // Set name from the array key.
- $field['name'] = $prefix . $key;
- $field += $params;
-
- if (isset($field['fields']) && (!isset($field['type']) || $field['type'] !== 'list')) {
- // Recursively get all the nested fields.
- $newParams = array_intersect_key($this->filter, $field);
- $this->parseFormFields($field['fields'], $newParams, $prefix, $current[$key]['fields']);
- } else if ($field['type'] !== 'ignore') {
- // Add rule.
+ * Gets all field definitions from the blueprints.
+ *
+ * @param array $fields
+ * @param array $params
+ * @param string $prefix
+ * @param array $current
+ * @internal
+ */
+ protected function parseFormFields(array &$fields, $params, $prefix, array &$current)
+ {
+ // Go though all the fields in current level.
+ foreach ($fields as $key => &$field) {
+ $current[$key] = &$field;
+ // Set name from the array key.
+ $field['name'] = $prefix . $key;
+ $field += $params;
+
+ if (isset($field['fields']) && (!isset($field['type']) || $field['type'] !== 'list')) {
+ // Recursively get all the nested fields.
+ $newParams = array_intersect_key($this->filter, $field);
+ $this->parseFormFields($field['fields'], $newParams, $prefix, $current[$key]['fields']);
+ } else if ($field['type'] !== 'ignore') {
$this->rules[$prefix . $key] = &$field;
$this->addProperty($prefix . $key);
- foreach ($field as $name => $value) {
- // Support nested blueprints.
- if ($this->context && $name == '@import') {
- $values = (array) $value;
- if (!isset($field['fields'])) {
- $field['fields'] = array();
- }
- foreach ($values as $bname) {
- $b = $this->context->get($bname);
- $field['fields'] = array_merge($field['fields'], $b->fields());
- }
- }
-
- // Support for callable data values.
- elseif (substr($name, 0, 6) == '@data-') {
- $property = substr($name, 6);
- if (is_array($value)) {
- $func = array_shift($value);
- } else {
- $func = $value;
- $value = array();
- }
- list($o, $f) = preg_split('/::/', $func);
- if (!$f && function_exists($o)) {
- $data = call_user_func_array($o, $value);
- } elseif ($f && method_exists($o, $f)) {
- $data = call_user_func_array(array($o, $f), $value);
- }
-
- // If function returns a value,
- if (isset($data)) {
- if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) {
- // Combine field and @data-field together.
- $field[$property] += $data;
- } else {
- // Or create/replace field with @data-field.
- $field[$property] = $data;
- }
- }
- }
-
- elseif (substr($name, 0, 8) == '@config-') {
- $property = substr($name, 8);
- $default = isset($field[$property]) ? $field[$property] : null;
- $config = self::getGrav()['config']->get($value, $default);
-
- if (!is_null($config)) {
- $field[$property] = $config;
- }
+ if ($field['type'] === 'list') {
+ // we loop through list to get the actual field
+ foreach($field['fields'] as $subName => &$subField) {
+ $this->parseFormField($subField);
}
+ } else {
+ $this->parseFormField($field);
}
- // Initialize predefined validation rule.
if (isset($field['validate']['rule']) && $field['type'] !== 'ignore') {
$field['validate'] += $this->getRule($field['validate']['rule']);
}
}
- }
- }
+ }
+ }
+ /**
+ * Parses individual field definition
+ *
+ * @param array $field
+ * @internal
+ */
+ protected function parseFormField(&$field) {
+ foreach ($field as $name => $value) {
+ // Support nested blueprints.
+ if ($this->context && $name == '@import') {
+ $values = (array) $value;
+ if (!isset($field['fields'])) {
+ $field['fields'] = array();
+ }
+ foreach ($values as $bname) {
+ $b = $this->context->get($bname);
+ $field['fields'] = array_merge($field['fields'], $b->fields());
+ }
+ }
+
+ // Support for callable data values.
+ elseif (substr($name, 0, 6) == '@data-') {
+ $property = substr($name, 6);
+ if (is_array($value)) {
+ $func = array_shift($value);
+ } else {
+ $func = $value;
+ $value = array();
+ }
+ list($o, $f) = preg_split('/::/', $func);
+ if (!$f && function_exists($o)) {
+ $data = call_user_func_array($o, $value);
+ } elseif ($f && method_exists($o, $f)) {
+ $data = call_user_func_array(array($o, $f), $value);
+ }
+
+ // If function returns a value,
+ if (isset($data)) {
+ if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) {
+ // Combine field and @data-field together.
+ $field[$property] += $data;
+ } else {
+ // Or create/replace field with @data-field.
+ $field[$property] = $data;
+ }
+ }
+ }
+
+ elseif (substr($name, 0, 8) == '@config-') {
+ $property = substr($name, 8);
+ $default = isset($field[$property]) ? $field[$property] : null;
+ $config = self::getGrav()['config']->get($value, $default);
+
+ if (!is_null($config)) {
+ $field[$property] = $config;
+ }
+ }
+ }
+ }
/**
* Add property to the definition.
@@ -452,7 +465,9 @@ protected function checkRequired(array $data, array $fields)
&& $field['validate']['required'] === true
&& empty($data[$name])) {
$value = isset($field['label']) ? $field['label'] : $field['name'];
- throw new \RuntimeException("Missing required field: {$value}");
+ $language = self::getGrav()['language'];
+ $message = sprintf($language->translate('FORM.MISSING_REQUIRED_FIELD', null, true) . ' %s', $value);
+ throw new \RuntimeException($message);
}
}
}
diff --git a/system/src/Grav/Common/Data/Data.php b/system/src/Grav/Common/Data/Data.php
index 6a35fbc9e..fe70efb1b 100644
--- a/system/src/Grav/Common/Data/Data.php
+++ b/system/src/Grav/Common/Data/Data.php
@@ -1,9 +1,11 @@
items = $items;
-
$this->blueprints = $blueprints;
}
@@ -57,126 +58,150 @@ public function value($name, $default = null, $separator = '.')
}
/**
- * Set default value by using dot notation for nested arrays/objects.
- *
- * @example $data->def('this.is.my.nested.variable', 'default');
+ * Join nested values together by using blueprints.
*
* @param string $name Dot separated path to the requested value.
- * @param mixed $default Default value (or null).
+ * @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
+ * @return $this
+ * @throws \RuntimeException
*/
- public function def($name, $default = null, $separator = '.')
+ public function join($name, $value, $separator = '.')
{
- $this->set($name, $this->get($name, $default, $separator), $separator);
+ $old = $this->get($name, null, $separator);
+ 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 join($name, $value, $separator = '.')
+ 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($old, $value, $name, $separator);
- } else {
- // No blueprints: replace existing top level variables with the new ones.
- $value = array_merge($old, $value);
+ if ($old !== null) {
+ $value = $this->blueprints()->mergeData($value, $old, $name, $separator);
}
$this->set($name, $value, $separator);
+
+ return $this;
}
/**
- * Join two values together by using blueprints if available.
+ * Get value from the configuration and join it with given data.
*
* @param string $name Dot separated path to the requested value.
- * @param mixed $value Value to be joined.
+ * @param array $value Value to be joined.
* @param string $separator Separator, defaults to '.'
+ * @return array
+ * @throws \RuntimeException
*/
- public function joinDefaults($name, $value, $separator = '.')
+ 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) {
- // 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);
+ // No value set; no need to join data.
+ return $value;
}
- $this->set($name, $value, $separator);
+ if (!is_array($old)) {
+ throw new \RuntimeException('Value ' . $old);
+ }
+
+ // Return joined data.
+ return $this->blueprints()->mergeData($old, $value, $name, $separator);
}
/**
- * Merge two sets of data together.
+ * Merge two configurations together.
*
* @param array $data
- * @return void
+ * @return $this
*/
public function merge(array $data)
{
- if ($this->blueprints) {
- $this->items = $this->blueprints->mergeData($this->items, $data);
- } else {
- $this->items = array_merge($this->items, $data);
- }
+ $this->items = $this->blueprints()->mergeData($this->items, $data);
+
+ return $this;
}
/**
- * Add default data to the set.
+ * Set default values to the configuration if variables were not set.
*
* @param array $data
- * @return void
+ * @return $this
*/
public function setDefaults(array $data)
{
- if ($this->blueprints) {
- $this->items = $this->blueprints->mergeData($data, $this->items);
- } else {
- $this->items = array_merge($data, $this->items);
- }
- }
+ $this->items = $this->blueprints()->mergeData($data, $this->items);
- /**
- * Return blueprints.
- *
- * @return Blueprint
- */
- public function blueprints()
- {
- return $this->blueprints;
+ return $this;
}
/**
* Validate by blueprints.
*
+ * @return $this
* @throws \Exception
*/
public function validate()
{
- if ($this->blueprints) {
- $this->blueprints->validate($this->items);
- }
+ $this->blueprints()->validate($this->items);
+
+ return $this;
}
/**
+ * @return $this
* Filter all items by using blueprints.
*/
public function filter()
{
- if ($this->blueprints) {
- $this->items = $this->blueprints->filter($this->items);
- }
+ $this->items = $this->blueprints()->filter($this->items);
+
+ return $this;
}
/**
@@ -186,7 +211,24 @@ public function filter()
*/
public function extra()
{
- return $this->blueprints ? $this->blueprints->extra($this->items) : array();
+ return $this->blueprints()->extra($this->items);
+ }
+
+ /**
+ * Return blueprints.
+ *
+ * @return Blueprints
+ */
+ public function blueprints()
+ {
+ if (!$this->blueprints){
+ $this->blueprints = new Blueprints;
+ } elseif (is_callable($this->blueprints)) {
+ // Lazy load blueprints.
+ $blueprints = $this->blueprints;
+ $this->blueprints = $blueprints();
+ }
+ return $this->blueprints;
}
/**
diff --git a/system/src/Grav/Common/Data/DataMutatorTrait.php b/system/src/Grav/Common/Data/DataMutatorTrait.php
deleted file mode 100644
index 707b5078a..000000000
--- a/system/src/Grav/Common/Data/DataMutatorTrait.php
+++ /dev/null
@@ -1,68 +0,0 @@
-get('this.is.my.nested.variable');
- *
- * @param string $name Dot separated path to the requested value.
- * @param mixed $default Default value (or null).
- * @param string $separator Separator, defaults to '.'
- * @return mixed Value.
- */
- public function get($name, $default = null, $separator = '.')
- {
- $path = explode($separator, $name);
- $current = $this->items;
- foreach ($path as $field) {
- if (is_object($current) && isset($current->{$field})) {
- $current = $current->{$field};
- } elseif (is_array($current) && isset($current[$field])) {
- $current = $current[$field];
- } else {
- return $default;
- }
- }
-
- return $current;
- }
-
- /**
- * Set value by using dot notation for nested arrays/objects.
- *
- * @example $value = $data->set('this.is.my.nested.variable', true);
- *
- * @param string $name Dot separated path to the requested value.
- * @param mixed $value New value.
- * @param string $separator Separator, defaults to '.'
- */
- public function set($name, $value, $separator = '.')
- {
- $path = explode($separator, $name);
- $current = &$this->items;
- foreach ($path as $field) {
- if (is_object($current)) {
- // Handle objects.
- if (!isset($current->{$field})) {
- $current->{$field} = array();
- }
- $current = &$current->{$field};
- } else {
- // Handle arrays and scalars.
- if (!is_array($current)) {
- $current = array($field => array());
- } elseif (!isset($current[$field])) {
- $current[$field] = array();
- }
- $current = &$current[$field];
- }
- }
-
- $current = $value;
- }
-
-}
diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php
index c232467ba..9cebd0d76 100644
--- a/system/src/Grav/Common/Data/Validation.php
+++ b/system/src/Grav/Common/Data/Validation.php
@@ -31,6 +31,11 @@ public static function validate($value, array $field)
return;
}
+ // special case for files, value is never empty and errors with code 4 instead
+ if (empty($validate['required']) && $field['type'] == 'file' && (isset($value['error']) && ($value['error'] == UPLOAD_ERR_NO_FILE) || in_array(UPLOAD_ERR_NO_FILE, $value['error']))) {
+ return;
+ }
+
// Get language class
$language = self::getGrav()['language'];
@@ -78,6 +83,11 @@ public static function filter($value, array $field)
return null;
}
+ // special case for files, value is never empty and errors with code 4 instead
+ if (empty($validate['required']) && $field['type'] == 'file' && (isset($value['error']) && ($value['error'] == UPLOAD_ERR_NO_FILE) || in_array(UPLOAD_ERR_NO_FILE, $value['error']))) {
+ return null;
+ }
+
// if this is a YAML field, simply parse it and return the value
if (isset($field['yaml']) && $field['yaml'] === true) {
try {
@@ -258,6 +268,24 @@ public static function typeToggle($value, array $params, array $field)
return self::typeArray((array) $value, $params, $field);
}
+ /**
+ * Custom input: file
+ *
+ * @param mixed $value Value to be validated.
+ * @param array $params Validation parameters.
+ * @param array $field Blueprint for the field.
+ * @return bool True if validation succeeded.
+ */
+ public static function typeFile($value, array $params, array $field)
+ {
+ return self::typeArray((array) $value, $params, $field);
+ }
+
+ protected static function filterFile($value, array $params, array $field)
+ {
+ return (array) $value;
+ }
+
/**
* HTML5 input: select
*
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 @@ public function content($var = null)
|| $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 @@ public function content($var = null)
$file->unlock();
}
}
+ $file->free();
$this->content = $cache['data'];
}
diff --git a/system/src/Grav/Common/Filesystem/Folder.php b/system/src/Grav/Common/Filesystem/Folder.php
index af1b93352..62d610b65 100644
--- a/system/src/Grav/Common/Filesystem/Folder.php
+++ b/system/src/Grav/Common/Filesystem/Folder.php
@@ -19,12 +19,12 @@ public static function lastModifiedFolder($path)
{
$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 @@ public static function lastModifiedFile($path, $extensions = 'md|yaml')
{
$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 @@ public static function lastModifiedFile($path, $extensions = 'md|yaml')
/**
* 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 @@ public static function getRelativePath($path, $base = GRAV_ROOT)
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 @@ public static function shift(&$path)
return $result ?: null;
}
-
-
/**
* Return recursive list of all files and directories under given path.
*
@@ -116,13 +151,17 @@ public static function all($path, array $params = array())
$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 @@ public static function all($path, array $params = array())
/** @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 @@ public static function all($path, array $params = array())
$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 @@ public static function all($path, array $params = array())
$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 @@ public static function all($path, array $params = array())
/**
* 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 @@ public static function copy($source, $target)
}
// 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 @@ public static function copy($source, $target)
/**
* 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 @@ public static function move($source, $target)
}
// 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 @@ public static function move($source, $target)
* 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 @@ public static function delete($target)
}
// 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 @@ public static function rcopy($src, $dest)
/**
* @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 @@ protected static function doDelete($folder)
/** @var \DirectoryIterator $fileinfo */
foreach ($files as $fileinfo) {
if ($fileinfo->isDir()) {
- if (false === rmdir($fileinfo->getRealPath())) {
+ if (false === @rmdir($fileinfo->getRealPath())) {
return false;
}
} else {
- if (false === unlink($fileinfo->getRealPath())) {
+ if (false === @unlink($fileinfo->getRealPath())) {
return false;
}
}
}
- return rmdir($folder);
+ return $include_target ? @rmdir($folder) : true;
}
}
diff --git a/system/src/Grav/Common/GPM/Response.php b/system/src/Grav/Common/GPM/Response.php
index f933f37f0..fcaed82b3 100644
--- a/system/src/Grav/Common/GPM/Response.php
+++ b/system/src/Grav/Common/GPM/Response.php
@@ -78,10 +78,12 @@ public static function get($uri = '', $options = [], $callback = null)
throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available');
}
- // disable time limit if possible to help with slow downloads
- if (!Utils::isFunctionDisabled('set_time_limit') && !ini_get('safe_mode')) {
- set_time_limit(0);
- }
+ // check if this function is available, if so use it to stop any timeouts
+ try {
+ if (!Utils::isFunctionDisabled('set_time_limit') && !ini_get('safe_mode') && function_exists('set_time_limit')) {
+ set_time_limit(0);
+ }
+ } catch (\Exception $e) {}
$options = array_replace_recursive(self::$defaults, $options);
$method = 'get' . ucfirst(strtolower(self::$method));
diff --git a/system/src/Grav/Common/Language/LanguageCodes.php b/system/src/Grav/Common/Language/LanguageCodes.php
index 2683c8b0a..86b130a90 100644
--- a/system/src/Grav/Common/Language/LanguageCodes.php
+++ b/system/src/Grav/Common/Language/LanguageCodes.php
@@ -193,7 +193,7 @@ class LanguageCodes
],
"fr" => [
"name" => "French",
- "nativeName" => "français"
+ "nativeName" => "Français"
],
"ff" => [
"name" => "Fula",
diff --git a/system/src/Grav/Common/Page/Collection.php b/system/src/Grav/Common/Page/Collection.php
index b5836bd45..cc1593c28 100644
--- a/system/src/Grav/Common/Page/Collection.php
+++ b/system/src/Grav/Common/Page/Collection.php
@@ -453,6 +453,55 @@ public function ofOneOfTheseTypes($types)
return $this;
}
+ /**
+ * Creates new collection with only pages of one of the specified access levels
+ *
+ * @return Collection The collection
+ */
+ public function ofOneOfTheseAccessLevels($accessLevels)
+ {
+ $items = [];
+
+ foreach ($this->items as $path => $slug) {
+ $page = $this->pages->get($path);
+
+ if ($page !== null && isset($page->header()->access)) {
+ if (is_array($page->header()->access)) {
+ //Multiple values for access
+ $valid = false;
+
+ foreach ($page->header()->access as $index => $accessLevel) {
+ if (is_array($accessLevel)) {
+ foreach($accessLevel as $innerIndex => $innerAccessLevel) {
+ if (in_array($innerAccessLevel, $accessLevels)) {
+ $valid = true;
+ }
+ }
+ } else {
+ if (in_array($index, $accessLevels)) {
+ $valid = true;
+ }
+ }
+ }
+ if ($valid) {
+ $items[$path] = $slug;
+ }
+ } else {
+ //Single value for access
+ if (in_array($page->header()->access, $accessLevels)) {
+ $items[$path] = $slug;
+ }
+ }
+
+ }
+ }
+
+ $this->items = $items;
+ return $this;
+ }
+
+
+
}
diff --git a/system/src/Grav/Common/Page/Medium/Medium.php b/system/src/Grav/Common/Page/Medium/Medium.php
index b36864c27..a5e4001a7 100644
--- a/system/src/Grav/Common/Page/Medium/Medium.php
+++ b/system/src/Grav/Common/Page/Medium/Medium.php
@@ -214,8 +214,9 @@ public function parsedownElement($title = null, $alt = null, $class = null, $res
else
$style .= $key . ': ' . $value . ';';
}
- if ($style)
+ if ($style) {
$attributes['style'] = $style;
+ }
if (empty($attributes['title'])) {
if (!empty($title)) {
@@ -392,6 +393,38 @@ public function lightbox($width = null, $height = null, $reset = true)
return $this->link($reset, $attributes);
}
+ /**
+ * Add a class to the element from Markdown or Twig
+ * Example: ![Example](myimg.png?classes=float-left) or ![Example](myimg.png?classes=myclass1,myclass2)
+ *
+ * @return $this
+ */
+ public function classes()
+ {
+ $classes = func_get_args();
+ if (!empty($classes)) {
+ $this->attributes['class'] = implode(',', (array)$classes);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add an id to the element from Markdown or Twig
+ * Example: ![Example](myimg.png?id=primary-img)
+ *
+ * @param $id
+ * @return $this
+ */
+ public function id($id)
+ {
+ if (is_string($id)) {
+ $this->attributes['id'] = trim($id);
+ }
+
+ return $this;
+ }
+
/**
* Allows to add an inline style attribute from Markdown or Twig
* Example: ![Example](myimg.png?style=float:left)
diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php
index 00a0f756f..50b3fa83f 100644
--- a/system/src/Grav/Common/Page/Page.php
+++ b/system/src/Grav/Common/Page/Page.php
@@ -2002,16 +2002,16 @@ protected function evaluate($value)
case 'children':
$results = $this->children()->nonModular();
break;
-
+ case 'all':
+ $results = $this->children();
+ break;
case 'parent':
$collection = new Collection();
$results = $collection->addPage($this->parent());
break;
-
case 'siblings':
$results = $this->parent()->children()->remove($this->path());
break;
-
case 'descendants':
$results = $pages->all($this)->remove($this->path())->nonModular();
break;
diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php
index 53790bac6..efe7ef1c0 100644
--- a/system/src/Grav/Common/Page/Pages.php
+++ b/system/src/Grav/Common/Page/Pages.php
@@ -486,6 +486,36 @@ public static function pageTypes()
return static::types();
}
+ /**
+ * Get access levels of the site pages
+ *
+ * @return array
+ */
+ public function accessLevels()
+ {
+ $accessLevels = [];
+ foreach($this->all() as $page) {
+ if (isset($page->header()->access)) {
+ if (is_array($page->header()->access)) {
+ foreach($page->header()->access as $index => $accessLevel) {
+ if (is_array($accessLevel)) {
+ foreach($accessLevel as $innerIndex => $innerAccessLevel) {
+ array_push($accessLevels, $innerIndex);
+ }
+ } else {
+ array_push($accessLevels, $index);
+ }
+ }
+ } else {
+
+ array_push($accessLevels, $page->header()->access);
+ }
+ }
+ }
+
+ return array_unique($accessLevels);
+ }
+
/**
* Get available parents.
*
@@ -498,7 +528,22 @@ public static function parents()
/** @var Pages $pages */
$pages = $grav['pages'];
- return $pages->getList();
+ $parents = $pages->getList();
+
+ /** @var Admin $admin */
+ $admin = $grav['admin'];
+
+ // Remove current route from parents
+ if (isset($admin)) {
+ $page = $admin->getPage($admin->route);
+ $page_route = $page->route();
+ if (isset($parents[$page_route])) {
+ unset($parents[$page_route]);
+ }
+
+ }
+
+ return $parents;
}
/**
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 @@
init();
+ };
- // Pre-load setup.php as it contains our initial configuration.
- $file = GRAV_ROOT . '/setup.php';
- $this->setup = is_file($file) ? (array) include $file : [];
- $this->environment = isset($this->setup['environment']) ? $this->setup['environment'] : null;
+ $container['blueprints'] = function ($c) {
+ return static::blueprints($c);
+ };
- $container['blueprints'] = function ($c) use ($self) {
- return $self->loadMasterBlueprints($c);
+ $container['config'] = function ($c) {
+ return static::load($c);
};
- $container['config'] = function ($c) use ($self) {
- return $self->loadMasterConfig($c);
+ $container['languages'] = function ($c) {
+ return static::languages($c);
};
}
- public function loadMasterConfig(Container $container)
+ public static function setup(Container $container)
{
- $environment = $this->getEnvironment($container);
+ return new Setup($container['uri']->environment());
+ }
+
+ public static function blueprints(Container $container)
+ {
+ /** Setup $setup */
+ $setup = $container['setup'];
+
+ /** @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');
- $config = new Config($this->setup, $container, $environment);
+ $blueprints = new CompiledBlueprints($cache, $files, GRAV_ROOT);
- return $config;
+ return $blueprints->name("master-{$setup->environment}")->load();
}
- public function loadMasterBlueprints(Container $container)
+ public static function load(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/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 function getEnvironment(Container $container)
+ public static function languages(Container $container)
{
- if (!isset($this->environment)) {
- $this->environment = $container['uri']->environment();
+ /** 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 @@ public function register(Container $container)
Stream::setLocator($locator);
ReadOnlyStream::setLocator($locator);
- return new StreamBuilder($config->getStreams($c));
+ return new StreamBuilder($setup->getStreams($c));
};
}
}
diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php
index 4ea09e350..12a1cb633 100644
--- a/system/src/Grav/Common/Twig/TwigExtension.php
+++ b/system/src/Grav/Common/Twig/TwigExtension.php
@@ -355,6 +355,12 @@ public function nicetimeFilter($date, $long_strings = true)
$periods[$j] .= '_PLURAL';
}
+ if ($this->grav['language']->getTranslation($this->grav['language']->getLanguage(), $periods[$j] . '_MORE_THAN_TWO')) {
+ if ($difference > 2) {
+ $periods[$j] .= '_MORE_THAN_TWO';
+ }
+ }
+
$periods[$j] = $this->grav['language']->translate($periods[$j], null, true);
return "$difference $periods[$j] {$tense}";
diff --git a/system/src/Grav/Common/User/Group.php b/system/src/Grav/Common/User/Group.php
new file mode 100644
index 000000000..965fde5f6
--- /dev/null
+++ b/system/src/Grav/Common/User/Group.php
@@ -0,0 +1,127 @@
+get('groups');
+ return $groups;
+ }
+
+ /**
+ * Checks if a group exists
+ *
+ * @return object
+ */
+ public static function group_exists($groupname)
+ {
+ return isset(self::groups()[$groupname]);
+ }
+
+ /**
+ * Get a group by name
+ *
+ * @return object
+ */
+ public static function load($groupname)
+ {
+ if (self::group_exists($groupname)) {
+ $content = self::groups()[$groupname];
+ } else {
+ $content = [];
+ }
+
+ $blueprints = new Blueprints('blueprints://');
+ $blueprint = $blueprints->get('user/group');
+ if (!isset($content['groupname'])) {
+ $content['groupname'] = $groupname;
+ }
+ $group = new Group($content, $blueprint);
+
+ return $group;
+ }
+
+ /**
+ * Save a group
+ */
+ public function save()
+ {
+ $blueprints = new Blueprints('blueprints://');
+ $blueprint = $blueprints->get('user/group');
+
+ $fields = $blueprint->fields();
+
+ self::getGrav()['config']->set("groups.$this->groupname", []);
+
+ foreach($fields as $field) {
+ if ($field['type'] == 'text') {
+ $value = $field['name'];
+ if (isset($this->items[$value])) {
+ self::getGrav()['config']->set("groups.$this->groupname.$value", $this->items[$value]);
+ }
+ }
+ if ($field['type'] == 'array') {
+ $value = $field['name'];
+ $arrayValues = Utils::resolve($this->items, $field['name']);
+
+ if ($arrayValues) foreach($arrayValues as $arrayIndex => $arrayValue) {
+ self::getGrav()['config']->set("groups.$this->groupname.$value.$arrayIndex", $arrayValue);
+ }
+ }
+ }
+
+ $type = 'groups';
+ $blueprints = $this->blueprints("config/{$type}");
+ $obj = new Data(self::getGrav()['config']->get($type), $blueprints);
+ $file = CompiledYamlFile::instance(self::getGrav()['locator']->findResource("config://{$type}.yaml"));
+ $obj->file($file);
+ $obj->save();
+ }
+
+ /**
+ * Remove a group
+ *
+ * @param string $username
+ * @return bool True if the action was performed
+ */
+ public static function remove($groupname)
+ {
+ $blueprints = new Blueprints('blueprints://');
+ $blueprint = $blueprints->get('user/group');
+
+ $groups = self::getGrav()['config']->get("groups");
+ unset($groups[$groupname]);
+ self::getGrav()['config']->set("groups", $groups);
+
+ $type = 'groups';
+ $obj = new Data(self::getGrav()['config']->get($type), $blueprint);
+ $file = CompiledYamlFile::instance(self::getGrav()['locator']->findResource("config://{$type}.yaml"));
+ $obj->file($file);
+ $obj->save();
+
+ return true;
+ }
+}
diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php
index 6282041a2..9caad3741 100644
--- a/system/src/Grav/Common/User/User.php
+++ b/system/src/Grav/Common/User/User.php
@@ -53,7 +53,7 @@ public static function load($username)
* Remove user account.
*
* @param string $username
- * @return bool True is the action was performed
+ * @return bool True if the action was performed
*/
public static function remove($username)
{
@@ -83,7 +83,10 @@ public function authenticate($password)
// 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
@@ -146,7 +149,28 @@ public function authorize($action)
return false;
}
- return Utils::isPositive($this->get("access.{$action}"));
+ $return = false;
+
+ //Check group access level
+ $groups = $this->get('groups');
+ if ($groups) foreach($groups as $group) {
+ $permission = self::getGrav()['config']->get("groups.{$group}.access.{$action}");
+ if (Utils::isPositive($permission)) {
+ $return = true;
+ }
+ }
+
+ //Check user access level
+ if (!$this->get('access')) {
+ return false;
+ }
+
+ if (Utils::resolve($this->get('access'), $action) !== null) {
+ $permission = $this->get("access.{$action}");
+ $return = Utils::isPositive($permission);
+ }
+
+ return $return;
}
/**
diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php
index 515d05851..815959927 100644
--- a/system/src/Grav/Common/Utils.php
+++ b/system/src/Grav/Common/Utils.php
@@ -15,6 +15,8 @@ abstract class Utils
{
use GravTrait;
+ protected static $nonces = [];
+
/**
* @param string $haystack
* @param string $needle
@@ -217,9 +219,11 @@ public static function download($file, $force_download = true)
$filesize = filesize($file);
// check if this function is available, if so use it to stop any timeouts
- if (function_exists('set_time_limit')) {
- set_time_limit(0);
- }
+ try {
+ if (!Utils::isFunctionDisabled('set_time_limit') && !ini_get('safe_mode') && function_exists('set_time_limit')) {
+ set_time_limit(0);
+ }
+ } catch (\Exception $e) {}
ignore_user_abort(false);
@@ -409,6 +413,25 @@ public static function date2timestamp($date)
}
}
+ /**
+ * Get value of an array using dot notation
+ */
+ public static function resolve(array $array, $path, $default = null)
+ {
+ $current = $array;
+ $p = strtok($path, '.');
+
+ while ($p !== false) {
+ if (!isset($current[$p])) {
+ return $default;
+ }
+ $current = $current[$p];
+ $p = strtok('.');
+ }
+
+ return $current;
+ }
+
/**
* Checks if a value is positive
*
@@ -421,7 +444,6 @@ public static function isPositive($value)
return in_array($value, [true, 1, '1', 'yes', 'on', 'true'], true);
}
-
/**
* Generates a nonce string to be hashed. Called by self::getNonce()
*
@@ -435,11 +457,10 @@ private static function generateNonceString($action, $plusOneTick = false)
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'] : '';
}
@@ -450,7 +471,7 @@ private static function generateNonceString($action, $plusOneTick = false)
$i++;
}
- return ( $i . '|' . $action . '|' . $username . '|' . $token );
+ return ( $i . '|' . $action . '|' . $username . '|' . $token . '|' . self::getGrav()['config']->get('security.salt'));
}
/**
@@ -467,19 +488,6 @@ private static function nonceTick()
return (int)ceil(time() / ( $secondsInHalfADay ));
}
- /**
- * Get hash of given string
- *
- * @param string $data string to hash
- *
- * @return string hashed value of $data, cut to 10 characters
- */
- private static function hash($data)
- {
- $hash = password_hash($data, PASSWORD_DEFAULT);
- return $hash;
- }
-
/**
* Creates a hashed nonce tied to the passed action. Tied to the current user and time. The nonce for a given
* action is the same for 12 hours.
@@ -491,9 +499,14 @@ private static function hash($data)
*/
public static function getNonce($action, $plusOneTick = false)
{
- $nonce = self::hash(self::generateNonceString($action, $plusOneTick));
- $nonce = str_replace('/', 'SLASH', $nonce);
- return $nonce;
+ // Don't regenerate this again if not needed
+ if (isset(static::$nonces[$action])) {
+ return static::$nonces[$action];
+ }
+ $nonce = md5(self::generateNonceString($action, $plusOneTick));
+ static::$nonces[$action] = $nonce;
+
+ return static::$nonces[$action];
}
/**
@@ -506,21 +519,18 @@ public static function getNonce($action, $plusOneTick = false)
*/
public static function verifyNonce($nonce, $action)
{
- $nonce = str_replace('SLASH', '/', $nonce);
-
//Nonce generated 0-12 hours ago
- if (password_verify(self::generateNonceString($action), $nonce)) {
+ if ($nonce == self::getNonce($action)) {
return true;
}
//Nonce generated 12-24 hours ago
$plusOneTick = true;
- if (password_verify(self::generateNonceString($action, $plusOneTick), $nonce)) {
+ if ($nonce == self::getNonce($action, $plusOneTick)) {
return true;
}
//Invalid nonce
return false;
}
-
}
diff --git a/web.config b/web.config
index c3dd31424..e09e94fb5 100644
--- a/web.config
+++ b/web.config
@@ -46,7 +46,7 @@
-
+