Compare commits

..

169 Commits

Author SHA1 Message Date
Andy Miller
e4e0c06ea8 Merge branch 'release/1.0.0' 2015-12-11 14:00:21 -07:00
Andy Miller
0c807b6108 version update 2015-12-11 13:59:52 -07:00
Andy Miller
3a47d6a580 force lowercase of username 2015-12-11 10:34:59 -07:00
Andy Miller
52947b3a2c Added Image cache perms option 2015-12-10 20:51:40 -07:00
Andy Miller
eacfc798f4 wrap security section of account blueprints in an authorize check 2015-12-10 16:05:22 -07:00
Andy Miller
5c0e5f3c01 updated debugbar 2015-12-10 16:04:46 -07:00
Andy Miller
985141b842 Merge pull request #515 from lennerd/fix/medium-url
Escape graph root in page medium to work with special characters.
2015-12-10 13:27:02 -07:00
Andy Miller
1aac2ab95d Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-12-10 12:13:01 -07:00
Andy Miller
aa7d5ddf59 translate validation messages 2015-12-10 12:12:56 -07:00
Flavio Copes
e87505378d Fix a parentheses issue that changes the logic and throw an error if $value['error'] is not set 2015-12-10 19:19:32 +01:00
Matias Griese
53f097c2b3 Fix wrong ordering when moving bulk of pages 2015-12-10 11:25:14 +02:00
Matias Griese
6ea7fe9dba Fix moving page if its just placeholder 2015-12-10 11:19:56 +02:00
Matias Griese
0206f1b0c7 Fix Page::copy() and Page::move() to support multiple moves at once 2015-12-10 10:46:09 +02:00
Matias Griese
ef9f2c77dd Minor fix in Page class 2015-12-10 10:24:21 +02:00
Andy Miller
822292f541 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-12-09 10:13:53 -07:00
Andy Miller
ec08cd9374 Better fix for GPM issues 2015-12-09 10:13:44 -07:00
Flavio Copes
8fb4063cf2 Merge pull request #511 from getgrav/feature/fix-multisite-subfolder
Make $container available in setup.php
2015-12-09 17:25:31 +01:00
Lennart Hildebrandt
8a2b444c48 Escape graph root in page medium to work with special characters. 2015-12-09 12:30:08 +01:00
Andy Miller
9c6f243902 Fix for GPM problems "Call to a member function set() on null" 2015-12-08 21:20:41 -07:00
Andy Miller
d5bd99b363 Fix for individual asset pipeline value not functioning + optimizations #513 2015-12-08 11:42:39 -07:00
Matias Griese
5554c07cbf Merge pull request #508 from getgrav/feature/gantry
Add support to manually initialize theme
2015-12-08 10:12:00 +02:00
Andy Miller
acd95aac6f Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-12-07 16:46:37 -07:00
Andy Miller
8ad5f2624d Added iconv polyfill library 2015-12-07 16:46:28 -07:00
Andy Miller
8adc0e1c17 Merge pull request #510 from getgrav/feature/fix-translation
Fix issue when `translations_fallback` is turned off, the translations are always relative to the default language even if the active language is != default
2015-12-07 15:29:56 -07:00
Flavio Copes
fab66cf3a5 Make $container available in setup.php 2015-12-07 19:20:10 +01:00
Flavio Copes
5df48e7a68 Fix issue when translations_fallback is turned off, the translations are always relative to the default language even if the active language is != default 2015-12-07 16:25:46 +01:00
Djamil Legato
079a8c5728 Fixed LICENSE link 2015-12-06 13:24:27 -08:00
Flavio Copes
d9196426a1 Hide the session setting in admin as turning it off crashes the admin. 2015-12-06 18:20:16 +01:00
Flavio Copes
2f686f5b74 Improve a couple page method docs 2015-12-06 18:19:21 +01:00
Djamil Legato
c4b5df20a9 🎈 Added many more gpm index options (--filter, --sort, --updates-only, --plugins-only, --themes-only, --desc) 2015-12-04 17:54:14 -08:00
Djamil Legato
06608a6d3c Implemented sort method for Iterator ($iterator->sort($callback)) 2015-12-04 17:39:49 -08:00
Andy Miller
fd63911faf Useful cache info output 2015-12-04 15:50:04 -07:00
Flavio Copes
66a1e55867 Update pages blueprints to add the page preview in the markdown editor with the current admin changes 2015-12-04 23:12:36 +01:00
Andy Miller
af2eb2e75d Reverted PHP7 apcu as it seems to kill Doctrine Cache 2015-12-04 15:02:26 -07:00
Andy Miller
9b4f32cafd Merge branch 'develop' into feature/gantry 2015-12-03 22:15:17 -07:00
Andy Miller
9179fbd1a2 minor performance optimization 2015-12-03 22:12:01 -07:00
Andy Miller
24ea7f1f55 Support APCu extension loaded for PHP7.0 2015-12-03 19:05:53 -07:00
Andy Miller
9b95053110 Support APCu extension loaded for PHP7.0 2015-12-03 19:05:36 -07:00
Andy Miller
71ffb9c72f Merge branch 'develop' into feature/gantry 2015-12-03 18:06:30 -07:00
Andy Miller
f29f698f61 setter method to allow explicit setting of asset timestamp 2015-12-03 16:56:30 -07:00
Andy Miller
f1f8579a0b New setters to set state of CSS / JS pipelining 2015-12-03 14:48:34 -07:00
Andy Miller
4ca8fab750 removed unused Plugin and Theme initialization 2015-12-03 14:48:15 -07:00
Andy Miller
b80ed731b0 Made Page.evaluate() public so it can be used by twig, plugins etc. 2015-12-03 13:44:25 -07:00
Andy Miller
23a9a73600 Added logic to support link attributes via query string 2015-12-03 12:16:16 -07:00
Djamil Legato
dc8c0b6522 Fixed changelog differ to take into account betas/rc versions (fixes #496) 2015-12-02 23:44:18 -08:00
Andy Miller
e695b1942c .gitignore for accounts - https://github.com/getgrav/grav-plugin-login/issues/16 2015-12-02 15:38:05 -07:00
Andy Miller
3f543e7e84 Merge branch 'feature/gantry' of https://github.com/getgrav/grav into feature/gantry 2015-12-02 15:36:22 -07:00
Andy Miller
7f6f9e82e3 .gitignore for accounts - https://github.com/getgrav/grav-plugin-login/issues/16 2015-12-02 15:36:16 -07:00
Djamil Legato
51529eb0ce Merge branch 'develop' into feature/gantry 2015-12-02 14:10:17 -08:00
Andy Miller
05b24a4b75 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-12-02 12:39:01 -07:00
Andy Miller
ca5819489f Fix for sandbox command 2015-12-02 12:38:56 -07:00
Matias Griese
1d2acf8096 Move onThemeInitialized event into Themes::initTheme() 2015-12-02 17:24:12 +02:00
Andy Miller
98278e965b Merge pull request #495 from tcsizmadia/develop
Updated Hungarian translation with new string.
2015-12-02 07:50:12 -07:00
Matias Griese
7bc990688c Add missing parent constructor to Themes class 2015-12-02 11:24:20 +02:00
Tamas Csizmadia
b86aa6d473 Updated Hungarian translation with new string. 2015-12-01 21:57:31 +01:00
Andy Miller
dba7347c1e Merge branch 'release/1.0.0-rc.6' 2015-12-01 13:13:16 -07:00
Andy Miller
fa52e18e3f Merge branch 'release/1.0.0-rc.6' into develop 2015-12-01 13:13:16 -07:00
Andy Miller
b605753a6d version update 2015-12-01 13:12:59 -07:00
Andy Miller
0af4fb351c changelog updated 2015-12-01 12:47:10 -07:00
Andy Miller
90edf95077 renamed LICENSE to LICENSE.txt 2015-12-01 11:15:00 -07:00
Andy Miller
ab3843442a Merge pull request #494 from getgrav/feature/introduce-user-groups
Feature/introduce user groups
2015-12-01 10:03:30 -07:00
Flavio Copes
023b9dd708 Merge remote-tracking branch 'origin/feature/introduce-user-groups' into feature/introduce-user-groups 2015-12-01 17:56:56 +01:00
Flavio Copes
0ac882314e Merge branch 'develop' into feature/introduce-user-groups 2015-12-01 17:54:20 +01:00
Flavio Copes
0fe9264582 Merge pull request #491 from MATsxm/patch-2
Update fr.yaml
2015-12-01 14:05:25 +01:00
Djamil Legato
b1e16b2206 Added validator for file input fields 2015-11-30 18:22:30 -08:00
Andy Miller
a67c1780c1 Added a new @self.all case that gets all children modular and non-modular 2015-11-30 19:16:46 -07:00
Marc-Antoine Thevenet
1170f2f58d Update fr.yaml
Update following the /en file
Thanks
2015-11-30 15:30:22 -04:00
Andy Miller
199c0a08ea Should not be able to set parent to self (infinite loop) #308 2015-11-30 12:18:32 -07:00
Matias Griese
84ad152536 Only create environmental config if the directory exists 2015-11-30 20:44:08 +02:00
Flavio Copes
5b0f905ae3 Update italian lang file 2015-11-30 19:20:20 +01:00
Flavio Copes
a2bba8f09d Merge pull request #489 from hugoaf/develop
Adding language string for MISSING_REQUIRED_FIELD
2015-11-30 19:19:51 +01:00
Matias Griese
2007975428 If environment does not have its own directory, remove it from the lookup 2015-11-30 10:13:40 -07:00
Matias Griese
e484997515 If environment does not have its own directory, remove it from the lookup 2015-11-30 18:56:34 +02:00
Flavio Copes
af5c52c52f Update session name hints 2015-11-30 14:36:23 +01:00
Hugo Avila
f3d0e10378 Update Blueprint.php 2015-11-29 21:43:05 -08:00
Hugo Avila
b33ab43ff9 Update en.yaml 2015-11-29 21:39:51 -08:00
Hugo Avila
c580399db6 Update es.yaml 2015-11-29 21:33:08 -08:00
Hugo Avila
00d8717d7c Create es.yaml 2015-11-29 21:26:21 -08:00
Andy Miller
6e2f4607a6 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-11-29 20:00:21 -07:00
Andy Miller
3a7abeb18b Added support to set classes and id on a medium object 2015-11-29 20:00:16 -07:00
Andy Miller
84a5984c65 Merge pull request #487 from aqnouch/patch-2
Change the first letter of the word 'français' to uppercase
2015-11-29 17:34:38 -07:00
AQNOUCH Mohammed
1465c26b1f Change the first letter of the word 'français' to uppercase 2015-11-28 19:20:59 +00:00
Flavio Copes
b399d8e3b9 Drop check used by the array field, introduced by 57b18edb55 2015-11-27 19:27:58 +01:00
Andy Miller
b5c04bdc9b Merge pull request #484 from nazwa/web.config-patch
Web.config update
2015-11-26 18:33:27 -07:00
Andy Miller
b4725800c3 Merge pull request #482 from diomed/patch-1
Update hr.yaml
2015-11-26 18:33:14 -07:00
nazwa
2b1a102efa Web.config update
web.config now correctly allows for static files inside vendor folder, just like .htaccess
2015-11-26 22:18:27 +00:00
Kruno H
500c548af4 Update hr.yaml
added PLURAL_MORE_THAN_TWO translations
2015-11-26 22:48:02 +01:00
Flavio Copes
fc7017f822 Merge pull request #480 from getgrav/feature/fix-set-time-limit
Include set_time_limit in a try/catch
2015-11-26 17:36:58 +01:00
Flavio Copes
5b254f4cf8 Drop accidentally added method 2015-11-26 16:19:29 +01:00
Flavio Copes
43783f3ce6 Correct the set_time_limit if 2015-11-26 16:18:05 +01:00
Flavio Copes
ae39aabee1 Include set_time_limit in a try/catch 2015-11-26 16:02:06 +01:00
Flavio Copes
df7a94148b Merge branch 'develop' into feature/introduce-user-groups 2015-11-26 12:14:48 +01:00
Flavio Copes
63890661fe Add security.yaml to the ignored files 2015-11-26 11:55:13 +01:00
Flavio Copes
793ac1a1bb Revert "no message"
This reverts commit b259927348.
2015-11-26 11:54:05 +01:00
Flavio Copes
77db54c50d Revert "If the page does not exist trigger a 404"
This reverts commit 8d8420c0d6.
2015-11-26 11:53:27 +01:00
Andy Miller
b259927348 no message 2015-11-25 15:42:11 -07:00
Andy Miller
e8972a6aa5 vendor updates 2015-11-25 15:41:53 -07:00
Flavio Copes
8d8420c0d6 If the page does not exist trigger a 404 2015-11-25 22:30:45 +01:00
Flavio Copes
7a6707f597 Merge pull request #473 from getgrav/feature/allow-multiple-plurals
Handle languages that support _PLURAL_MORE_THAN_TWO
2015-11-25 17:42:37 +01:00
Djamil Legato
b0ec66cce8 Merge pull request #475 from nazwa/lists-patch
Proper handling of list fields
2015-11-24 15:51:58 -08:00
Maciej Ka
edfd7db88b pretty & clean 2015-11-24 23:48:22 +00:00
Maciej Ka
fb3e68e16e removed old parameters 2015-11-24 23:34:47 +00:00
Maciej Ka
080ab9e289 Fixed conflict caused by w00fz quickfix 2015-11-24 23:17:37 +00:00
nazwa
f67e441b83 Merge pull request #1 from getgrav/develop
Fixed nested logic for lists and forms parsing (fixes #273)
2015-11-24 23:13:47 +00:00
nazwa
9c07d69c45 Proper handling of list fields
Moved field handling to a separate function and added logic to correctly process nested lists.
2015-11-24 23:02:42 +00:00
Djamil Legato
13207f13ad Fixed nested logic for lists and forms parsing (fixes #273) 2015-11-24 14:57:05 -08:00
Flavio Copes
34f83ebde2 Use the new security salt to calculate the nonce instead of using password_hash 2015-11-24 21:13:46 +01:00
Flavio Copes
14ed805656 Merge branch 'develop' into feature/introduce-user-groups 2015-11-24 21:00:08 +01:00
Matias Griese
385233c508 Automatically create unique security salt for each configuration 2015-11-24 21:14:46 +02:00
Flavio Copes
8a3b987cd5 Merge branch 'develop' into feature/introduce-user-groups 2015-11-24 19:45:04 +01:00
Andy Miller
bdd17fc56a Moving back to release version of Toolbox 2015-11-24 10:06:41 -07:00
Andy Miller
29b3c081ee Merge branch 'feature/refactor' into develop 2015-11-24 09:59:51 -07:00
Andy Miller
b1d80b6c5f Merge branch 'develop' into feature/refactor 2015-11-24 09:59:33 -07:00
Flavio Copes
e00560f81a Handle languages that support _PLURAL_MORE_THAN_TWO
Ex. WEEK_PLURAL, WEEK_PLURAL_MORE_THAN_TWO
2015-11-24 15:18:10 +01:00
Flavio Copes
20f17130a2 Add the IP address of the user to the nonce string calculation even if it's logged in 2015-11-24 15:17:08 +01:00
Flavio Copes
583156d2f3 Handle multiple nonce action types per page 2015-11-24 09:03:48 +01:00
Andy Miller
88f36f4987 Merge branch 'develop' into feature/refactor 2015-11-23 19:08:46 -07:00
Andy Miller
965c8cfbe9 Only generate one nonce per process 2015-11-23 19:07:46 -07:00
Andy Miller
b8f00243e6 Make hash() public for future use 2015-11-23 19:07:12 -07:00
Andy Miller
1d97f98515 Made hash() public for future usage 2015-11-23 17:41:16 -07:00
Matias Griese
9764cf3f65 Sync CompiledFile class with Gantry 2015-11-23 11:00:28 +02:00
Matias Griese
1f5df81496 Sync Folder class with Gantry 2015-11-23 11:00:03 +02:00
Matias Griese
5f76a0255c Merge branches 'develop' and 'feature/refactor' of https://github.com/getgrav/grav into feature/refactor
# Conflicts:
#	CHANGELOG.md
#	composer.lock
2015-11-23 09:48:30 +02:00
Flavio Copes
17f3ca6eba Merge pull request #466 from tcsizmadia/develop
Updated system/languages/hu.yaml
2015-11-22 16:32:57 +01:00
Tamas Csizmadia
b29d79738b Updated system/languages/hu.yaml
Tweaked translation for 'ago' - sounds less weird.
2015-11-22 12:34:44 +01:00
Andy Miller
020cdd7324 Merge pull request #464 from tcsizmadia/develop
Hungarian translation in system/languages/hu.yaml
2015-11-21 22:04:51 -07:00
Andy Miller
1e39f3b22d Merge pull request #465 from MAT978/patch-1
Update fr.yaml
2015-11-21 22:04:45 -07:00
Djamil Legato
37035a488d Fixed bin/plugin help output 2015-11-21 20:53:11 -08:00
Marc-Antoine Thevenet
745b418cd7 Update fr.yaml
update based on the version: en
Nov 17, 2015
2015-11-21 21:47:21 -04:00
Tamas Csizmadia
2a02c8bc4f Hungarian translation in system/languages/hu.yaml 2015-11-21 21:53:03 +01:00
Flavio Copes
a15e063b92 Merge branch 'develop' into feature/introduce-user-groups 2015-11-21 16:52:51 +01:00
Andy Miller
2051fed5b7 Merge pull request #458 from benblee/patch-1
Prevent crawling of unnecessary directories
2015-11-20 19:57:45 -07:00
Ben Lee
268714863e Prevent crawling of unnecessary directories
Prevent search engines from crawling and indexing unnecessary files and directories. The "/user/plugins/" directory may need to be added to the Allow list if plugins use frontend accessible assets. This is tested at a basic level using Google Fetch and Render.
2015-11-20 19:33:24 -07:00
Andy Miller
60c6532307 Merge branch 'release/1.0.0-rc.5' into develop 2015-11-20 17:54:40 -07:00
Matias Griese
9adf81294d Merge branches 'develop' and 'feature/refactor' of https://github.com/getgrav/grav into feature/refactor 2015-11-20 09:28:58 +02:00
Matias Griese
3033818589 On plaintext authentication verify, use default hash even if its not set in configuration 2015-11-20 09:22:07 +02:00
Matias Griese
09ed480628 Composer update 2015-11-19 17:05:21 +02:00
Matias Griese
5dd1554e5d Fix modified function in config 2015-11-19 12:02:08 +02:00
Matias Griese
748f329c8e Merge branches 'develop' and 'feature/refactor' of https://github.com/getgrav/grav into feature/refactor
Conflicts:
	CHANGELOG.md
2015-11-19 10:51:26 +02:00
Matias Griese
7228b25393 Fix configuration reload 2015-11-19 10:49:55 +02:00
Matias Griese
90fcf448c7 Merge branches 'develop' and 'feature/refactor' of https://github.com/getgrav/grav into feature/refactor 2015-11-18 18:17:33 +02:00
Matias Griese
dd2ddfeb40 Refactor Config classes 2015-11-18 15:32:14 +02:00
Matias Griese
ac3396e6c4 Data objects: Allow function call chaining, lazy load blueprints 2015-11-18 14:43:32 +02:00
Flavio Copes
d888dcd085 Add some methods to ImageMedium to be used by the media manager 2015-11-16 20:15:47 +01:00
Flavio Copes
184cb9ea3a Media Meta blueprint 2015-11-16 20:15:19 +01:00
Matias Griese
997c772b7c Make Data classes to implement proper interfaces 2015-11-13 14:09:03 +02:00
Matias Griese
bc4a09f80d Fix undefined variable in Config class 2015-11-13 14:02:06 +02:00
Flavio Copes
55890b4fd8 Merge branch 'develop' into feature/introduce-user-groups 2015-11-04 14:54:56 +01:00
Flavio Copes
6fdfaccc92 Add blueprints 2015-11-04 14:54:47 +01:00
Flavio Copes
76e01e7aea Minor fix 2015-11-04 14:44:21 +01:00
Flavio Copes
87378562ea Fix error when logging in with a non-existing username 2015-11-03 14:35:11 +01:00
Flavio Copes
7e540e0623 Merge branch 'develop' into feature/introduce-user-groups
Use the new isPositive() method to check permissions
2015-10-28 11:00:48 +01:00
Flavio Copes
b0c171f453 Merge branch 'develop' into feature/introduce-user-groups 2015-10-26 17:48:53 +01:00
Flavio Copes
67fefb53ad Allow to filter pages by access level 2015-10-21 19:47:06 +02:00
Flavio Copes
0ff5dc0016 Add accessLevels to return the used page access levels 2015-10-21 18:41:34 +02:00
Flavio Copes
ae17a77789 Use config/groups.yaml instead of config/site.yaml 2015-10-21 17:47:39 +02:00
Flavio Copes
5f11ae7482 Allow editing user groups from user form 2015-10-21 12:28:36 +02:00
Flavio Copes
4d33eb2173 Add access to account blueprint 2015-10-21 12:22:04 +02:00
Flavio Copes
fe7873ddbe Allow to remove a group 2015-10-21 12:21:53 +02:00
Flavio Copes
f95a4f5cc6 Move resolve() to Utils. 2015-10-21 12:21:47 +02:00
Flavio Copes
f973b61b5e Change how permissions work: if true in group, but false in user, false has precedence (allows to fine tune removing permissions per-user) 2015-10-21 12:21:23 +02:00
Flavio Copes
f1d4192ae7 Introduce a resolve() function to Utils, used by Group and User to access an array using dot notation 2015-10-21 12:20:45 +02:00
Flavio Copes
fb500d3e1c Handle saving a new group 2015-10-21 11:09:29 +02:00
Flavio Copes
14347ebf88 Group object 2015-10-20 19:31:47 +02:00
Flavio Copes
3e0188e40b Group blueprint 2015-10-20 19:31:25 +02:00
Flavio Copes
b82f17f367 First draft of group blueprints 2015-10-20 16:36:25 +02:00
Flavio Copes
3f28dc59ea Add check 2015-10-20 16:07:04 +02:00
Flavio Copes
77deea8ad4 Allow a user to be assigned to multiple groups 2015-10-20 15:58:07 +02:00
Flavio Copes
e27f638fe3 The User authorize method now checks first if the user group the user belongs to has permissions for the resource (single group version) 2015-10-19 20:15:11 +02:00
73 changed files with 2724 additions and 1343 deletions

3
.gitignore vendored
View File

@@ -16,12 +16,15 @@ logs/*
!logs/.*
images/*
!images/.*
user/accounts/*
!user/accounts/.*
user/data/*
!user/data/.*
user/plugins/*
!user/plugins/.*
user/themes/*
!user/themes/.*
user/localhost/config/security.yaml
# OS Generated
.DS_Store*

View File

@@ -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
</IfModule>

View File

@@ -1,3 +1,58 @@
# v1.0.0
## 12/11/2015
1. [](#new)
* Add new link attributes via markdown media
* Added setters to set state of CSS/JS pipelining
* Added `user/accounts` to `.gitignore`
* Added configurable permissions option for Image cache
1. [](#improved)
* Hungarian translation updated
* Refactored Theme initialization for improved flexibility
* Wrapped security section of account blueprints in an 'super user' authorize check
* Minor performance optimizations
* Updated core page blueprints with markdown preview option
* Added useful cache info output to Debugbar
* Added `iconv` polyfill library used by Symfony 2.8
* Force lowercase of username in a few places for case sensitive filesystems
1. [](#bugfix)
* Fix for GPM problems "Call to a member function set() on null"
* Fix for individual asset pipeline values not functioning
* Fix `Page::copy()` and `Page::move()` to support multiple moves at once
* Fixed page moving of a page with no content
* Fix for wrong ordering when moving many pages
* Escape root path in page medium files to work with special characters
* Add missing parent constructor to Themes class
* Fix missing file error in `bin/grav sandbox` command
* Fixed changelog differ when upgrading Grav
* Fixed a logic error in `Validation->validate()`
* Make `$container` available in `setup.php` to fix multi-site
# 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 +61,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
@@ -53,7 +108,7 @@
1. [](#new)
* New Page collection options! `@self.parent, @self.siblings, @self.descendants` + more
* Whitelist of file types for fallback route functionality (images by default)
* White list of file types for fallback route functionality (images by default)
1. [](#improved)
* Assets switched from defines to streams
1. [](#bugfix)
@@ -78,7 +133,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

View File

@@ -103,7 +103,7 @@ What you mainly want to know is that:
# License
See [LICENSE](LICENSE)
See [LICENSE](LICENSE.txt)
[gitflow-model]: http://nvie.com/posts/a-successful-git-branching-model/

View File

@@ -40,8 +40,6 @@ if (!function_exists('curl_version')) {
$grav = Grav::instance(array('loader' => $autoload));
$grav['config']->init();
$grav['streams'];
$grav['plugins']->init();
$grav['themes']->init();
$app = new Application('Grav Package Manager', GRAV_VERSION);
$app->addCommands(array(

View File

@@ -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("<red>Example:</red>");
$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("<red>No Console Commands for <white>'{$name}'</white> where found in <white>'{$path}'</white></red>");
exit;

View File

@@ -7,21 +7,22 @@
"license": "MIT",
"require": {
"php": ">=5.4.0",
"twig/twig": "~1.16",
"twig/twig": "~1.23",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~2.7",
"symfony/console": "~2.7",
"symfony/event-dispatcher": "~2.7",
"symfony/var-dumper": "~2.7",
"doctrine/cache": "~1.4",
"filp/whoops": "1.2.*@dev",
"symfony/yaml": "~2.8",
"symfony/console": "~2.8",
"symfony/event-dispatcher": "~2.8",
"symfony/var-dumper": "~2.8",
"symfony/polyfill-iconv": "~1.0",
"doctrine/cache": "~1.5",
"filp/whoops": "1.1.10",
"monolog/monolog": "~1.0",
"gregwar/image": "~2.0",
"ircmaxell/password-compat": "1.0.*",
"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": {

282
composer.lock generated
View File

@@ -4,21 +4,21 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "e1db721096772d41f16003b39b47c85a",
"content-hash": "294dd2282a332d96b19d163ad08e7ba7",
"hash": "09fcc6b4528be7d9c8af68a66e85f0b2",
"content-hash": "69bee250cbc5160401d50cc47c8d6aba",
"packages": [
{
"name": "doctrine/cache",
"version": "v1.5.1",
"version": "v1.5.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e"
"reference": "47c7128262da274f590ae6f86eb137a7a64e82af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/2b9cec5a5e722010cbebc91713d4c11eaa064d5e",
"reference": "2b9cec5a5e722010cbebc91713d4c11eaa064d5e",
"url": "https://api.github.com/repos/doctrine/cache/zipball/47c7128262da274f590ae6f86eb137a7a64e82af",
"reference": "47c7128262da274f590ae6f86eb137a7a64e82af",
"shasum": ""
},
"require": {
@@ -75,7 +75,7 @@
"cache",
"caching"
],
"time": "2015-11-02 18:35:48"
"time": "2015-12-03 10:50:37"
},
{
"name": "donatj/phpuseragentparser",
@@ -169,16 +169,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,20 +209,20 @@
"parsedown",
"parser"
],
"time": "2015-01-25 14:52:34"
"time": "2015-11-01 10:19:22"
},
{
"name": "filp/whoops",
"version": "dev-master",
"version": "1.1.10",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "50a288b51058fa94cf5b37cfa4277535983cc9d5"
"reference": "72538eeb70bbfb11964412a3d098d109efd012f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/50a288b51058fa94cf5b37cfa4277535983cc9d5",
"reference": "50a288b51058fa94cf5b37cfa4277535983cc9d5",
"url": "https://api.github.com/repos/filp/whoops/zipball/72538eeb70bbfb11964412a3d098d109efd012f7",
"reference": "72538eeb70bbfb11964412a3d098d109efd012f7",
"shasum": ""
},
"require": {
@@ -267,7 +267,7 @@
"whoops",
"zf2"
],
"time": "2015-11-14 20:08:27"
"time": "2015-06-29 05:42:04"
},
{
"name": "gregwar/cache",
@@ -404,25 +404,25 @@
},
{
"name": "maximebf/debugbar",
"version": "v1.10.5",
"version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "30e53e8a28284b69dd223c9f5ee8957befd72636"
"reference": "07741d84d39d10f00551c94284cdefcc69703e77"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e53e8a28284b69dd223c9f5ee8957befd72636",
"reference": "30e53e8a28284b69dd223c9f5ee8957befd72636",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/07741d84d39d10f00551c94284cdefcc69703e77",
"reference": "07741d84d39d10f00551c94284cdefcc69703e77",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0",
"symfony/var-dumper": "~2.6"
"psr/log": "^1.0",
"symfony/var-dumper": "^2.6|^3.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "^4.0|^5.0"
},
"suggest": {
"kriswallsmith/assetic": "The best way to manage assets",
@@ -432,12 +432,12 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.10-dev"
"dev-master": "1.11-dev"
}
},
"autoload": {
"psr-0": {
"DebugBar": "src/"
"psr-4": {
"DebugBar\\": "src/DebugBar/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -449,14 +449,19 @@
"name": "Maxime Bouroumeau-Fuseau",
"email": "maxime.bouroumeau@gmail.com",
"homepage": "http://maximebf.com"
},
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "Debug bar in the browser for php application",
"homepage": "https://github.com/maximebf/php-debugbar",
"keywords": [
"debug"
"debug",
"debugbar"
],
"time": "2015-10-19 20:35:12"
"time": "2015-12-10 09:50:24"
},
{
"name": "monolog/monolog",
@@ -666,16 +671,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,29 +716,30 @@
"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.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "5efd632294c8320ea52492db22292ff853a43766"
"reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/5efd632294c8320ea52492db22292ff853a43766",
"reference": "5efd632294c8320ea52492db22292ff853a43766",
"url": "https://api.github.com/repos/symfony/console/zipball/d232bfc100dfd32b18ccbcab4bcc8f28697b7e41",
"reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
"php": ">=5.3.9",
"symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1",
"symfony/process": "~2.1"
"symfony/event-dispatcher": "~2.1|~3.0.0",
"symfony/process": "~2.1|~3.0.0"
},
"suggest": {
"psr/log": "For using the console logger",
@@ -743,13 +749,16 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
"dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -767,20 +776,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2015-10-20 14:38:46"
"time": "2015-11-30 12:35:10"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.7.6",
"version": "v2.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8"
"reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc"
},
"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/a5eb815363c0388e83247e7e9853e5dbc14999cc",
"reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc",
"shasum": ""
},
"require": {
@@ -788,10 +797,10 @@
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.0,>=2.0.5",
"symfony/dependency-injection": "~2.6",
"symfony/expression-language": "~2.6",
"symfony/stopwatch": "~2.3"
"symfony/config": "~2.0,>=2.0.5|~3.0.0",
"symfony/dependency-injection": "~2.6|~3.0.0",
"symfony/expression-language": "~2.6|~3.0.0",
"symfony/stopwatch": "~2.3|~3.0.0"
},
"suggest": {
"symfony/dependency-injection": "",
@@ -800,13 +809,16 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
"dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -824,24 +836,140 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
"time": "2015-10-30 20:15:42"
},
{
"name": "symfony/var-dumper",
"version": "v2.7.6",
"name": "symfony/polyfill-iconv",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "eb033050050916b6bfa51be71009ef67b16046c9"
"url": "https://github.com/symfony/polyfill-iconv.git",
"reference": "21a18998764e569c1675efc7191887130b319605"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/eb033050050916b6bfa51be71009ef67b16046c9",
"reference": "eb033050050916b6bfa51be71009ef67b16046c9",
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/21a18998764e569c1675efc7191887130b319605",
"reference": "21a18998764e569c1675efc7191887130b319605",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Iconv\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Iconv extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"iconv",
"polyfill",
"portable",
"shim"
],
"time": "2015-11-04 20:28:58"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "0b6a8940385311a24e060ec1fe35680e17c74497"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0b6a8940385311a24e060ec1fe35680e17c74497",
"reference": "0b6a8940385311a24e060ec1fe35680e17c74497",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"time": "2015-11-04 20:28:58"
},
{
"name": "symfony/var-dumper",
"version": "v2.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "e6f3855005f2bfad7d7e72431d374a6478893fe3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/e6f3855005f2bfad7d7e72431d374a6478893fe3",
"reference": "e6f3855005f2bfad7d7e72431d374a6478893fe3",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
"twig/twig": "~1.20|~2.0"
},
"suggest": {
"ext-symfony_debug": ""
@@ -849,7 +977,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
"dev-master": "2.8-dev"
}
},
"autoload": {
@@ -858,7 +986,10 @@
],
"psr-4": {
"Symfony\\Component\\VarDumper\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -880,20 +1011,20 @@
"debug",
"dump"
],
"time": "2015-10-25 17:17:38"
"time": "2015-11-18 13:45:00"
},
{
"name": "symfony/yaml",
"version": "v2.7.6",
"version": "v2.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d"
"reference": "f79824187de95064a2f5038904c4d7f0227fedb5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/eca9019c88fbe250164affd107bc8057771f3f4d",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d",
"url": "https://api.github.com/repos/symfony/yaml/zipball/f79824187de95064a2f5038904c4d7f0227fedb5",
"reference": "f79824187de95064a2f5038904c4d7f0227fedb5",
"shasum": ""
},
"require": {
@@ -902,13 +1033,16 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
"dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -926,7 +1060,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
"time": "2015-11-30 12:35:10"
},
{
"name": "twig/twig",
@@ -993,9 +1127,7 @@
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"filp/whoops": 20
},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {

View File

@@ -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
</IfModule>

View File

@@ -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)/(.*)" {

View File

@@ -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
}

View File

@@ -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/

View File

@@ -516,6 +516,17 @@ form:
validate:
type: bool
twig.umask_fix:
type: toggle
label: PLUGIN_ADMIN.TWIG_UMASK_FIX
help: PLUGIN_ADMIN.TWIG_UMASK_FIX_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
assets:
type: section
title: PLUGIN_ADMIN.ASSETS
@@ -688,6 +699,17 @@ form:
validate:
type: bool
images.cache_perms:
type: select
size: small
label: PLUGIN_ADMIN.CACHE_PERMS
help: PLUGIN_ADMIN.CACHE_PERMS_HELP
highlight: '0755'
options:
1: '0755'
0: '0775'
images.debug:
type: toggle
label: PLUGIN_ADMIN.IMAGES_DEBUG
@@ -745,13 +767,14 @@ form:
fields:
session.enabled:
type: toggle
type: hidden
label: PLUGIN_ADMIN.ENABLED
help: PLUGIN_ADMIN.SESSION_ENABLED_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool

View File

@@ -0,0 +1,7 @@
form:
validation: loose
fields:
alt_text:
type: string
label: Alt Text

View File

@@ -0,0 +1,8 @@
form:
validation: loose
fields:
route:
type: select
label: PLUGIN_ADMIN.PAGE
classes: fancy
'@data-options': '\Grav\Common\Page\Pages::parents'

View File

@@ -0,0 +1,8 @@
form:
validation: loose
fields:
new_file_name:
type: text
label: PLUGIN_ADMIN_PRO.NEW_FILE_NAME
validate:
required: true

View File

@@ -29,6 +29,7 @@ form:
content:
type: markdown
showPreview: true
validate:
type: textarea

View File

@@ -25,6 +25,7 @@ form:
content:
type: markdown
showPreview: true
uploads:
type: pagemedia

View File

@@ -25,6 +25,7 @@ form:
content:
type: markdown
showPreview: true
uploads:
type: pagemedia

View File

@@ -54,3 +54,32 @@ form:
default: 'en'
help: PLUGIN_ADMIN.LANGUAGE_HELP
security:
title: Security
type: section
security: admin.super
fields:
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

View File

@@ -0,0 +1,44 @@
title: Group
form:
validation: loose
fields:
spacer:
type: spacer
text: '<br>'
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

View File

@@ -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

View File

@@ -97,6 +97,7 @@ debugger:
images:
default_image_quality: 85 # Default image quality to use when resampling images (85%)
cache_all: false # Cache all image by default
cache_perms: 0755 # Default cache folder perms. Usually 0755 or 0775 depending on setup
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
media:
@@ -108,6 +109,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

View File

@@ -2,7 +2,7 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.0.0-rc.5');
define('GRAV_VERSION', '1.0.0');
define('DS', '/');
// Directories and Paths

View File

@@ -95,3 +95,4 @@ NICETIME:
FORM:
VALIDATION_FAIL: <b>Validation failed:</b>
INVALID_INPUT: Invalid input in
MISSING_REQUIRED_FIELD: Missing required field:

42
system/languages/es.yaml Normal file
View File

@@ -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: <b>Falló la validación. </b>
INVALID_INPUT: "Dato inválido en: "
MISSING_REQUIRED_FIELD: "Falta el campo requerido: "

View File

@@ -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: <b>La validation a échoué :</b>
INVALID_INPUT: Saisie non valide
MISSING_REQUIRED_FIELD: Champ obligatoire manquant :

View File

@@ -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

53
system/languages/hu.yaml Normal file
View File

@@ -0,0 +1,53 @@
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:
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:
YR_PLURAL: év
DEC_PLURAL: évt
FORM:
VALIDATION_FAIL: <b>A validáció hibát talált:</b>
INVALID_INPUT: Az itt megadott érték érvénytelen:
MISSING_REQUIRED_FIELD: Ez a kötelező mező nincs kitöltve:

View File

@@ -19,3 +19,7 @@ NICETIME:
MONTH_PLURAL: mesi
YEAR_PLURAL: anni
DECADE_PLURAL: decadi
FORM:
VALIDATION_FAIL: <b>Validazione fallita:</b>
INVALID_INPUT: Input invalido in
MISSING_REQUIRED_FIELD: Campo richiesto mancante:

View File

@@ -203,7 +203,7 @@ class Assets
*
* @return $this
*/
public function add($asset, $priority = null, $pipeline = null)
public function add($asset, $priority = null, $pipeline = true)
{
// More than one asset
if (is_array($asset)) {
@@ -243,7 +243,7 @@ class Assets
*
* @return $this
*/
public function addCss($asset, $priority = null, $pipeline = null, $group = null)
public function addCss($asset, $priority = null, $pipeline = true, $group = null)
{
if (is_array($asset)) {
foreach ($asset as $a) {
@@ -259,16 +259,20 @@ class Assets
$asset = $this->buildLocalLink($asset);
}
// Check for existence
if ($asset === false) {
return $this;
}
$data = [
'asset' => $asset,
'priority' => intval($priority ?: 10),
'order' => count($this->css),
'pipeline' => $pipeline ?: true,
'pipeline' => (bool) $pipeline,
'group' => $group ?: 'head'
];
// check for dynamic array and merge with defaults
$count_args = func_num_args();
if (func_num_args() == 2) {
$dynamic_arg = func_get_arg(1);
if (is_array($dynamic_arg)) {
@@ -297,7 +301,7 @@ class Assets
* @param string $group name of the group
* @return $this
*/
public function addJs($asset, $priority = null, $pipeline = null, $loading = null, $group = null)
public function addJs($asset, $priority = null, $pipeline = true, $loading = null, $group = null)
{
if (is_array($asset)) {
foreach ($asset as $a) {
@@ -313,17 +317,21 @@ class Assets
$asset = $this->buildLocalLink($asset);
}
// Check for existence
if ($asset === false) {
return $this;
}
$data = [
'asset' => $asset,
'priority' => intval($priority ?: 10),
'order' => count($this->js),
'pipeline' => $pipeline ?: true,
'pipeline' => (bool) $pipeline,
'loading' => $loading ?: '',
'group' => $group ?: 'head'
];
// check for dynamic array and merge with defaults
$count_args = func_num_args();
if (func_num_args() == 2) {
$dynamic_arg = func_get_arg(1);
if (is_array($dynamic_arg)) {
@@ -351,7 +359,7 @@ class Assets
*
* @return \Grav\Common\Assets
*/
public function addAsyncJs($asset, $priority = null, $pipeline = null, $group = null)
public function addAsyncJs($asset, $priority = null, $pipeline = true, $group = null)
{
return $this->addJs($asset, $priority, $pipeline, 'async', $group);
}
@@ -368,7 +376,7 @@ class Assets
*
* @return \Grav\Common\Assets
*/
public function addDeferJs($asset, $priority = null, $pipeline = null, $group = null)
public function addDeferJs($asset, $priority = null, $pipeline = true, $group = null)
{
return $this->addJs($asset, $priority, $pipeline, 'defer', $group);
}
@@ -1124,6 +1132,36 @@ class Assets
return $this->addDir($directory, self::JS_REGEX);
}
/**
* Sets the state of CSS Pipeline
*
* @param boolean $value
*/
public function setCssPipeline($value)
{
$this->css_pipeline = (bool) $value;
}
/**
* Sets the state of JS Pipeline
*
* @param boolean $value
*/
public function setJsPipeline($value)
{
$this->js_pipeline = (bool) $value;
}
/**
* Explicitly set's a timestamp for assets
*
* @param $value
*/
public function setTimestamp($value)
{
$this->timestamp = '?'.$value;
}
public function __toString()
{
return '';

View File

@@ -38,6 +38,8 @@ class Cache extends Getters
*/
protected $driver;
protected $driver_name;
/**
* @var bool
*/
@@ -110,6 +112,10 @@ class Cache extends Getters
// Set the cache namespace to our unique key
$this->driver->setNamespace($this->key);
// Dump Cache state
$grav['debugger']->addMessage('Cache: [' . ($this->enabled ? 'true' : 'false') . '] Driver: [' . $this->driver_name . ']');
}
/**
@@ -136,6 +142,8 @@ class Cache extends Getters
$driver_name = $setting;
}
$this->driver_name = $driver_name;
switch ($driver_name) {
case 'apc':
$driver = new \Doctrine\Common\Cache\ApcCache();

View File

@@ -1,207 +0,0 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\Filesystem\Folder;
use RocketTheme\Toolbox\Blueprints\Blueprints as BaseBlueprints;
use RocketTheme\Toolbox\File\PhpFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* The Blueprints class contains configuration rules.
*
* @author RocketTheme
* @license MIT
*/
class Blueprints extends BaseBlueprints
{
protected $grav;
protected $files = [];
protected $blueprints;
public function __construct(array $serialized = null, Grav $grav = null)
{
parent::__construct($serialized);
$this->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];
}
}

View File

@@ -0,0 +1,236 @@
<?php
namespace Grav\Common\Config;
use RocketTheme\Toolbox\File\PhpFile;
/**
* The Compiled base class.
*/
abstract class CompiledBase
{
/**
* @var int Version number for the compiled file.
*/
public $version = 1;
/**
* @var string Filename (base name) of the compiled configuration.
*/
public $name;
/**
* @var string|bool Configuration checksum.
*/
public $checksum;
/**
* @var string Cache folder to be used.
*/
protected $cacheFolder;
/**
* @var array List of files to load.
*/
protected $files;
/**
* @var string
*/
protected $path;
/**
* @var mixed Configuration object.
*/
protected $object;
/**
* @param string $cacheFolder Cache folder to be used.
* @param array $files List of files as returned from ConfigFileFinder class.
* @param string $path Base path for the file list.
* @throws \BadMethodCallException
*/
public function __construct($cacheFolder, array $files, $path)
{
if (!$cacheFolder) {
throw new \BadMethodCallException('Cache folder not defined.');
}
$this->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();
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
use RocketTheme\Toolbox\Blueprints\Blueprints;
/**
* The Compiled Blueprints class.
*/
class CompiledBlueprints extends CompiledBase
{
/**
* @var int Version number for the compiled file.
*/
public $version = 1;
/**
* @var Blueprints Blueprints object.
*/
protected $object;
/**
* Create configuration object.
*
* @param array $data
*/
protected function createObject(array $data = [])
{
$this->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();
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
/**
* The Compiled Configuration class.
*/
class CompiledConfig extends CompiledBase
{
/**
* @var int Version number for the compiled file.
*/
public $version = 1;
/**
* @var Config Configuration object.
*/
protected $object;
/**
* @var callable Blueprints loader.
*/
protected $callable;
/**
* @var bool
*/
protected $withDefaults;
/**
* Set blueprints for the configuration.
*
* @param callable $blueprints
* @return $this
*/
public function setBlueprints(callable $blueprints)
{
$this->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();
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
/**
* The Compiled Languages class.
*/
class CompiledLanguages extends CompiledBase
{
/**
* @var int Version number for the compiled file.
*/
public $version = 1;
/**
* @var Languages Configuration object.
*/
protected $object;
/**
* Create configuration object.
*
* @param array $data
*/
protected function createObject(array $data = [])
{
$this->object = new Languages($data);
}
/**
* Finalize configuration object.
*/
protected function finalizeObject()
{
$this->object->checksum($this->checksum());
}
/**
* Function gets called when cached configuration is saved.
*/
public function modified()
{
$this->object->modified(true);
}
/**
* Load single configuration file and append it to the correct position.
*
* @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();
}
}

View File

@@ -1,12 +1,10 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Data\Data;
use RocketTheme\Toolbox\Blueprints\Blueprints;
use RocketTheme\Toolbox\File\PhpFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Grav\Common\Service\ConfigServiceProvider;
/**
* The Config class contains configuration information.
@@ -16,471 +14,84 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
*/
class Config extends Data
{
protected $grav;
protected $streams = [
'system' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['system'],
]
],
'user' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user'],
]
],
'asset' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['assets'],
]
],
'blueprints' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://blueprints', 'system/blueprints'],
]
],
'config' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://config', 'system/config'],
]
],
'plugins' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://plugins'],
]
],
'plugin' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://plugins'],
]
],
'themes' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://themes'],
]
],
'languages' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://languages', 'system/languages'],
]
],
'cache' => [
'type' => 'Stream',
'prefixes' => [
'' => ['cache'],
'images' => ['images']
]
],
'log' => [
'type' => 'Stream',
'prefixes' => [
'' => ['logs']
]
],
'backup' => [
'type' => 'Stream',
'prefixes' => [
'' => ['backup']
]
]
];
protected $setup = [];
protected $blueprintFiles = [];
protected $configFiles = [];
protected $languageFiles = [];
protected $checksum;
protected $timestamp;
protected $configLookup;
protected $blueprintLookup;
protected $pluginLookup;
protected $languagesLookup;
protected $finder;
protected $environment;
protected $messages = [];
protected $languages;
public function __construct(array $setup = array(), Grav $grav = null, $environment = null)
{
$this->grav = $grav ?: Grav::instance();
$this->finder = new ConfigFinder;
$this->environment = $environment ?: 'localhost';
$this->messages[] = 'Environment Name: ' . $this->environment;
// Make sure that
if (!isset($setup['streams']['schemes'])) {
$setup['streams']['schemes'] = [];
}
$setup['streams']['schemes'] += $this->streams;
$setup = $this->autoDetectEnvironmentConfig($setup);
$this->setup = $setup;
parent::__construct($setup);
$this->check();
}
protected $modified = false;
public function key()
{
return $this->checksum();
}
public function reload()
public function checksum($checksum = null)
{
$this->items = $this->setup;
$this->check();
$this->init();
$this->debug();
return $this;
}
protected function check()
{
$streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
if (!is_array($streams)) {
throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
}
$diff = array_keys(array_diff_key($this->streams, $streams));
if ($diff) {
throw new \InvalidArgumentException(
sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
);
}
}
public function debug()
{
foreach ($this->messages as $message) {
$this->grav['debugger']->addMessage($message);
}
$this->messages = [];
}
public function init()
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$this->configLookup = $locator->findResources('config://');
$this->blueprintLookup = $locator->findResources('blueprints://config');
$this->pluginLookup = $locator->findResources('plugins://');
$this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master');
$this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master');
// process languages if supported
if ($this->get('system.languages.translations', true)) {
$this->languagesLookup = $locator->findResources('languages://');
$this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master');
}
$this->initializeLocator($locator);
}
public function checksum()
{
if (empty($this->checksum)) {
$checkBlueprints = $this->get('system.cache.check.blueprints', false);
$checkLanguages = $this->get('system.cache.check.languages', false);
$checkConfig = $this->get('system.cache.check.config', true);
$checkSystem = $this->get('system.cache.check.system', true);
if (!$checkBlueprints && !$checkLanguages && !$checkConfig && !$checkSystem) {
$this->messages[] = 'Skip configuration timestamp check.';
return false;
}
// Generate checksum according to the configuration settings.
if (!$checkConfig) {
// Just check changes in system.yaml files and ignore all the other files.
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
} else {
// Check changes in all configuration files.
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
}
if ($checkBlueprints) {
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
} else {
$cb = [];
}
if ($checkLanguages) {
$cl = $this->finder->locateLanguageFiles($this->languagesLookup, $this->pluginLookup);
} else {
$cl = [];
}
$this->checksum = md5(json_encode([$cc, $cb, $cl]));
if ($checksum !== null) {
$this->checksum = $checksum;
}
return $this->checksum;
}
protected function autoDetectEnvironmentConfig($items)
public function modified($modified = null)
{
$environment = $this->environment;
$env_stream = 'user://'.$environment.'/config';
if (file_exists(USER_DIR.$environment.'/config')) {
array_unshift($items['streams']['schemes']['config']['prefixes'][''], $env_stream);
if ($modified !== null) {
$this->modified = $modified;
}
return $items;
return $this->modified;
}
protected function loadCompiledBlueprints($blueprints, $plugins, $filename = null)
public function reload()
{
$checksum = md5(json_encode($blueprints));
$filename = $filename
? CACHE_DIR . 'compiled/blueprints/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/blueprints/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$cache = $file->exists() ? $file->content() : null;
$blueprintFiles = $this->finder->locateBlueprintFiles($blueprints, $plugins);
$checksum .= ':'.md5(json_encode($blueprintFiles));
$class = get_class($this);
$grav = Grav::instance();
// Load real file if cache isn't up to date (or is invalid).
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Load new configuration.
$config = ConfigServiceProvider::load($grav);
// Load blueprints.
$this->blueprints = new Blueprints;
foreach ($blueprintFiles as $files) {
$this->loadBlueprintFiles($files);
}
/** @var Debugger $debugger */
$debugger = $grav['debugger'];
$cache = [
'@class' => $class,
'checksum' => $checksum,
'files' => $blueprintFiles,
'data' => $this->blueprints->toArray()
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$this->messages[] = 'Saving compiled blueprints.';
$file->save($cache);
$file->unlock();
}
} else {
$this->blueprints = new Blueprints($cache['data']);
if ($config->modified()) {
// Update current configuration.
$this->items = $config->toArray();
$this->checksum($config->checksum());
$this->modified(true);
$debugger->addMessage('Configuration was changed and saved.');
}
return $this;
}
public function debug()
{
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];
$debugger->addMessage('Environment Name: ' . $this->environment);
if ($this->modified()) {
$debugger->addMessage('Configuration reloaded and cached.');
}
}
protected function loadCompiledConfig($configs, $plugins, $filename = null)
public function init()
{
$checksum = md5(json_encode($configs));
$filename = $filename
? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$cache = $file->exists() ? $file->content() : null;
$class = get_class($this);
$checksum = $this->checksum();
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['@class'] != $class
) {
$this->messages[] = 'No cached configuration, compiling new configuration..';
} else if ($cache['checksum'] !== $checksum) {
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
} else {
$this->messages[] = 'Configuration checksum matches, using cached version.';
$this->items = $cache['data'];
return;
}
$configFiles = $this->finder->locateConfigFiles($configs, $plugins);
// Attempt to lock the file for writing.
$file->lock(false);
// Load configuration.
foreach ($configFiles as $files) {
$this->loadConfigFiles($files);
}
$cache = [
'@class' => $class,
'timestamp' => time(),
'checksum' => $checksum,
'data' => $this->toArray()
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$this->messages[] = 'Saving compiled configuration.';
$file->save($cache);
$file->unlock();
}
$this->items = $cache['data'];
}
/**
* @param $languages
* @param $plugins
* @param null $filename
*/
protected function loadCompiledLanguages($languages, $plugins, $filename = null)
{
$checksum = md5(json_encode($languages));
$filename = $filename
? CACHE_DIR . 'compiled/languages/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/languages/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$cache = $file->exists() ? $file->content() : null;
$languageFiles = $this->finder->locateLanguageFiles($languages, $plugins);
$checksum .= ':' . md5(json_encode($languageFiles));
$class = get_class($this);
// Load real file if cache isn't up to date (or is invalid).
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Load languages.
$this->languages = new Languages;
$pluginPaths = str_ireplace(GRAV_ROOT . '/', '', array_reverse($plugins));
foreach ($pluginPaths as $path) {
if (isset($languageFiles[$path])) {
foreach ((array) $languageFiles[$path] as $plugin => $item) {
$lang_file = CompiledYamlFile::instance($item['file']);
$content = $lang_file->content();
$this->languages->mergeRecursive($content);
}
unset($languageFiles[$path]);
}
}
foreach ($languageFiles as $location) {
foreach ($location as $lang => $item) {
$lang_file = CompiledYamlFile::instance($item['file']);
$content = $lang_file->content();
$this->languages->join($lang, $content, '/');
}
}
$cache = [
'@class' => $class,
'checksum' => $checksum,
'files' => $languageFiles,
'data' => $this->languages->toArray()
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$this->messages[] = 'Saving compiled languages.';
$file->save($cache);
$file->unlock();
}
} else {
$this->languages = new Languages($cache['data']);
}
}
/**
* Load blueprints.
*
* @param array $files
*/
public function loadBlueprintFiles(array $files)
{
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->blueprints->embed($name, $file->content(), '/');
}
}
/**
* Load configuration.
*
* @param array $files
*/
public function loadConfigFiles(array $files)
{
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->join($name, $file->content(), '/');
}
}
/**
* Initialize resource locator by using the configuration.
*
* @param UniformResourceLocator $locator
*/
public function initializeLocator(UniformResourceLocator $locator)
{
$locator->reset();
$schemes = (array) $this->get('streams.schemes', []);
foreach ($schemes as $scheme => $config) {
if (isset($config['paths'])) {
$locator->addPath($scheme, '', $config['paths']);
}
if (isset($config['prefixes'])) {
foreach ($config['prefixes'] as $prefix => $paths) {
$locator->addPath($scheme, $prefix, $paths);
}
$setup = Grav::instance()['setup']->toArray();
foreach ($setup as $key => $value) {
if ($key === 'streams' || !is_array($value)) {
// Optimized as streams and simple values are fully defined in setup.
$this->items[$key] = $value;
} else {
$this->joinDefaults($key, $value);
}
}
}
/**
* Get available streams and their types from the configuration.
*
* @return array
* @return mixed
* @deprecated
*/
public function getStreams()
{
$schemes = [];
foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
if ($type[0] != '\\') {
$type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
}
$schemes[$scheme] = $type;
}
return $schemes;
}
public function getLanguages()
{
return $this->languages;
return Grav::instance()['languages'];
}
}

View File

@@ -0,0 +1,258 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\Filesystem\Folder;
/**
* The Configuration & Blueprints Finder class.
*/
class ConfigFileFinder
{
protected $base = '';
/**
* @param string $base
* @return $this
*/
public function setBase($base)
{
$this->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;
}
}

View File

@@ -1,186 +0,0 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\Filesystem\Folder;
/**
* The Configuration Finder class.
*
* @author RocketTheme
* @license MIT
*/
class ConfigFinder
{
/**
* Get all locations for blueprint files (including plugins).
*
* @param array $blueprints
* @param array $plugins
* @return array
*/
public function locateBlueprintFiles(array $blueprints, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->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];
}
}

View File

@@ -11,6 +11,23 @@ use Grav\Common\Data\Data;
*/
class Languages extends Data
{
public function checksum($checksum = null)
{
if ($checksum !== null) {
$this->checksum = $checksum;
}
return $this->checksum;
}
public function modified($modified = null)
{
if ($modified !== null) {
$this->modified = $modified;
}
return $this->modified;
}
public function reformat()
{

View File

@@ -0,0 +1,254 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Data\Data;
use Grav\Common\Utils;
use RocketTheme\Toolbox\File\YamlFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* The Config class contains configuration information.
*
* @author RocketTheme
* @license MIT
*/
class Setup extends Data
{
protected $streams = [
'system' => [
'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($container)
{
$environment = $container['uri']->environment();
if (!$environment) {
$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();
}
}
}

View File

@@ -3,6 +3,8 @@ namespace Grav\Common\Data;
use Grav\Common\GravTrait;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
/**
* Blueprint handles the inside logic of blueprints.
@@ -10,9 +12,9 @@ use RocketTheme\Toolbox\ArrayTraits\Export;
* @author RocketTheme
* @license MIT
*/
class Blueprint
class Blueprint implements \ArrayAccess, ExportInterface
{
use Export, DataMutatorTrait, GravTrait;
use Export, NestedArrayAccessWithGetters, GravTrait;
public $name;
@@ -240,9 +242,6 @@ class Blueprint
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 @@ class Blueprint
}
/**
* 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;
* 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.
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 @@ class Blueprint
&& $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);
}
}
}

View File

@@ -1,9 +1,11 @@
<?php
namespace Grav\Common\Data;
use RocketTheme\Toolbox\ArrayTraits\ArrayAccessWithGetters;
use RocketTheme\Toolbox\ArrayTraits\Countable;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
use RocketTheme\Toolbox\Blueprints\Blueprints;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\FileInterface;
@@ -13,9 +15,9 @@ use RocketTheme\Toolbox\File\FileInterface;
* @author RocketTheme
* @license MIT
*/
class Data implements DataInterface
class Data implements DataInterface, \ArrayAccess, \Countable, ExportInterface
{
use ArrayAccessWithGetters, Countable, Export, DataMutatorTrait;
use NestedArrayAccessWithGetters, Countable, Export;
protected $gettersVariable = 'items';
protected $items;
@@ -32,12 +34,11 @@ class Data implements DataInterface
/**
* @param array $items
* @param Blueprint $blueprints
* @param Blueprint|callable $blueprints
*/
public function __construct(array $items = array(), Blueprint $blueprints = null)
public function __construct(array $items = array(), $blueprints = null)
{
$this->items = $items;
$this->blueprints = $blueprints;
}
@@ -57,126 +58,150 @@ class Data implements DataInterface
}
/**
* Set default value by using dot notation for nested arrays/objects.
*
* @example $data->def('this.is.my.nested.variable', 'default');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
*/
public function def($name, $default = null, $separator = '.')
{
$this->set($name, $this->get($name, $default, $separator), $separator);
}
/**
* Join two values together by using blueprints if available.
* Join nested values together by using blueprints.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return $this
* @throws \RuntimeException
*/
public function join($name, $value, $separator = '.')
{
$old = $this->get($name, null, $separator);
if ($old === null) {
// Variable does not exist yet: just use the incoming value.
} elseif ($this->blueprints) {
// Blueprints: join values by using blueprints.
$value = $this->blueprints->mergeData($old, $value, $name, $separator);
} else {
// No blueprints: replace existing top level variables with the new ones.
$value = array_merge($old, $value);
if ($old !== null) {
if (!is_array($old)) {
throw new \RuntimeException('Value ' . $old);
}
if (is_object($value)) {
$value = (array) $value;
} elseif (!is_array($value)) {
throw new \RuntimeException('Value ' . $value);
}
$value = $this->blueprints()->mergeData($old, $value, $name, $separator);
}
$this->set($name, $value, $separator);
return $this;
}
/**
* Join two values together by using blueprints if available.
* Get nested structure containing default values defined in the blueprints.
*
* Fields without default value are ignored in the list.
* @return array
*/
public function getDefaults()
{
return $this->blueprints()->getDefaults();
}
/**
* Set default values by using blueprints.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return $this
*/
public function joinDefaults($name, $value, $separator = '.')
{
if (is_object($value)) {
$value = (array) $value;
}
$old = $this->get($name, null, $separator);
if ($old === null) {
// Variable does not exist yet: just use the incoming value.
} elseif ($this->blueprints) {
// Blueprints: join values by using blueprints.
$value = $this->blueprints->mergeData($value, $old, $name, $separator);
} else {
// No blueprints: replace existing top level variables with the new ones.
$value = array_merge($value, $old);
if ($old !== null) {
$value = $this->blueprints()->mergeData($value, $old, $name, $separator);
}
$this->set($name, $value, $separator);
return $this;
}
/**
* Get value from the configuration and join it with given data.
*
* @param string $name Dot separated path to the requested value.
* @param array $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return array
* @throws \RuntimeException
*/
public function getJoined($name, $value, $separator = '.')
{
if (is_object($value)) {
$value = (array) $value;
} elseif (!is_array($value)) {
throw new \RuntimeException('Value ' . $value);
}
$old = $this->get($name, null, $separator);
if ($old === null) {
// No value set; no need to join data.
return $value;
}
if (!is_array($old)) {
throw new \RuntimeException('Value ' . $old);
}
// Return joined data.
return $this->blueprints()->mergeData($old, $value, $name, $separator);
}
/**
* Merge two sets of data together.
* Merge two configurations together.
*
* @param array $data
* @return void
* @return $this
*/
public function merge(array $data)
{
if ($this->blueprints) {
$this->items = $this->blueprints->mergeData($this->items, $data);
} else {
$this->items = array_merge($this->items, $data);
}
$this->items = $this->blueprints()->mergeData($this->items, $data);
return $this;
}
/**
* Add default data to the set.
* Set default values to the configuration if variables were not set.
*
* @param array $data
* @return void
* @return $this
*/
public function setDefaults(array $data)
{
if ($this->blueprints) {
$this->items = $this->blueprints->mergeData($data, $this->items);
} else {
$this->items = array_merge($data, $this->items);
}
}
$this->items = $this->blueprints()->mergeData($data, $this->items);
/**
* Return blueprints.
*
* @return Blueprint
*/
public function blueprints()
{
return $this->blueprints;
return $this;
}
/**
* Validate by blueprints.
*
* @return $this
* @throws \Exception
*/
public function validate()
{
if ($this->blueprints) {
$this->blueprints->validate($this->items);
}
$this->blueprints()->validate($this->items);
return $this;
}
/**
* @return $this
* Filter all items by using blueprints.
*/
public function filter()
{
if ($this->blueprints) {
$this->items = $this->blueprints->filter($this->items);
}
$this->items = $this->blueprints()->filter($this->items);
return $this;
}
/**
@@ -186,7 +211,24 @@ class Data implements DataInterface
*/
public function extra()
{
return $this->blueprints ? $this->blueprints->extra($this->items) : array();
return $this->blueprints()->extra($this->items);
}
/**
* Return blueprints.
*
* @return Blueprints
*/
public function blueprints()
{
if (!$this->blueprints){
$this->blueprints = new Blueprints;
} elseif (is_callable($this->blueprints)) {
// Lazy load blueprints.
$blueprints = $this->blueprints;
$this->blueprints = $blueprints();
}
return $this->blueprints;
}
/**

View File

@@ -1,68 +0,0 @@
<?php
namespace Grav\Common\Data;
trait DataMutatorTrait
{
/**
* Get value by using dot notation for nested arrays/objects.
*
* @example $value = $data->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;
}
}

View File

@@ -31,6 +31,11 @@ class Validation
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'];
@@ -38,7 +43,7 @@ class Validation
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
$method = 'type'.strtr($type, '-', '_');
$name = ucfirst(isset($field['label']) ? $field['label'] : $field['name']);
$message = (string) isset($field['validate']['message']) ? $field['validate']['message'] : $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
$message = (string) isset($field['validate']['message']) ? $language->translate($field['validate']['message']) : $language->translate('FORM.INVALID_INPUT', null, true) . ' "' . $language->translate($name) . '"';
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $validate, $field);
@@ -78,6 +83,11 @@ class Validation
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 @@ class Validation
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
*
@@ -592,7 +620,7 @@ class Validation
if (is_string($value)) {
$value = trim($value);
}
return (bool) $params !== true || !empty($value);
}

View File

@@ -47,7 +47,11 @@ trait CompiledFile
|| $cache['filename'] != $this->filename
) {
// Attempt to lock the file for writing.
$file->lock(false);
try {
$file->lock(false);
} catch (\Exception $e) {
// Another process has locked the file; we will check this in a bit.
}
// Decode RAW file into compiled array.
$data = (array) $this->decode($this->raw());
@@ -64,6 +68,7 @@ trait CompiledFile
$file->unlock();
}
}
$file->free();
$this->content = $cache['data'];
}

View File

@@ -19,12 +19,12 @@ abstract class Folder
{
$last_modified = 0;
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$filterItr = new RecursiveFolderFilterIterator($dirItr);
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
$directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$filter = new RecursiveFolderFilterIterator($directory);
$iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
/** @var \RecursiveDirectoryIterator $file */
foreach ($itr as $dir) {
foreach ($iterator as $dir) {
$dir_modified = $dir->getMTime();
if ($dir_modified > $last_modified) {
$last_modified = $dir_modified;
@@ -46,12 +46,12 @@ abstract class Folder
{
$last_modified = 0;
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST);
$itr = new \RegexIterator($itrItr, '/^.+\.'.$extensions.'$/i');
$directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$recursive = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
$iterator = new \RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
/** @var \RecursiveDirectoryIterator $file */
foreach ($itr as $filepath => $file) {
foreach ($iterator as $filepath => $file) {
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
@@ -64,8 +64,8 @@ abstract class Folder
/**
* Get relative path between target and base path. If path isn't relative, return full path.
*
* @param string $path
* @param mixed|string $base
* @param string $path
* @param string $base
* @return string
*/
public static function getRelativePath($path, $base = GRAV_ROOT)
@@ -81,6 +81,43 @@ abstract class Folder
return $path;
}
/**
* Get relative path between target and base path. If path isn't relative, return full path.
*
* @param string $path
* @param string $base
* @return string
*/
public static function getRelativePathDotDot($path, $base)
{
$base = preg_replace('![\\\/]+!', '/', $base);
$path = preg_replace('![\\\/]+!', '/', $path);
if ($path === $base) {
return '';
}
$baseParts = explode('/', isset($base[0]) && '/' === $base[0] ? substr($base, 1) : $base);
$pathParts = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path);
array_pop($baseParts);
$lastPart = array_pop($pathParts);
foreach ($baseParts as $i => $directory) {
if (isset($pathParts[$i]) && $pathParts[$i] === $directory) {
unset($baseParts[$i], $pathParts[$i]);
} else {
break;
}
}
$pathParts[] = $lastPart;
$path = str_repeat('../', count($baseParts)) . implode('/', $pathParts);
return '' === $path
|| '/' === $path[0]
|| false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
? "./$path" : $path;
}
/**
* Shift first directory out of the path.
*
@@ -96,8 +133,6 @@ abstract class Folder
return $result ?: null;
}
/**
* Return recursive list of all files and directories under given path.
*
@@ -116,13 +151,17 @@ abstract class Folder
$pattern = isset($params['pattern']) ? $params['pattern'] : null;
$filters = isset($params['filters']) ? $params['filters'] : null;
$recursive = isset($params['recursive']) ? $params['recursive'] : true;
$levels = isset($params['levels']) ? $params['levels'] : -1;
$key = isset($params['key']) ? 'get' . $params['key'] : null;
$value = isset($params['value']) ? 'get' . $params['value'] : ($recursive ? 'getSubPathname' : 'getFilename');
$folders = isset($params['folders']) ? $params['folders'] : true;
$files = isset($params['files']) ? $params['files'] : true;
if ($recursive) {
$directory = new \RecursiveDirectoryIterator($path,
\RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
$iterator->setMaxDepth(max($levels, -1));
} else {
$iterator = new \FilesystemIterator($path);
}
@@ -131,6 +170,16 @@ abstract class Folder
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
// Ignore hidden files.
if ($file->getFilename()[0] == '.') {
continue;
}
if (!$folders && $file->isDir()) {
continue;
}
if (!$files && $file->isFile()) {
continue;
}
if ($compare && $pattern && !preg_match($pattern, $file->{$compare}())) {
continue;
}
@@ -138,7 +187,8 @@ abstract class Folder
$filePath = $file->{$value}();
if ($filters) {
if (isset($filters['key'])) {
$fileKey = preg_replace($filters['key'], '', $fileKey);
$pre = !empty($filters['pre-key']) ? $filters['pre-key'] : '';
$fileKey = $pre . preg_replace($filters['key'], '', $fileKey);
}
if (isset($filters['value'])) {
$filter = $filters['value'];
@@ -146,12 +196,12 @@ abstract class Folder
$filePath = call_user_func($filter, $file);
} else {
$filePath = preg_replace($filter, '', $filePath);
}
}
}
}
if ($fileKey !== null) {
$results[$fileKey] = $filePath;
$results[$fileKey] = $filePath;
} else {
$results[] = $filePath;
}
@@ -163,11 +213,12 @@ abstract class Folder
/**
* Recursively copy directory in filesystem.
*
* @param string $source
* @param string $target
* @param string $source
* @param string $target
* @param string $ignore Ignore files matching pattern (regular expression).
* @throws \RuntimeException
*/
public static function copy($source, $target)
public static function copy($source, $target, $ignore = null)
{
$source = rtrim($source, '\\/');
$target = rtrim($target, '\\/');
@@ -177,19 +228,24 @@ abstract class Folder
}
// Make sure that path to the target exists before copying.
self::mkdir($target);
self::create($target);
$success = true;
// Go through all sub-directories and copy everything.
$files = self::all($source);
foreach ($files as $file) {
if ($ignore && preg_match($ignore, $file)) {
continue;
}
$src = $source .'/'. $file;
$dst = $target .'/'. $file;
if (is_dir($src)) {
// Create current directory.
$success &= @mkdir($dst);
// Create current directory (if it doesn't exist).
if (!is_dir($dst)) {
$success &= @mkdir($dst, 0777, true);
}
} else {
// Or copy current file.
$success &= @copy($src, $dst);
@@ -208,8 +264,8 @@ abstract class Folder
/**
* Move directory in filesystem.
*
* @param string $source
* @param string $target
* @param string $source
* @param string $target
* @throws \RuntimeException
*/
public static function move($source, $target)
@@ -218,8 +274,13 @@ abstract class Folder
throw new \RuntimeException('Cannot move non-existing folder.');
}
// Don't do anything if the source is the same as the new target
if ($source == $target) {
return;
}
// 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 +299,16 @@ abstract class Folder
* Recursively delete directory from filesystem.
*
* @param string $target
* @throws \RuntimeException
* @param bool $include_target
* @return bool
*/
public static function delete($target)
public static function delete($target, $include_target = true)
{
if (!is_dir($target)) {
return;
return false;
}
$success = self::doDelete($target);
$success = self::doDelete($target, $include_target);
if (!$success) {
$error = error_get_last();
@@ -255,16 +316,31 @@ abstract class Folder
}
// Make sure that the change will be detected when caching.
@touch(dirname($target));
if ($include_target) {
@touch(dirname($target));
} else {
@touch($target);
}
return $success;
}
/**
* @param string $folder
* @param string $folder
* @throws \RuntimeException
* @internal
*/
public static function mkdir($folder)
{
self::create($folder);
}
/**
* @param string $folder
* @throws \RuntimeException
* @internal
*/
public static function create($folder)
{
if (is_dir($folder)) {
return;
@@ -320,10 +396,11 @@ abstract class Folder
/**
* @param string $folder
* @param bool $include_target
* @return bool
* @internal
*/
protected static function doDelete($folder)
protected static function doDelete($folder, $include_target = true)
{
// Special case for symbolic links.
if (is_link($folder)) {
@@ -338,16 +415,16 @@ abstract class Folder
/** @var \DirectoryIterator $fileinfo */
foreach ($files as $fileinfo) {
if ($fileinfo->isDir()) {
if (false === rmdir($fileinfo->getRealPath())) {
if (false === @rmdir($fileinfo->getRealPath())) {
return false;
}
} else {
if (false === unlink($fileinfo->getRealPath())) {
if (false === @unlink($fileinfo->getRealPath())) {
return false;
}
}
}
return rmdir($folder);
return $include_target ? @rmdir($folder) : true;
}
}

View File

@@ -55,7 +55,7 @@ class Grav extends AbstractPackageCollection
$diffLog = [];
foreach ($this->data['changelog'] as $version => $changelog) {
preg_match("/[\d\.]+/", $version, $cleanVersion);
preg_match("/[\w-\.]+/", $version, $cleanVersion);
if (!$cleanVersion || version_compare($diff, $cleanVersion[0], ">=")) { continue; }

View File

@@ -78,10 +78,12 @@ class Response
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));

View File

@@ -230,7 +230,6 @@ class Grav extends Container
$debugger->startTimer('themes', 'Themes');
$this['themes']->init();
$this->fireEvent('onThemeInitialized');
$debugger->stopTimer('themes');
$task = $this['task'];

View File

@@ -215,4 +215,26 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
return $this;
}
/**
* Sorts elements from the list and returns a copy of the list in the proper order
*
* @param callable|null $callback
*
* @param bool $desc
*
* @return $this|array
* @internal param bool $asc
*
*/
public function sort(callable $callback = null, $desc = false)
{
if (!$callback || !is_callable($callback)) { return $this; }
$items = $this->items;
uasort($items, $callback);
return !$desc ? $items : array_reverse($items, true);
}
}

View File

@@ -352,7 +352,7 @@ class Language
if ($this->config->get('system.languages.translations_fallback', true)) {
$languages = $this->getFallbackLanguages();
} else {
$languages = (array)$this->getDefault();
$languages = (array)$this->getLanguage();
}
}
} else {

View File

@@ -193,7 +193,7 @@ class LanguageCodes
],
"fr" => [
"name" => "French",
"nativeName" => "français"
"nativeName" => "Français"
],
"ff" => [
"name" => "Fula",

View File

@@ -204,6 +204,48 @@ trait ParsedownGravTrait
if (isset($excerpt['element']['attributes']['href'])) {
$url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['href']));
// if there is a query, then parse it and build action calls
if (isset($url['query'])) {
$actions = array_reduce(explode('&', $url['query']), function ($carry, $item) {
$parts = explode('=', $item, 2);
$value = isset($parts[1]) ? $parts[1] : null;
$carry[$parts[0]] = $value;
return $carry;
}, []);
// valid attributes supported
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
// Unless told to not process, go through actions
if (array_key_exists('noprocess', $actions)) {
unset($actions['noprocess']);
} else {
// loop through actions for the image and call them
foreach ($actions as $attrib => $value) {
$key = $attrib;
if (in_array($attrib, $valid_attributes)) {
// support both class and classes
if ($attrib == 'classes') {
$attrib = 'class';
}
$excerpt['element']['attributes'][$attrib] = $value;
unset($actions[$key]);
}
}
}
$url['query']= http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
}
// if no query elements left, unset query
if (empty($url['query'])) {
unset ($url['query']);
}
// if there is no scheme, the file is local
if (!isset($url['scheme']) && (count($url) > 0)) {
// convert the URl is required

View File

@@ -453,6 +453,55 @@ class Collection extends Iterator
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;
}
}

View File

@@ -55,6 +55,7 @@ class ImageFile extends Image
$cacheFile .= $this->prettyName;
}
$cacheFile .= '.'.$type;
// If the files does not exists, save it
@@ -79,7 +80,8 @@ class ImageFile extends Image
// Asking the cache for the cacheFile
try {
$file = $this->cache->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
$perms = octdec(self::getGrav()['config']->get('system.images.cache_perms', '0755'));
$file = $this->cache->setDirectoryMode($perms)->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
} catch (GenerationError $e) {
$file = $e->getNewFile();
}

View File

@@ -135,7 +135,7 @@ class ImageMedium extends Medium
*/
public function url($reset = true)
{
$output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->saveImage());
$output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $this->saveImage());
if ($reset) {
$this->reset();

View File

@@ -137,7 +137,7 @@ class Medium extends Data implements RenderableInterface
*/
public function url($reset = true)
{
$output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->get('filepath'));
$output = preg_replace('|^' . preg_quote(GRAV_ROOT) . '|', '', $this->get('filepath'));
if ($reset) {
$this->reset();
@@ -214,8 +214,9 @@ class Medium extends Data implements RenderableInterface
else
$style .= $key . ': ' . $value . ';';
}
if ($style)
if ($style) {
$attributes['style'] = $style;
}
if (empty($attributes['title'])) {
if (!empty($title)) {
@@ -392,6 +393,38 @@ class Medium extends Data implements RenderableInterface
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)

View File

@@ -5,6 +5,7 @@ use Exception;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Config\Config;
use Grav\Common\GravTrait;
use Grav\Common\Language\Language;
use Grav\Common\Utils;
use Grav\Common\Cache;
use Grav\Common\Twig;
@@ -381,7 +382,7 @@ class Page
*/
public function modifyHeader($key, $value)
{
$this->header->$key = $value;
$this->header->{$key} = $value;
}
/**
@@ -392,8 +393,7 @@ class Page
*/
public function summary($size = null)
{
/** @var Config $config */
$config = self::getGrav()['config']->get('site.summary');
$config = (array) self::getGrav()['config']->get('site.summary');
if (isset($this->header->summary)) {
$config = array_merge($config, $this->header->summary);
}
@@ -728,27 +728,30 @@ class Page
* You need to call $this->save() in order to perform the move.
*
* @param Page $parent New parent page.
* @return Page
* @return $this
*/
public function move(Page $parent)
{
$clone = clone $this;
$clone->_action = 'move';
$clone->_original = $this;
$clone->parent($parent);
$clone->id(time().md5($clone->filePath()));
if (!$this->_original) {
$clone = clone $this;
$this->_original = $clone;
}
$this->_action = 'move';
$this->parent($parent);
$this->id(time().md5($this->filePath()));
if ($parent->path()) {
$clone->path($parent->path() . '/' . $clone->folder());
$this->path($parent->path() . '/' . $this->folder());
}
if ($parent->route()) {
$clone->route($parent->route() . '/'. $clone->slug());
$this->route($parent->route() . '/'. $this->slug());
} else {
$clone->route(self::getGrav()['pages']->root()->route() . '/'. $clone->slug());
$this->route(self::getGrav()['pages']->root()->route() . '/'. $this->slug());
}
return $clone;
return $this;
}
/**
@@ -758,14 +761,14 @@ class Page
* You need to call $this->save() in order to perform the move.
*
* @param Page $parent New parent page.
* @return Page
* @return $this
*/
public function copy($parent)
{
$clone = $this->move($parent);
$clone->_action = 'copy';
$this->move($parent);
$this->_action = 'copy';
return $clone;
return $this;
}
/**
@@ -826,7 +829,7 @@ class Page
$blueprints = $this->blueprints();
$values = $blueprints->filter($this->toArray());
if ($values && isset($values['header'])) {
$this->header($values['header']);
$this->header($values['header']);
}
}
@@ -1805,11 +1808,13 @@ class Page
if (isset($routes[$uri_path])) {
$child_page = $pages->dispatch($uri->route())->parent();
if ($child_page) while (!$child_page->root()) {
if ($this->path() == $child_page->path()) {
return true;
if ($child_page) {
while (!$child_page->root()) {
if ($this->path() == $child_page->path()) {
return true;
}
$child_page = $child_page->parent();
}
$child_page = $child_page->parent();
}
}
@@ -1843,7 +1848,7 @@ class Page
/**
* Helper method to return a page.
*
* @param string $url the url of the page
* @param string $url the url of the page
* @param bool $all
*
* @return \Grav\Common\Page\Page page you were looking for if it exists
@@ -1951,7 +1956,7 @@ class Page
* @return mixed
* @internal
*/
protected function evaluate($value)
public function evaluate($value)
{
// Parse command.
if (is_string($value)) {
@@ -2002,16 +2007,16 @@ class Page
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;
@@ -2141,7 +2146,7 @@ class Page
*/
protected function doRelocation($reorder)
{
if (empty($this->_original) ) {
if (!$this->_original) {
return;
}
@@ -2182,7 +2187,7 @@ class Page
// Handle all the other pages.
$page = $pages->get($path);
if ($page && $page->exists() && $page->order() != $order+1) {
if ($page && $page->exists() && !$page->_action && $page->order() != $order+1) {
$page = $page->move($parent);
$page->order($order+1);
$page->save(false);
@@ -2190,11 +2195,12 @@ class Page
}
}
}
if ($this->_action == 'move' && $this->_original->exists()) {
Folder::move($this->_original->path(), $this->path());
}
if ($this->_action == 'copy' && $this->_original->exists()) {
Folder::copy($this->_original->path(), $this->path());
if (is_dir($this->_original->path())) {
if ($this->_action == 'move') {
Folder::move($this->_original->path(), $this->path());
} elseif ($this->_action == 'copy') {
Folder::copy($this->_original->path(), $this->path());
}
}
if ($this->name() != $this->_original->name()) {
@@ -2204,7 +2210,6 @@ class Page
}
}
$this->_action = null;
$this->_original = null;
}

View File

@@ -237,7 +237,7 @@ class Pages
/**
* Get a page instance.
*
* @param string $path
* @param string $path The filesystem full path of the page
* @return Page
* @throws \Exception
*/
@@ -264,7 +264,7 @@ class Pages
/**
* Dispatch URI to a page.
*
* @param $url
* @param string $url The relative URL of the page
* @param bool $all
* @return Page|null
*/
@@ -486,6 +486,36 @@ class Pages
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 @@ class Pages
/** @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;
}
/**

View File

@@ -104,8 +104,12 @@ class Plugins extends Iterator
continue;
}
$type = $directory->getBasename();
$list[$type] = self::get($type);
$plugin = $directory->getBasename();
$result = self::get($plugin);
if ($result) {
$list[$plugin] = $result;
}
}
}
ksort($list);
@@ -120,9 +124,9 @@ class Plugins extends Iterator
$blueprint->name = $name;
// Load default configuration.
$file = CompiledYamlFile::instance("plugins://{$name}/{$name}.yaml");
$file = CompiledYamlFile::instance("plugins://{$name}/{$name}" . YAML_EXT);
// ensure the plugin exists physically
// ensure this is a valid plugin
if (!$file->exists()) {
return null;
}

View File

@@ -1,10 +1,15 @@
<?php
namespace Grav\Common\Service;
use Grav\Common\Config\CompiledBlueprints;
use Grav\Common\Config\CompiledConfig;
use Grav\Common\Config\CompiledLanguages;
use Grav\Common\Config\Config;
use Grav\Common\Config\ConfigFileFinder;
use Grav\Common\Config\Setup;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use RocketTheme\Toolbox\Blueprints\Blueprints;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* The Config class contains configuration information.
@@ -14,51 +19,99 @@ use RocketTheme\Toolbox\Blueprints\Blueprints;
*/
class ConfigServiceProvider implements ServiceProviderInterface
{
private $environment;
private $setup;
public function register(Container $container)
{
$self = $this;
// 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) use ($self) {
return $self->loadMasterBlueprints($c);
$container['setup'] = function ($c) {
return static::setup($c)->init();
};
$container['config'] = function ($c) use ($self) {
return $self->loadMasterConfig($c);
$container['blueprints'] = function ($c) {
return static::blueprints($c);
};
$container['config'] = function ($c) {
return static::load($c);
};
$container['languages'] = function ($c) {
return static::languages($c);
};
}
public function loadMasterConfig(Container $container)
public static function setup(Container $container)
{
$environment = $this->getEnvironment($container);
$config = new Config($this->setup, $container, $environment);
return $config;
return new Setup($container);
}
public function loadMasterBlueprints(Container $container)
public static function blueprints(Container $container)
{
$environment = $this->getEnvironment($container);
$file = CACHE_DIR . 'compiled/blueprints/master-'.$environment.'.php';
$data = is_file($file) ? (array) include $file : [];
/** Setup $setup */
$setup = $container['setup'];
return new Blueprints($data, $container);
/** @var UniformResourceLocator $locator */
$locator = $container['locator'];
$cache = $locator->findResource('cache://compiled/blueprints', true, true);
$files = [];
$paths = $locator->findResources('blueprints://config');
$files += (new ConfigFileFinder)->locateFiles($paths);
$paths = $locator->findResources('plugins://');
$files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'blueprints');
$blueprints = new CompiledBlueprints($cache, $files, GRAV_ROOT);
return $blueprints->name("master-{$setup->environment}")->load();
}
public function getEnvironment(Container $container)
public static function load(Container $container)
{
if (!isset($this->environment)) {
$this->environment = $container['uri']->environment();
/** Setup $setup */
$setup = $container['setup'];
/** @var UniformResourceLocator $locator */
$locator = $container['locator'];
$cache = $locator->findResource('cache://compiled/config', true, true);
$files = [];
$paths = $locator->findResources('config://');
$files += (new ConfigFileFinder)->locateFiles($paths);
$paths = $locator->findResources('plugins://');
$files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths);
$config = new CompiledConfig($cache, $files, GRAV_ROOT);
$config->setBlueprints(function() use ($container) {
return $container['blueprints'];
});
return $config->name("master-{$setup->environment}")->load();
}
public static function languages(Container $container)
{
/** Setup $setup */
$setup = $container['setup'];
/** @var Config $config */
$config = $container['config'];
/** @var UniformResourceLocator $locator */
$locator = $container['locator'];
$cache = $locator->findResource('cache://compiled/languages', true, true);
$files = [];
// Process languages only if enabled in configuration.
if ($config->get('system.languages.translations', true)) {
$paths = $locator->findResources('languages://');
$files += (new ConfigFileFinder)->locateFiles($paths);
$paths = $locator->findResources('plugins://');
$files += (new ConfigFileFinder)->setBase('plugins')->locateInFolders($paths, 'languages');
}
return $this->environment;
$languages = new CompiledLanguages($cache, $files, GRAV_ROOT);
return $languages->name("master-{$setup->environment}")->load();
}
}

View File

@@ -1,7 +1,7 @@
<?php
namespace Grav\Common\Service;
use Grav\Common\Config\Config;
use Grav\Common\Config\Setup;
use Pimple\Container;
use RocketTheme\Toolbox\DI\ServiceProviderInterface;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
@@ -14,18 +14,18 @@ class StreamsServiceProvider implements ServiceProviderInterface
public function register(Container $container)
{
$container['locator'] = function($c) {
$locator = new UniformResourceLocator(ROOT_DIR);
$locator = new UniformResourceLocator(GRAV_ROOT);
/** @var Config $config */
$config = $c['config'];
$config->initializeLocator($locator);
/** @var Setup $setup */
$setup = $c['setup'];
$setup->initializeLocator($locator);
return $locator;
};
$container['streams'] = function($c) {
/** @var Config $config */
$config = $c['config'];
/** @var Setup $setup */
$setup = $c['setup'];
/** @var UniformResourceLocator $locator */
$locator = $c['locator'];
@@ -34,7 +34,7 @@ class StreamsServiceProvider implements ServiceProviderInterface
Stream::setLocator($locator);
ReadOnlyStream::setLocator($locator);
return new StreamBuilder($config->getStreams($c));
return new StreamBuilder($setup->getStreams($c));
};
}
}

View File

@@ -25,6 +25,8 @@ class Themes extends Iterator
public function __construct(Grav $grav)
{
parent::__construct();
$this->grav = $grav;
$this->config = $grav['config'];
@@ -34,13 +36,18 @@ class Themes extends Iterator
public function init()
{
/** @var EventDispatcher $events */
$events = $this->grav['events'];
/** @var Themes $themes */
$themes = $this->grav['themes'];
$themes->configure();
$this->initTheme();
}
public function initTheme()
{
/** @var Themes $themes */
$themes = $this->grav['themes'];
try {
$instance = $themes->load();
} catch (\InvalidArgumentException $e) {
@@ -48,10 +55,15 @@ class Themes extends Iterator
}
if ($instance instanceof EventSubscriberInterface) {
/** @var EventDispatcher $events */
$events = $this->grav['events'];
$events->addSubscriber($instance);
}
$this->grav['theme'] = $instance;
$this->grav->fireEvent('onThemeInitialized');
}
/**
@@ -74,8 +86,12 @@ class Themes extends Iterator
continue;
}
$type = $directory->getBasename();
$list[$type] = self::get($type);
$theme = $directory->getBasename();
$result = self::get($theme);
if ($result) {
$list[$theme] = $result;
}
}
}
ksort($list);
@@ -100,14 +116,20 @@ class Themes extends Iterator
$blueprint = $blueprints->get("{$name}/blueprints");
$blueprint->name = $name;
// Load default configuration.
$file = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT);
// ensure this is a valid theme
if (!$file->exists()) {
return null;
}
// Find thumbnail.
$thumb = "themes://{$name}/thumbnail.jpg";
if ($path = $this->grav['locator']->findResource($thumb, false)) {
$blueprint->set('thumbnail', $this->grav['base_url'] . '/' . $path);
}
// Load default configuration.
$file = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT);
$obj = new Data($file->content(), $blueprint);
// Override with user configuration.

View File

@@ -355,6 +355,12 @@ class TwigExtension extends \Twig_Extension
$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}";

View File

@@ -0,0 +1,127 @@
<?php
namespace Grav\Common\User;
use Grav\Common\Data\Blueprints;
use Grav\Common\Data\Data;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\GravTrait;
use Grav\Common\Utils;
/**
* Group object
*
* @property mixed authenticated
* @property mixed password
* @property bool|string hashed_password
* @author RocketTheme
* @license MIT
*/
class Group extends Data
{
use GravTrait;
/**
* Get the groups list
*
* @return array
*/
private static function groups()
{
$groups = self::getGrav()['config']->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;
}
}

View File

@@ -32,6 +32,9 @@ class User extends Data
{
$locator = self::getGrav()['locator'];
// force lowercase of username
$username = strtolower($username);
$blueprints = new Blueprints('blueprints://');
$blueprint = $blueprints->get('user/account');
$file_path = $locator->findResource('account://' . $username . YAML_EXT);
@@ -53,7 +56,7 @@ class User extends Data
* 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 +86,10 @@ class User extends Data
// Plain-text passwords do not match, we know we should fail but execute
// verify to protect us from timing attacks and return false regardless of
// the result
Authentication::verify($password, self::getGrav()['config']->get('system.security.default_hash'));
Authentication::verify(
$password,
self::getGrav()['config']->get('system.security.default_hash', '$2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK')
);
return false;
} else {
// Plain-text does match, we can update the hash and proceed
@@ -146,7 +152,28 @@ class User extends Data
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;
}
/**

View File

@@ -15,6 +15,8 @@ abstract class Utils
{
use GravTrait;
protected static $nonces = [];
/**
* @param string $haystack
* @param string $needle
@@ -217,9 +219,11 @@ abstract class Utils
$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 @@ abstract class Utils
}
}
/**
* 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 @@ abstract class Utils
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 @@ abstract class Utils
if (isset(self::getGrav()['user'])) {
$user = self::getGrav()['user'];
$username = $user->username;
if (isset($_SERVER['REMOTE_ADDR'])) {
$username .= $_SERVER['REMOTE_ADDR'];
}
} else {
$username = false;
}
if (!$username) {
$username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
}
@@ -450,7 +471,7 @@ abstract class Utils
$i++;
}
return ( $i . '|' . $action . '|' . $username . '|' . $token );
return ( $i . '|' . $action . '|' . $username . '|' . $token . '|' . self::getGrav()['config']->get('security.salt'));
}
/**
@@ -467,19 +488,6 @@ abstract class Utils
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 @@ abstract class Utils
*/
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 @@ abstract class Utils
*/
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;
}
}

View File

@@ -48,7 +48,7 @@ class SandboxCommand extends ConsoleCommand
'/.editorconfig' => '/.editorconfig',
'/.gitignore' => '/.gitignore',
'/CHANGELOG.md' => '/CHANGELOG.md',
'/LICENSE' => '/LICENSE',
'/LICENSE.txt' => '/LICENSE.txt',
'/README.md' => '/README.md',
'/index.php' => '/index.php',
'/composer.json' => '/composer.json',

View File

@@ -21,6 +21,16 @@ class IndexCommand extends ConsoleCommand
*/
protected $gpm;
/**
* @var
*/
protected $options;
/**
* @var array
*/
protected $sortKeys = ['name', 'slug', 'author', 'date'];
/**
*
*/
@@ -34,6 +44,43 @@ class IndexCommand extends ConsoleCommand
InputOption::VALUE_NONE,
'Force re-fetching the data from remote'
)
->addOption(
'filter',
'F',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Allows to limit the results based on one or multiple filters input. This can be either portion of a name/slug or a regex'
)
->addOption(
'themes-only',
'T',
InputOption::VALUE_NONE,
'Filters the results to only Themes'
)
->addOption(
'plugins-only',
'P',
InputOption::VALUE_NONE,
'Filters the results to only Plugins'
)
->addOption(
'updates-only',
'U',
InputOption::VALUE_NONE,
'Filters the results to Updatable Themes and Plugins only'
)
->addOption(
'sort',
's',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Allows to sort (ASC) the results based on one or multiple keys. SORT can be either "name", "slug", "author", "date"',
['date']
)
->addOption(
'desc',
'D',
InputOption::VALUE_NONE,
'Reverses the order of the output.'
)
->setDescription("Lists the plugins and themes available for installation")
->setHelp('The <info>index</info> command lists the plugins and themes available for installation')
;
@@ -44,16 +91,21 @@ class IndexCommand extends ConsoleCommand
*/
protected function serve()
{
$this->gpm = new GPM($this->input->getOption('force'));
$this->options = $this->input->getOptions();
$this->gpm = new GPM($this->options['force']);
$this->data = $this->gpm->getRepository();
$this->output->writeln('');
foreach ($this->data as $type => $packages) {
$data = $this->filter($this->data);
foreach ($data as $type => $packages) {
$this->output->writeln("<green>" . ucfirst($type) . "</green> [ " . count($packages) . " ]");
$index = 0;
$index = 0;
$packages = $this->sort($packages);
foreach ($packages as $slug => $package) {
$this->output->writeln(
// index
@@ -108,4 +160,65 @@ class IndexCommand extends ConsoleCommand
return '';
}
/**
* @param $data
*
* @return mixed
*/
public function filter($data)
{
// filtering and sorting
if ($this->options['plugins-only']) {
unset($data['themes']);
}
if ($this->options['themes-only']) {
unset($data['plugins']);
}
if ($this->options['filter'] || $this->options['updates-only'] || $this->options['desc']) {
foreach ($data as $type => $packages) {
foreach ($packages as $slug => $package) {
$filter = true;
// Filtering by string
if ($this->options['filter']) {
$filter = preg_grep('/(' . (implode('|', $this->options['filter'])) . ')/i', [$slug, $package->name]);
}
// Filtering updatables only
if ($this->options['updates-only'] && $filter) {
$method = ucfirst(preg_replace("/s$/", '', $package->package_type));
$filter = $this->gpm->{'is' . $method . 'Updatable'}($package->slug);
}
if (!$filter) {
unset($data[$type][$slug]);
}
}
}
}
return $data;
}
/**
* @param $packages
*/
public function sort($packages)
{
foreach ($this->options['sort'] as $key) {
$packages = $packages->sort(function ($a, $b) use ($key) {
switch ($key) {
case 'author':
return strcmp($a->{$key}['name'], $b->{$key}['name']);
break;
default:
return strcmp($a->$key, $b->$key);
}
}, $this->options['desc'] ? true : false);
}
return $packages;
}
}

View File

@@ -46,7 +46,7 @@
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="vendor" stopProcessing="true">
<match url="^vendor/(.*)$" ignoreCase="false" />
<match url="^vendor/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
</rules>