Compare commits

...

283 Commits

Author SHA1 Message Date
Andy Miller
2bb23efa7c Merge branch 'release/1.0.1' 2015-12-11 15:38:45 -07:00
Andy Miller
def726a012 version update 2015-12-11 15:38:08 -07:00
Andy Miller
7d1291e2b9 Fixed cleanup to take into account symfony folder changes 2015-12-11 15:17:01 -07:00
Andy Miller
a0297e9d65 Fix error that can results from enabling debugger from admin 2015-12-11 15:00:54 -07:00
Andy Miller
e4e0c06ea8 Merge branch 'release/1.0.0' 2015-12-11 14:00:21 -07:00
Andy Miller
f46ad2d032 Merge branch 'release/1.0.0' into develop 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
Andy Miller
50c6e81c09 Merge branch 'release/1.0.0-rc.5' 2015-11-20 17:54:39 -07:00
Andy Miller
2a0a9a225c version update 2015-11-20 17:54:28 -07:00
Andy Miller
aee92b58c7 Updated composer.phar 2015-11-20 17:34:34 -07:00
Andy Miller
16db950009 composer updates 2015-11-20 17:34:02 -07:00
Andy Miller
bde33e7188 Lighttpd/Lightly configuration file - Thanks @Mr3ase 2015-11-20 14:59:58 -07:00
Djamil Legato
267efbe164 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-11-20 12:40:52 -08:00
Djamil Legato
f9e137c994 Added pad filter for strings (uses str_pad) 2015-11-20 12:40:48 -08:00
Andy Miller
e7f9751403 add new Page.relativePagePath helper method 2015-11-20 08:08:39 -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
Djamil Legato
06d663680c Removed unused label 2015-11-19 20:05:29 -08:00
Djamil Legato
1bbdca5032 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-11-19 18:07:40 -08:00
Djamil Legato
c1654a988e Fixed deprecated message 2015-11-19 18:07:16 -08:00
Andy Miller
18a540c867 Don't set a default hash in system.yaml 2015-11-19 18:56:05 -07:00
Andy Miller
99fc8df322 old command was called newuser not new-user, also pointed to login plugin now. 2015-11-19 18:30:31 -07:00
Flavio Copes
2d21cb8b1e Simplify, clear slashes for all nonces automatically. Remove Utils:: getNonceForGetRequest methoid
The reason is, we need to get the nonce in JavaScript and we can simply
use the one in the form, made for POST requests
2015-11-19 23:23:32 +01:00
Flavio Copes
d8008654b9 Add a new Utils::getNonceForGetRequest() method, and use that in Uri:: addNonce() 2015-11-19 22:44:10 +01:00
Andy Miller
146295fb1e fix for state check if user was already logged in without state 2015-11-19 13:43:15 -07: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
Djamil Legato
63e083ea37 Implemented new state check for accounts. If an account state is set to disabled no actions will be allowed 2015-11-18 18:31:19 -08:00
Andy Miller
b1630feb5d Disable time limit in case of slow downloads: #385 2015-11-18 19:04:57 -07:00
Andy Miller
1185a91c90 Fix for Media using absolute URLs internally causing breakages in functionality: #401 2015-11-18 18:39:30 -07:00
Andy Miller
dce6d7894b Added form blueprints for new append_url_extension field in system.yaml and page headers 2015-11-18 17:35:07 -07:00
Andy Miller
375ee0d1fa remove version from auto-generated generator tag 2015-11-18 17:17:53 -07:00
Andy Miller
ce0574f897 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-11-18 17:13:31 -07:00
Andy Miller
c515111446 Don't check valid media list if its' in whitelist 2015-11-18 17:13:22 -07:00
Djamil Legato
7d6393628e Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-11-18 16:12:12 -08:00
Djamil Legato
24fde7261a Deprecated: bin/grav new-user is now deprecated in favor of bin/plugin admin new-user 2015-11-18 16:12:08 -08:00
Djamil Legato
3d922abf1a Fixed listing duplicate plugins when multiple commands present. 2015-11-18 16:11:44 -08:00
Andy Miller
7f1d3a94fe Support default case of allowing all valid media types. Config option replaces with a whitelist of types supported. #452 2015-11-18 15:52:01 -07:00
Andy Miller
3d774b7585 moved fallback types to media 2015-11-18 15:50:46 -07:00
Djamil Legato
b0083548b6 Rearranged properly changelog entries 2015-11-18 11:55:34 -08:00
Djamil Legato
905dae3b16 Updated changelog 2015-11-18 11:51:16 -08:00
Djamil Legato
d79979371b Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-11-18 11:47:26 -08:00
Flavio Copes
c4bff94f7d Merge pull request #454 from diomed/patch-1
Croatian translation
2015-11-18 20:43:07 +01:00
Kruno H
7c4fd3858c Croatian translation 2015-11-18 20:35:23 +01:00
Djamil Legato
3b9af8883d Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-11-18 10:43:02 -08:00
Djamil Legato
29b34d7de0 Listing all plugins with CLI available. Better helper when empty arguments 2015-11-18 10:40:06 -08: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
Flavio Copes
5193551d04 Fix date representation in system config
Fixes https://github.com/getgrav/grav-plugin-admin/issues/278
2015-11-18 17:15:18 +01:00
Matias Griese
d2660e0755 Fixed gzip compression making it to work correctly with all servers and browsers 2015-11-18 18:14:35 +02:00
Flavio Copes
2d8ac27fdd Merge pull request #451 from yaman-jain/docfixes
PhpDoc
2015-11-18 14:49:58 +01: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
yaman-jain
49a5b38589 PhpDoc: callback definition as per #451 2015-11-18 16:01:19 +05:30
yaman-jain
6e2f792bb9 Merge branch 'develop' into docfixes 2015-11-18 15:55:25 +05:30
Djamil Legato
da098fd46a Implemented support for Plugins to hook into Grav CLI via bin/plugin <plugin-name> 2015-11-17 19:33:55 -08:00
Djamil Legato
3e081b340f Cleaned up Console commands, now extending Grav's own ConsoleCommand class 2015-11-17 19:31:13 -08:00
Djamil Legato
698015a03d Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-11-17 19:12:45 -08:00
Djamil Legato
000a10f936 Added check in Plugins::get method to ensure a plugin exists 2015-11-17 18:36:08 -08:00
Flavio Copes
c764e31c8a Merge pull request #448 from bovisp/patch-3
Added form validation translations
2015-11-17 22:32:38 +01:00
Flavio Copes
c18021d52a Merge pull request #450 from bovisp/patch-2
Added a translation for "Invalid input in " text
2015-11-17 22:32:15 +01:00
Flavio Copes
3d0cc67415 Merge pull request #449 from bovisp/patch-1
Added a translation for "Validation failed:" text
2015-11-17 22:32:00 +01:00
Paul Bovis
3f94a6fda9 moved $language variable inside catch statement
As requested
2015-11-17 14:25:51 -07:00
yaman-jain
7f0eefbde5 updated/added PhpDoc for methods 2015-11-18 02:32:44 +05:30
Paul Bovis
280377985f Added form validation translations
These will need to be translated into other languages.
2015-11-17 11:57:15 -07:00
Paul Bovis
d5b3f070a5 Added a translation for "Invalid input in " text
The English phrase "Invalid input in " was hardcoded at line 40. I created a new translation 'FORM.INVALID_INPUT' and placed it in /system/languages/en.yaml. It will need to be translated into other languages by others.
2015-11-17 11:55:31 -07:00
Paul Bovis
3505ef046d Added a translation for "Validation failed:" text
The English term "Validation failed:" was hard coded on line 83. Instead, I created a translation and placed this at /system/languages/en.yaml. It will need to be translated into other languages by others.
2015-11-17 11:50:47 -07:00
Flavio Copes
a6bc565356 Replace forward slashes automatically to avoid problems when used in GET request 2015-11-17 11:59:30 +01:00
Flavio Copes
a1ee3cf4e4 Avoid using our own hash, let PHP use its own for password_hash, fix #445 2015-11-17 11:59:02 +01:00
Andy Miller
e96445abe3 Added append_url_extension option to system/page headers. 2015-11-16 21:33:24 -07:00
Andy Miller
c22fae0d3d just added a comment 2015-11-16 21:33:01 -07: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
Flavio Copes
3cf6e8762c Merge pull request #426 from getgrav/feature/nonce
Add nonce functionality
2015-11-13 19:18:29 +01:00
Flavio Copes
f0cdd7c03e Merge branch 'develop' into feature/nonce 2015-11-13 18:24:03 +01:00
Andy Miller
5e40201888 Merge pull request #441 from yaman-jain/removePackageInterface
PackageInterface was deleted #435
2015-11-13 11:03:08 -06:00
Flavio Copes
f2c2debb28 Fix exception message when label is not set 2015-11-13 17:50:04 +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
Andy Miller
0e3e7497ac Fix for https://github.com/getgrav/grav-plugin-form/issues/17 2015-11-12 18:31:15 -07:00
Andy Miller
72313ac9ec Merge branch 'feature/new-imagemedium-methods' into develop 2015-11-12 17:40:21 -07:00
yaman-jain
dc80228f0b PackageInterface was deleted #435 2015-11-13 00:48:54 +05:30
Flavio Copes
a83642a7e3 Added a higherQualityAlternative () method to get the highest quality image available (3x or 2x) 2015-11-12 19:15:26 +01:00
Flavio Copes
00d8403095 Allow to get the image quality in addition to setting it 2015-11-12 19:14:52 +01:00
Matias Griese
e1ec8e9742 Fix undefined variable in Config class 2015-11-12 20:09:23 +02:00
Flavio Copes
0725af5367 Handle case login plugin disabled (thanks @hwmaier) 2015-11-12 09:17:22 +01:00
Andy Miller
65e543af02 Merge pull request #434 from yaman-jain/feature/smallfixes
remove duplicate key
2015-11-11 18:24:29 -07:00
Andy Miller
b49e8315eb Merge pull request #439 from enko/patch-1
Use PCRE_UTF8 so unicode strings don't break up.
2015-11-12 00:42:45 +01:00
Tim
c5a89112b4 Use PCRE_UTF8 so unicode strings don't break up.
The `Truncator`-class fails when the truncated text contains unicode characters with a nice exception:

    "DOMDocumentFragment::appendXML(): Entity: line 1: parser error : Input is not proper UTF-8, indicate encoding ! Bytes: 0xC3 0xE2 0x80 0xA6"

By using the `PCRE_UTF8` modifier `u` everything is UTF8 and all is fine.
2015-11-12 00:13:21 +01:00
Flavio Copes
94ec474ffa Merge branch 'develop' into feature/nonce 2015-11-11 17:47:57 +01:00
Flavio Copes
da4593fdc1 Drop commented referrer code 2015-11-11 17:13:54 +01:00
Flavio Copes
b8413cefaf Avoid having to deal with slashes in URLs 2015-11-10 17:34:23 +01:00
Flavio Copes
d3097e4fd0 Merge pull request #433 from Sommerregen/patch-3
Fixes #432 (Theme autoloading doesn't seem to work)
2015-11-10 14:32:14 +01:00
yaman-jain
51753f0716 remove unused 'use' statements 2015-11-10 18:24:01 +05:30
yaman-jain
b7ada873b8 remove duplicate key 2015-11-10 17:34:01 +05:30
Benny
ed8b08a9e4 Fixes #432 (Theme autoloading doesn't seem to work) 2015-11-10 11:19:30 +01:00
Flavio Copes
e1fdb6803d Add nonce functionality 2015-11-06 15:31:49 +01:00
Andy Miller
5cb9f2f42f Merge pull request #419 from hwmaier/feature/empty-inline-assets
Don't create <style> or <script> tags for empty inline CSS or inline JS
2015-11-06 10:08:10 +05:30
Flavio Copes
fcf48ed2e5 Merge pull request #420 from bariscelik/develop
added "Turkish" language
2015-11-05 09:16:36 +01:00
Barış ÇELİK
050512536a added "Turkish" language 2015-11-05 08:42:24 +03:00
Henrik Maier
99207fca13 Don't create <style> or <script> tags for empty inline CSS or inline JS 2015-11-05 13:37:06 +10: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
Andy Miller
77d80f12f3 Fix for untranslated validation messages #246 2015-11-02 17:28:46 -07:00
Flavio Copes
e400207a65 Fix https://github.com/getgrav/grav-plugin-admin/issues/249, password not required any more. If not set, it's not changed. If set, it's changed (if it satisfies the requirements) 2015-11-02 18:57:16 +01:00
Andy Miller
4b68036a1b Fixed assets stream for bin/grav clear 2015-11-01 20:52:14 -07:00
Henrik Maier
a95b716aa7 Return resource even if not physically present 2015-11-01 19:43:54 -07:00
Henrik Maier
dc8efded34 Use streams instead of hardcoded paths for clearCache() 2015-11-01 19:43:53 -07:00
Andy Miller
e016b17276 Revert "Use streams instead of hardcoded paths for clearCache()"
This reverts commit a86ce7cb28.
2015-11-01 19:24:46 -07:00
Andy Miller
b99876f0b4 Revert "Return resource even if not physically present"
This reverts commit b3144ee921.
2015-11-01 19:24:39 -07:00
Andy Miller
66abc842b7 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2015-11-01 18:34:41 -07:00
Andy Miller
9f36158c67 Merge pull request #405 from hwmaier/feature/trailing-slash
Added support for default routes with a trailing slash
2015-11-01 18:34:31 -07:00
Andy Miller
18c1ca3919 added 'theme' to config containing current theme configuration so now possible to just to {{ config.theme.dropdown_enabled }} #406 2015-11-01 18:32:55 -07:00
Andy Miller
0c729e5b0a latest vendor updates 2015-11-01 13:56:10 -07:00
Andy Miller
55f3b78ab1 Merge pull request #393 from hwmaier/feature/image-height-width
Support for width/height and style attributes to media and image files
2015-11-01 09:43:28 -07:00
Andy Miller
a73b796ca7 Merge pull request #407 from hwmaier/feature/enable-progressive-bug
Clear previously applied operations when doing a reset on image files to avoid calling filters twice
2015-11-01 09:42:29 -07:00
Andy Miller
bb0bca7ef1 Merge pull request #408 from hwmaier/feature/clear-cache
Use streams instead of hardcoded paths for clearCache()
2015-11-01 08:53:54 -07:00
Henrik Maier
b3144ee921 Return resource even if not physically present 2015-11-01 18:55:33 +10:00
Henrik Maier
a86ce7cb28 Use streams instead of hardcoded paths for clearCache() 2015-11-01 17:50:23 +10:00
Henrik Maier
ccf2a780b6 Clear previously applied operations when doing a reset on image files 2015-11-01 10:26:56 +10:00
Henrik Maier
b0c1dbe4b7 Added support for default routes with a trailing slash 2015-10-31 16:21:05 +10:00
Djamil Legato
ec73eef695 Updated comments for subfolders 2015-10-30 10:49:08 -07:00
Andy Miller
467d68344e added a default umask_fix property to system.yaml 2015-10-30 11:42:36 -06:00
Andy Miller
8899b3ebb8 Rewrote nginx.conf to be simpler and more in line with .htaccess 2015-10-30 11:42:08 -06:00
Andy Miller
5478cfaf9f Improved .htaccess security 2015-10-30 11:41:52 -06:00
Andy Miller
4b6a85f30a updated vendor libs 2015-10-30 11:41:03 -06:00
Andy Miller
a045107cc7 Merge branch 'release/1.0.0-rc.4' into develop 2015-10-29 22:11:19 -06:00
Henrik Maier
bb16dbab78 Remove debug error_log output 2015-10-29 13:02:22 +10:00
Henrik Maier
8f9671ad32 Allow width and height attributes to be added to Markdown and Twig image tags 2015-10-29 12:53:00 +10:00
Henrik Maier
c87e3f419d Allow inline styles to be added to Markdown and Twig image tags 2015-10-29 12:52:24 +10: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
104 changed files with 3752 additions and 2026 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

@@ -44,15 +44,17 @@ RewriteRule .* index.php [L]
## Begin - Security
# Block all direct access for these folders
RewriteRule ^(.git|cache|bin|logs|backup)/(.*) error [L]
# Block access to specific file types for these folders
RewriteRule ^(system|user|vendor)/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$ error [L]
RewriteRule ^(.git|cache|bin|logs|backup)/(.*) error [F]
# Block access to specific file types for these system folders
RewriteRule ^(system|vendor)/(.*)\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$ error [F]
# Block access to specific file types for these user folders
RewriteRule ^(user)/(.*)\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ error [F]
# Block all direct access to .md files:
RewriteRule \.md$ error [L]
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)$ 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,110 @@
# v1.0.1
## 12/11/2015
1. [](#improved)
* Reduced package sizes by removing extra vendor dev bits
1. [](#bugfix)
* Fix issue when you enable debugger from admin plugin
# 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
1. [](#new)
* Added **nonce** functionality for all admin forms for improved security
* 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 `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
* Added new `|pad` Twig filter for strings (uses `str_pad()`)
* Added `lighttpd.conf` for Lightly web server
1. [](#improved)
* Clear previously applied operations when doing a reset on image media
* Password no longer required when editing user
* Improved support for trailing `/` URLs
* Improved `.nginx.conf` configuration file
* Improved `.htaccess` security
* Updated vendor libs
* Updated `composer.phar`
* Use streams instead of paths for `clearCache()`
* Use PCRE_UTF8 so unicode strings can be regexed in Truncator
* Handle case when login plugin is disabled
* Improved `quality` functionality in media handling
* Added some missing translation strings
* Deprecated `bin/grav newuser` in favor of `bin/plugin login new-user`
* Moved fallback types to use any valid media type
* Renamed `system.pages.fallback_types` to `system.media.allowed_fallback_types`
* Removed version number in default `generator` meta tag
* Disable time limit in case of slow downloads
* Removed default hash in `system.yaml`
1. [](#bugfix)
* Fix for media using absolute URLs causing broken links
* Fix theme auto-loading #432
* Don't create empty `<style>` or `<script>` scripts if no data
* Code cleanups
* Fix undefined variable in Config class
* Fix exception message when label is not set
* Check in `Plugins::get()` to ensure plugins exists
* Fixed GZip compression making output buffering work correctly with all servers and browsers
* Fixed date representation in system config
# v1.0.0-rc.4
## 10/29/2015
@@ -9,7 +116,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)
@@ -34,7 +141,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/

Binary file not shown.

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(

116
bin/plugin Executable file
View File

@@ -0,0 +1,116 @@
#!/usr/bin/env php
<?php
define('GRAV_CLI', true);
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
}
if (!file_exists(__DIR__ . '/../vendor')) {
require_once __DIR__ . '/../system/src/Grav/Common/Composer.php';
}
use Grav\Common\Composer;
if (!file_exists(__DIR__ . '/../vendor')) {
// Before we can even start, we need to run composer first
$composer = Composer::getComposerExecutor();
echo "Preparing to install vendor dependencies...\n\n";
echo system($composer . ' --working-dir="' . __DIR__ . '/../" --no-interaction --no-dev --prefer-dist -o install');
echo "\n\n";
}
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Grav\Common\Grav;
use Grav\Common\Filesystem\Folder;
$autoload = require_once(__DIR__ . '/../vendor/autoload.php');
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
if (!file_exists(ROOT_DIR . 'index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}
$grav = Grav::instance(array('loader' => $autoload));
$grav['config']->init();
$grav['streams'];
$grav['plugins']->init();
$grav['themes']->init();
$app = new Application('Grav Plugins Commands', GRAV_VERSION);
$pattern = '([A-Z]\w+Command\.php)';
// get arguments and strip the application name
if (null === $argv) {
$argv = $_SERVER['argv'];
}
$bin = array_shift($argv);
$name = array_shift($argv);
$argv = array_merge([$bin], $argv);
$input = new ArgvInput($argv);
$plugin = $grav['plugins']->get($name);
$output = new ConsoleOutput();
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold')));
$output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
if (!$name) {
$output->writeln('');
$output->writeln("<red>Usage:</red>");
$output->writeln(" {$bin} [slug] [command] [arguments]");
$output->writeln('');
$output->writeln("<red>Example:</red>");
$output->writeln(" {$bin} error log -l 1 --trace");
$list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm']);
if (count($list)) {
$available = [];
$output->writeln('');
$output->writeln('<red>Plugins with CLI available:</red>');
foreach ($list as $index => $entry) {
$split = explode('/', $entry);
$entry = array_shift($split);
$index = str_pad($index++ + 1, 2, '0', STR_PAD_LEFT);
if (in_array($entry, $available)) {
continue;
}
$available[] = $entry;
$output->writeln(' ' . $index . ". <red>" . str_pad($entry, 15) . "</red> <white>${bin} ${entry} list</white>");
}
}
exit;
}
if ($plugin === null) {
$output->writeln("<red>Grav Plugin <white>'{$name}'</white> is not installed</red>");
exit;
}
$path = 'plugins://' . $name . '/cli';
try {
$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;
}
foreach ($commands as $command) {
require_once "plugins://{$name}/cli/{$command}";
$command = 'Grav\Plugin\Console\\' . preg_replace('/.php$/', '', $command);
$app->add(new $command());
}
$app->run($input);

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": {

303
composer.lock generated
View File

@@ -1,23 +1,24 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "e1db721096772d41f16003b39b47c85a",
"hash": "09fcc6b4528be7d9c8af68a66e85f0b2",
"content-hash": "69bee250cbc5160401d50cc47c8d6aba",
"packages": [
{
"name": "doctrine/cache",
"version": "v1.4.2",
"version": "v1.5.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "8c434000f420ade76a07c64cbe08ca47e5c101ca"
"reference": "47c7128262da274f590ae6f86eb137a7a64e82af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/8c434000f420ade76a07c64cbe08ca47e5c101ca",
"reference": "8c434000f420ade76a07c64cbe08ca47e5c101ca",
"url": "https://api.github.com/repos/doctrine/cache/zipball/47c7128262da274f590ae6f86eb137a7a64e82af",
"reference": "47c7128262da274f590ae6f86eb137a7a64e82af",
"shasum": ""
},
"require": {
@@ -38,8 +39,8 @@
}
},
"autoload": {
"psr-0": {
"Doctrine\\Common\\Cache\\": "lib/"
"psr-4": {
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -74,7 +75,7 @@
"cache",
"caching"
],
"time": "2015-08-31 12:36:41"
"time": "2015-12-03 10:50:37"
},
{
"name": "donatj/phpuseragentparser",
@@ -168,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": {
@@ -208,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": "9a393ceb80f7497b6513feb574638e87048fed55"
"reference": "72538eeb70bbfb11964412a3d098d109efd012f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/9a393ceb80f7497b6513feb574638e87048fed55",
"reference": "9a393ceb80f7497b6513feb574638e87048fed55",
"url": "https://api.github.com/repos/filp/whoops/zipball/72538eeb70bbfb11964412a3d098d109efd012f7",
"reference": "72538eeb70bbfb11964412a3d098d109efd012f7",
"shasum": ""
},
"require": {
@@ -266,7 +267,7 @@
"whoops",
"zf2"
],
"time": "2015-09-27 09:47:06"
"time": "2015-06-29 05:42:04"
},
{
"name": "gregwar/cache",
@@ -403,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",
@@ -431,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/",
@@ -448,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",
@@ -665,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": {
@@ -710,30 +716,30 @@
"php",
"rockettheme"
],
"time": "2015-10-15 23:27:40"
"time": "2015-11-24 17:04:24"
},
{
"name": "symfony/console",
"version": "v2.7.5",
"version": "v2.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "06cb17c013a82f94a3d840682b49425cd00a2161"
"reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/06cb17c013a82f94a3d840682b49425cd00a2161",
"reference": "06cb17c013a82f94a3d840682b49425cd00a2161",
"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/phpunit-bridge": "~2.7",
"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-09-25 08:32:23"
"time": "2015-11-30 12:35:10"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.7.5",
"version": "v2.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9"
"reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ae4dcc2a8d3de98bd794167a3ccda1311597c5d9",
"reference": "ae4dcc2a8d3de98bd794167a3ccda1311597c5d9",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc",
"reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc",
"shasum": ""
},
"require": {
@@ -788,11 +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/phpunit-bridge": "~2.7",
"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": "",
@@ -801,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": [
@@ -825,27 +836,140 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2015-09-22 13:49:29"
"time": "2015-10-30 20:15:42"
},
{
"name": "symfony/var-dumper",
"version": "v2.7.5",
"name": "symfony/polyfill-iconv",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "ba8c9a0edf18f70a7efcb8d3eb35323a10263338"
"url": "https://github.com/symfony/polyfill-iconv.git",
"reference": "21a18998764e569c1675efc7191887130b319605"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/ba8c9a0edf18f70a7efcb8d3eb35323a10263338",
"reference": "ba8c9a0edf18f70a7efcb8d3eb35323a10263338",
"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": {
"symfony/phpunit-bridge": "~2.7"
"twig/twig": "~1.20|~2.0"
},
"suggest": {
"ext-symfony_debug": ""
@@ -853,7 +977,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
"dev-master": "2.8-dev"
}
},
"autoload": {
@@ -862,7 +986,10 @@
],
"psr-4": {
"Symfony\\Component\\VarDumper\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -884,38 +1011,38 @@
"debug",
"dump"
],
"time": "2015-09-22 14:41:01"
"time": "2015-11-18 13:45:00"
},
{
"name": "symfony/yaml",
"version": "v2.7.5",
"version": "v2.8.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770"
"reference": "f79824187de95064a2f5038904c4d7f0227fedb5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/31cb2ad0155c95b88ee55fe12bc7ff92232c1770",
"reference": "31cb2ad0155c95b88ee55fe12bc7ff92232c1770",
"url": "https://api.github.com/repos/symfony/yaml/zipball/f79824187de95064a2f5038904c4d7f0227fedb5",
"reference": "f79824187de95064a2f5038904c4d7f0227fedb5",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"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": [
@@ -933,20 +1060,20 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-09-14 14:14:09"
"time": "2015-11-30 12:35:10"
},
{
"name": "twig/twig",
"version": "v1.22.3",
"version": "v1.23.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "ebfc36b7e77b0c1175afe30459cf943010245540"
"reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/ebfc36b7e77b0c1175afe30459cf943010245540",
"reference": "ebfc36b7e77b0c1175afe30459cf943010245540",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/d9b6333ae8dd2c8e3fd256e127548def0bc614c6",
"reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6",
"shasum": ""
},
"require": {
@@ -959,7 +1086,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.22-dev"
"dev-master": "1.23-dev"
}
},
"autoload": {
@@ -994,15 +1121,13 @@
"keywords": [
"templating"
],
"time": "2015-10-13 07:07:02"
"time": "2015-11-05 12:49:06"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"filp/whoops": 20
},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {

View File

@@ -44,15 +44,17 @@ RewriteRule .* index.php [L]
## Begin - Security
# Block all direct access for these folders
RewriteRule ^(.git|cache|bin|logs|backup)/(.*) error [L]
# Block access to specific file types for these folders
RewriteRule ^(system|user|vendor)/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$ error [L]
RewriteRule ^(.git|cache|bin|logs|backup)/(.*) error [F]
# Block access to specific file types for these system folders
RewriteRule ^(system|vendor)/(.*)\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$ error [F]
# Block access to specific file types for these user folders
RewriteRule ^(user)/(.*)\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ error [F]
# Block all direct access to .md files:
RewriteRule \.md$ error [L]
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)$ error [F]
RewriteRule ^(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess)$ error [F]
## End - Security
</IfModule>

48
lighttpd.conf Normal file
View File

@@ -0,0 +1,48 @@
############# DO NOT FORGET TO CHANGE "grav_path" BY YOUR ACTUAL GRAV INSTALLATION FOLDER #############
############# IF GRAV IS AT THE ROOT OF YOUR WEBSITE, ie http://yoursite.tld POINTS TO #############
############# GRAV DIRECTLY, THEN JUST REMOVE ANY "/grav_path/" MENTION BELOW. OTHERWISE #############
############# WE ASSUME YOU RUN AN INSTALLATION SUCH AS http://yoursite.tld/grav_path/ #############
#######################################################################################################
### GRAV RULES FOR LIGHTTPD ###
### By Mr3ase ###
### Last Rev. 2015/11/20 ###
#PREVENTING EXPLOITS
$HTTP["querystring"] =~ "base64_encode[^(]*\([^)]*\)" {
url.redirect = (".*" => "/grav_path/index.php" )
}
$HTTP["querystring"] =~ "(<|%3C)([^s]*s)+cript.*(>|%3E)" {
url.redirect = (".*" => "/grav_path/index.php" )
}
$HTTP["querystring"] =~ "GLOBALS(=|\[|\%[0-9A-Z])" {
url.redirect = (".*" => "/grav_path/index.php" )
}
$HTTP["querystring"] =~ "_REQUEST(=|\[|\%[0-9A-Z])" {
url.redirect = (".*" => "/grav_path/index.php" )
}
#REROUTING TO THE INDEX PAGE
url.rewrite-if-not-file = (
"^/grav_path/(.*)$" => "/grav_path/index.php"
)
#IMPROVING SECURITY
$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)/(.*)" {
url.access-deny = ("")
}
$HTTP["url"] =~ "^/grav_path/(system|user|vendor)/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$" {
url.access-deny = ("")
}
$HTTP["url"] =~ "^/grav_path/(\.(.*))|(\.(.*)/)" {
url.access-deny = ("")
}
url.access-deny = (".md","~",".inc")
#PREVENT BROWSING AND SET INDEXES
$HTTP["url"] =~ "^/grav_path($|/)" {
dir-listing.activate = "disable"
index-file.names = ( "index.php", "index.html" , "index.htm" )
}

View File

@@ -1,87 +1,44 @@
worker_processes 1;
server {
#listen 80;
index index.html index.php;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location / {
root html;
index index.php;
if (!-e $request_filename){ rewrite ^(.*)$ /index.php last; }
}
# if you want grav in a sub-directory of your main site
# (for example, example.com/mygrav) then you need this rewrite:
location /mygrav {
index index.php;
if (!-e $request_filename){ rewrite ^(.*)$ /mygrav/$2 last; }
try_files $uri $uri/ /index.php?$args;
}
# if using grav in a sub-directory of your site,
# prepend the actual path to each location
# for example: /mygrav/images
# and: /mygrav/user
# and: /mygrav/cache
# and so on
location /images/ {
# Serve images as static
}
location /user {
rewrite ^/user/accounts/(.*)$ /error redirect;
rewrite ^/user/config/(.*)$ /error redirect;
rewrite ^/user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
location /cache {
rewrite ^/cache/(.*) /error redirect;
}
location /bin {
rewrite ^/bin/(.*)$ /error redirect;
}
location /backup {
rewrite ^/backup/(.*) /error redirect;
}
location /system {
rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
location /vendor {
rewrite ^/vendor/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
# Remember to change 127.0.0.1:9000 to the Ip/port
# you configured php-cgi.exe to run from
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
## Begin - Server Info
root /home/user/www/html;
server_name localhost;
## End - Server Info
## Begin - Index
# for subfolders, simply adjust:
# `location /subfolder {`
# and the rewrite to use `/subfolder/index.php`
location / {
try_files $uri $uri/ /index.html;
if (!-e $request_filename){ rewrite ^(.*)$ /index.php last; }
}
## End - Index
## Begin - PHP
location ~ \.php$ {
# Choose either a socket or TCP/IP address
fastcgi_pass unix:/var/run/php5-fpm.sock;
# fastcgi_pass 127.0.0.1:9000;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
}
## End - PHP
## Begin - Security
# deny all direct access for these folders
location ~* /(.git|cache|bin|logs|backups)/.*$ { return 403; }
# deny running scripts inside core system folders
location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# 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.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

@@ -73,7 +73,7 @@ form:
options:
"F jS \\a\\t g:ia": Date1
"l jS \\of F g:i A": Date2
"D, m M Y G:i:s": Date3
"D, d M Y G:i:s": Date3
"d-m-y G:i": Date4
"jS M Y": Date5
@@ -86,7 +86,7 @@ form:
options:
"F jS \\a\\t g:ia": Date1
"l jS \\of F g:i A": Date2
"D, m M Y G:i:s": Date3
"D, d M Y G:i:s": Date3
"d-m-y G:i": Date4
"jS M Y": Date5
@@ -142,6 +142,12 @@ form:
twig: Twig Events
use: keys
pages.append_url_extension:
type: text
placeholder: "e.g. .html"
label: PLUGIN_ADMIN.APPEND_URL_EXT
help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
pages.redirect_default_route:
type: toggle
label: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE
@@ -215,15 +221,6 @@ form:
validate:
type: bool
pages.fallback_types:
type: selectize
size: large
label: PLUGIN_ADMIN.FALLBACK_TYPES
help: PLUGIN_ADMIN.FALLBACK_TYPES_HELP
classes: fancy
validate:
type: commalist
languages:
type: section
title: PLUGIN_ADMIN.LANGUAGES
@@ -234,6 +231,7 @@ form:
languages.supported:
type: selectize
size: large
placeholder: "e.g. en, fr"
label: PLUGIN_ADMIN.SUPPORTED
help: PLUGIN_ADMIN.SUPPORTED_HELP
classes: fancy
@@ -518,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
@@ -690,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
@@ -720,6 +740,26 @@ form:
validate:
type: bool
media.allowed_fallback_types:
type: selectize
size: large
label: PLUGIN_ADMIN.FALLBACK_TYPES
help: PLUGIN_ADMIN.FALLBACK_TYPES_HELP
classes: fancy
validate:
type: commalist
media.unsupported_inline_types:
type: selectize
size: large
label: PLUGIN_ADMIN.INLINE_TYPES
help: PLUGIN_ADMIN.INLINE_TYPES_HELP
classes: fancy
validate:
type: commalist
session:
type: section
title: PLUGIN_ADMIN.SESSION
@@ -727,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,7 +29,7 @@ form:
content:
type: markdown
label: PLUGIN_ADMIN.CONTENT
showPreview: true
validate:
type: textarea
@@ -88,7 +88,6 @@ form:
placeholder_key: PLUGIN_ADMIN.METADATA_KEY
placeholder_value: PLUGIN_ADMIN.METADATA_VALUE
taxonomies:
type: section
title: PLUGIN_ADMIN.TAXONOMIES
@@ -264,6 +263,12 @@ form:
default: default
'@data-options': '\Grav\Common\Page\Pages::types'
header.append_url_extension:
type: text
label: PLUGIN_ADMIN.APPEND_URL_EXT
toggleable: true
help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
header.order_by:
type: hidden

View File

@@ -25,7 +25,7 @@ form:
content:
type: markdown
label: PLUGIN_ADMIN.CONTENT
showPreview: true
uploads:
type: pagemedia

View File

@@ -25,7 +25,7 @@ form:
content:
type: markdown
label: PLUGIN_ADMIN.CONTENT
showPreview: true
uploads:
type: pagemedia

View File

@@ -29,7 +29,7 @@ form:
size: large
label: PLUGIN_ADMIN.PASSWORD
validate:
required: true
required: false
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
@@ -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

@@ -1,9 +1,4 @@
schemes:
asset:
type: ReadOnlyStream
paths:
- assets
image:
type: ReadOnlyStream
paths:

View File

@@ -1,112 +1,114 @@
absolute_urls: false # Absolute or relative URLs for `base_url`
timezone: '' # Valid values: http://php.net/manual/en/timezones.php
default_locale: # Default locale (defaults to system)
param_sep: ':' # Parameter separator, use ';' for Apache on windows
wrapped_site: false # For themes/plugins to know if Grav is wrapped by another platform
absolute_urls: false # Absolute or relative URLs for `base_url`
timezone: '' # Valid values: http://php.net/manual/en/timezones.php
default_locale: # Default locale (defaults to system)
param_sep: ':' # Parameter separator, use ';' for Apache on windows
wrapped_site: false # For themes/plugins to know if Grav is wrapped by another platform
languages:
supported: [] # List of languages supported. eg: [en, fr, de]
include_default_lang: true # Include the default lang prefix in all URLs
translations: true # Enable translations by default
translations_fallback: true # Fallback through supported translations if active lang doesn't exist
session_store_active: false # Store active language in session
http_accept_language: false # Attempt to set the language based on http_accept_language header in the browser
override_locale: false # Override the default or system locale with language specific one
supported: [] # List of languages supported. eg: [en, fr, de]
include_default_lang: true # Include the default lang prefix in all URLs
translations: true # Enable translations by default
translations_fallback: true # Fallback through supported translations if active lang doesn't exist
session_store_active: false # Store active language in session
http_accept_language: false # Attempt to set the language based on http_accept_language header in the browser
override_locale: false # Override the default or system locale with language specific one
home:
alias: '/home' # Default path for home, ie /
alias: '/home' # Default path for home, ie /
pages:
theme: antimatter # Default theme (defaults to "antimatter" theme)
theme: antimatter # Default theme (defaults to "antimatter" theme)
order:
by: default # Order pages by "default", "alpha" or "date"
dir: asc # Default ordering direction, "asc" or "desc"
by: default # Order pages by "default", "alpha" or "date"
dir: asc # Default ordering direction, "asc" or "desc"
list:
count: 20 # Default item count per page
count: 20 # Default item count per page
dateformat:
default: # The default date format Grav expects in the `date: ` field
short: 'jS M Y' # Short date format
long: 'F jS \a\t g:ia' # Long date format
publish_dates: true # automatically publish/unpublish based on dates
default: # The default date format Grav expects in the `date: ` field
short: 'jS M Y' # Short date format
long: 'F jS \a\t g:ia' # Long date format
publish_dates: true # automatically publish/unpublish based on dates
process:
markdown: true # Process Markdown
twig: false # Process Twig
markdown: true # Process Markdown
twig: false # Process Twig
events:
page: true # Enable page level events
twig: true # Enable twig level events
page: true # Enable page level events
twig: true # Enable twig level events
markdown:
extra: false # Enable support for Markdown Extra support (GFM by default)
auto_line_breaks: false # Enable automatic line breaks
auto_url_links: false # Enable automatic HTML links
escape_markup: false # Escape markup tags into entities
special_chars: # List of special characters to automatically convert to entities
extra: false # Enable support for Markdown Extra support (GFM by default)
auto_line_breaks: false # Enable automatic line breaks
auto_url_links: false # Enable automatic HTML links
escape_markup: false # Escape markup tags into entities
special_chars: # List of special characters to automatically convert to entities
'>': 'gt'
'<': 'lt'
types: [txt,xml,html,json,rss,atom] # list of valid page types
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
last_modified: false # Set the last modified date header based on file modifcation timestamp
etag: false # Set the etag header tag
vary_accept_encoding: false # Add `Vary: Accept-Encoding` header
redirect_default_route: false # Automatically redirect to a page's default route
redirect_default_code: 301 # Default code to use for redirects
redirect_trailing_slash: true # Handle automatically or 301 redirect a trailing / URL
ignore_files: [.DS_Store] # Files to ignore in Pages
ignore_folders: [.git, .idea] # Folders to ignore in Pages
ignore_hidden: true # Ignore all Hidden files and folders
url_taxonomy_filters: true # Enable auto-magic URL-based taxonomy filters for page collections
fallback_types: [png,jpg,jpeg,gif] # Allowed types of files found if accessed via Page route
types: [txt,xml,html,htm,json,rss,atom] # list of valid page types
append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html)
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
last_modified: false # Set the last modified date header based on file modifcation timestamp
etag: false # Set the etag header tag
vary_accept_encoding: false # Add `Vary: Accept-Encoding` header
redirect_default_route: false # Automatically redirect to a page's default route
redirect_default_code: 301 # Default code to use for redirects
redirect_trailing_slash: true # Handle automatically or 301 redirect a trailing / URL
ignore_files: [.DS_Store] # Files to ignore in Pages
ignore_folders: [.git, .idea] # Folders to ignore in Pages
ignore_hidden: true # Ignore all Hidden files and folders
url_taxonomy_filters: true # Enable auto-magic URL-based taxonomy filters for page collections
cache:
enabled: true # Set to true to enable caching
enabled: true # Set to true to enable caching
check:
method: file # Method to check for updates in pages: file|folder|none
driver: auto # One of: auto|file|apc|xcache|memcache|wincache
prefix: 'g' # Cache prefix string (prevents cache conflicts)
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
gzip: false # GZip compress the page output
method: file # Method to check for updates in pages: file|folder|none
driver: auto # One of: auto|file|apc|xcache|memcache|wincache
prefix: 'g' # Cache prefix string (prevents cache conflicts)
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
gzip: false # GZip compress the page output
twig:
cache: true # Set to true to enable twig caching
debug: false # Enable Twig debug
auto_reload: true # Refresh cache on changes
autoescape: false # Autoescape Twig vars
undefined_functions: true # Allow undefined functions
undefined_filters: true # Allow undefined filters
cache: true # Set to true to enable twig caching
debug: false # Enable Twig debug
auto_reload: true # Refresh cache on changes
autoescape: false # Autoescape Twig vars
undefined_functions: true # Allow undefined functions
undefined_filters: true # Allow undefined filters
umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
assets: # Configuration for Assets Manager (JS, CSS)
css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file
css_minify: true # Minify the CSS during pipelining
css_minify_windows: false # Minify Override for Windows platforms. False by default due to ThreadStackSize
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_minify: true # Minify the JS during pipelining
enable_asset_timestamp: false # Enable asset timestamps
assets: # Configuration for Assets Manager (JS, CSS)
css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file
css_minify: true # Minify the CSS during pipelining
css_minify_windows: false # Minify Override for Windows platforms. False by default due to ThreadStackSize
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_minify: true # Minify the JS during pipelining
enable_asset_timestamp: false # Enable asset timestamps
collections:
jquery: system://assets/jquery/jquery-2.1.4.min.js
errors:
display: false # Display full backtrace-style error page
log: true # Log errors to /logs folder
display: false # Display full backtrace-style error page
log: true # Log errors to /logs folder
debugger:
enabled: false # Enable Grav debugger and following settings
enabled: false # Enable Grav debugger and following settings
shutdown:
close_connection: true # Close the connection before calling onShutdown(). false for debugging
close_connection: true # Close the connection before calling onShutdown(). false for debugging
images:
default_image_quality: 85 # Default image quality to use when resampling images (85%)
cache_all: false # Cache all image by default
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
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:
enable_media_timestamp: false # Enable media timetsamps
upload_limit: 0 # Set maximum upload size in bytes (0 is unlimited)
unsupported_inline_types: [] # Array of unsupported media file types to try to display inline
enable_media_timestamp: false # Enable media timetsamps
upload_limit: 0 # Set maximum upload size in bytes (0 is unlimited)
unsupported_inline_types: [] # Array of supported media types to try to display inline
allowed_fallback_types: [] # Array of allowed media types of files found if accessed via Page route
session:
enabled: true # Enable Session support
timeout: 1800 # Timeout in seconds
name: grav-site # Name prefix of the session cookie
enabled: true # Enable Session support
timeout: 1800 # Timeout in seconds
name: grav-site # Name prefix of the session cookie. Use alphanumeric, dashes or underscores only. Do not use dots in the session name
security:
default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK

View File

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

View File

@@ -92,3 +92,7 @@ NICETIME:
MO_PLURAL: mos
YR_PLURAL: yrs
DEC_PLURAL: decs
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 :

52
system/languages/hr.yaml Normal file
View File

@@ -0,0 +1,52 @@
INFLECTOR_IRREGULAR:
'person': 'Osoba'
'man': 'Čovjek'
'child': 'Dijete'
'sex': 'Spol'
'move': 'Pomakni'
NICETIME:
NO_DATE_PROVIDED: Datum nije upisan
BAD_DATE: Pogrešan datum
AGO: prije
FROM_NOW: od sad
SECOND: sekundi
MINUTE: minuta
HOUR: godina
DAY: dan
WEEK: tjedan
MONTH: mjesec
YEAR: godina
DECADE: desetljeće
SEC: sek
MIN: min
HR: sat
DAY: dan
WK: t
MO: m
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
HR_PLURAL: sat
DAY_PLURAL: dan
WK_PLURAL: t
MO_PLURAL: m
YR_PLURAL: g
DEC_PLURAL: des
FORM:
VALIDATION_FAIL: <b>Validacija nije uspjela:</b>
INVALID_INPUT: Unos nije valjan

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:

37
system/languages/tr.yaml Normal file
View File

@@ -0,0 +1,37 @@
NICETIME:
NO_DATE_PROVIDED: Tarih yok
BAD_DATE: Yanlış tarih
AGO: önce
FROM_NOW: (şimdiden)
SECOND: saniye
MINUTE: dakika
HOUR: saat
DAY: gün
WEEK: hafta
MONTH: ay
YEAR: yıl
DECADE: onyıl
SEC: sn
MIN: dk
HR: sa
DAY: gün
WK: hft
MO: ay
YR: yl
DEC: onyl
SECOND_PLURAL: saniye
MINUTE_PLURAL: dakika
HOUR_PLURAL: saat
DAY_PLURAL: gün
WEEK_PLURAL: hafta
MONTH_PLURAL: ay
YEAR_PLURAL: yıl
DECADE_PLURAL: onyıl
SEC_PLURAL: sn
MIN_PLURAL: dk
HR_PLURAL: sa
DAY_PLURAL: gün
WK_PLURAL: hft
MO_PLURAL: ay
YR_PLURAL: yl
DEC_PLURAL: onyl

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);
}
@@ -413,7 +421,7 @@ class Assets
}
$key = md5($asset);
if (is_string($asset) && !array_key_exists($key, $this->inline_css)) {
if ($asset && is_string($asset) && !array_key_exists($key, $this->inline_css)) {
$this->inline_css[$key] = $data;
}
@@ -460,7 +468,7 @@ class Assets
}
$key = md5($asset);
if (is_string($asset) && !array_key_exists($key, $this->inline_js)) {
if ($asset && is_string($asset) && !array_key_exists($key, $this->inline_js)) {
$this->inline_js[$key] = $data;
}
@@ -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

@@ -21,6 +21,8 @@ use Grav\Common\Filesystem\Folder;
*/
class Cache extends Getters
{
use GravTrait;
/**
* @var string Cache key.
*/
@@ -36,6 +38,8 @@ class Cache extends Getters
*/
protected $driver;
protected $driver_name;
/**
* @var bool
*/
@@ -44,30 +48,30 @@ class Cache extends Getters
protected $cache_dir;
protected static $standard_remove = [
'cache/twig/',
'cache/doctrine/',
'cache/compiled/',
'cache/validated-',
'images/',
'assets/',
'cache://twig/',
'cache://doctrine/',
'cache://compiled/',
'cache://validated-',
'cache://images',
'asset://',
];
protected static $all_remove = [
'cache/',
'images/',
'assets/'
'cache://',
'cache://images',
'asset://'
];
protected static $assets_remove = [
'assets/'
'asset://'
];
protected static $images_remove = [
'images/'
'cache://images'
];
protected static $cache_remove = [
'cache/'
'cache://'
];
/**
@@ -108,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 . ']');
}
/**
@@ -134,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();
@@ -221,7 +231,7 @@ class Cache extends Getters
*/
public static function clearCache($remove = 'standard')
{
$locator = self::getGrav()['locator'];
$output = [];
$user_config = USER_DIR . 'config/system.yaml';
@@ -243,10 +253,16 @@ class Cache extends Getters
}
foreach ($remove_paths as $path) {
foreach ($remove_paths as $stream) {
// Convert stream to a real path
$path = $locator->findResource($stream, true, true);
// Make sure path exists before proceeding, otherwise we would wipe ROOT_DIR
if (!$path)
throw new \RuntimeException("Stream '{$stream}' not found", 500);
$anything = false;
$files = glob(ROOT_DIR . $path . '*');
$files = glob($path . '/*');
if (is_array($files)) {
foreach ($files as $file) {
@@ -263,7 +279,7 @@ class Cache extends Getters
}
if ($anything) {
$output[] = '<red>Cleared: </red>' . $path . '*';
$output[] = '<red>Cleared: </red>' . $path . '/*';
}
}

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,464 +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'],
]
],
'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()
{
$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;
@@ -76,7 +78,9 @@ class Blueprint
try {
$this->validateArray($data, $this->nested);
} catch (\RuntimeException $e) {
throw new \RuntimeException(sprintf('<b>Validation failed:</b> %s', $e->getMessage()));
$language = self::getGrav()['language'];
$message = sprintf($language->translate('FORM.VALIDATION_FAIL', null, true) . ' %s', $e->getMessage());
throw new \RuntimeException($message);
}
}
@@ -238,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.
@@ -310,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.
@@ -449,7 +464,10 @@ class Blueprint
if (isset($field['validate']['required'])
&& $field['validate']['required'] === true
&& empty($data[$name])) {
throw new \RuntimeException("Missing required field: {$field['name']}");
$value = isset($field['label']) ? $field['label'] : $field['name'];
$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

@@ -1,5 +1,6 @@
<?php
namespace Grav\Common\Data;
use Grav\Common\GravTrait;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
@@ -30,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'];
@@ -37,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'] : 'Invalid input in "' . $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);
@@ -77,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 {
@@ -257,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
*
@@ -591,7 +620,7 @@ class Validation
if (is_string($value)) {
$value = trim($value);
}
return (bool) $params !== true || !empty($value);
}

View File

@@ -15,6 +15,8 @@ class Debugger
protected $renderer;
protected $enabled;
protected $timers = [];
public function __construct()
{
$this->debugbar = new StandardDebugBar();
@@ -98,15 +100,17 @@ class Debugger
{
if ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled')) {
$this->debugbar['time']->startMeasure($name, $description);
$this->timers[] = $name;
}
return $this;
}
public function stopTimer($name)
{
if ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled')) {
if (isset($this->timers[$name]) && ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled'))) {
$this->debugbar['time']->stopMeasure($name);
}
return $this;
}

View File

@@ -10,7 +10,7 @@ use Whoops\Run;
* Class Debugger
* @package Grav\Common
*/
class Errors extends \Whoops\Run
class Errors extends Run
{
public function pushHandler($handler, $key = null)

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

@@ -3,10 +3,9 @@
namespace Grav\Common\GPM\Local;
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
use Grav\Common\GPM\Local\Package;
abstract class AbstractPackageCollection extends BaseCollection {
abstract class AbstractPackageCollection extends BaseCollection
{
public function __construct($items)
{
foreach ($items as $name => $data) {

View File

@@ -1,58 +0,0 @@
<?php
namespace Grav\Common\GPM;
use Grav\Common\Data\Data;
/**
* Interface Package
* @package Grav\Common\GPM
*/
class Package
{
/**
* @var Data
*/
protected $data;
/**
* @var \Grav\Common\Data\Blueprint
*/
protected $blueprints;
/**
* @param Data $package
* @param bool $package_type
*/
public function __construct(Data $package, $package_type = false);
/**
* @return mixed
*/
public function isEnabled();
/**
* @return Data
*/
public function getData();
/**
* @param $key
* @return mixed
*/
public function __get($key);
/**
* @return string
*/
public function __toString();
/**
* @return string
*/
public function toJson();
/**
* @return array
*/
public function toArray();
}

View File

@@ -3,7 +3,6 @@ namespace Grav\Common\GPM\Remote;
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
use Grav\Common\GPM\Response;
use \Doctrine\Common\Cache\FilesystemCache;
class AbstractPackageCollection extends BaseCollection

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

@@ -16,6 +16,8 @@ class Plugins extends AbstractPackageCollection
/**
* Local Plugins Constructor
* @param bool $refresh
* @param callable $callback Either a function or callback in array notation
*/
public function __construct($refresh = false, $callback = null)
{

View File

@@ -16,6 +16,8 @@ class Themes extends AbstractPackageCollection
/**
* Local Themes Constructor
* @param bool $refresh
* @param callable $callback Either a function or callback in array notation
*/
public function __construct($refresh = false, $callback = null)
{

View File

@@ -1,6 +1,8 @@
<?php
namespace Grav\Common\GPM;
use Grav\Common\Utils;
class Response
{
/**
@@ -50,6 +52,7 @@ class Response
/**
* Sets the preferred method to use for making HTTP calls.
* @param string $method Default is `auto`
* @return Response
*/
public static function setMethod($method = 'auto')
{
@@ -64,8 +67,9 @@ class Response
/**
* Makes a request to the URL by using the preferred method
* @param string $uri URL to call
* @param array $options An array of parameters for both `curl` and `fopen`
* @param string $uri URL to call
* @param array $options An array of parameters for both `curl` and `fopen`
* @param callable $callback Either a function or callback in array notation
* @return string The response of the request
*/
public static function get($uri = '', $options = [], $callback = null)
@@ -74,6 +78,13 @@ class Response
throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available');
}
// 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

@@ -114,7 +114,7 @@ class Grav extends Container
/** @var Uri $uri */
$uri = $c['uri'];
$path = rtrim($uri->path(), '/');
$path = $uri->path(); // Don't trim to support trailing slash default routes
$path = $path ?: '/';
$page = $pages->dispatch($path);
@@ -201,7 +201,10 @@ class Grav extends Container
// Use output buffering to prevent headers from being sent too early.
ob_start();
if ($this['config']->get('system.cache.gzip')) {
ob_start('ob_gzhandler');
// Enable zip/deflate with a fallback in case of if browser does not support compressing.
if(!ob_start("ob_gzhandler")) {
ob_start();
}
}
// Initialize the timezone
@@ -227,7 +230,6 @@ class Grav extends Container
$debugger->startTimer('themes', 'Themes');
$this['themes']->init();
$this->fireEvent('onThemeInitialized');
$debugger->stopTimer('themes');
$task = $this['task'];
@@ -296,7 +298,10 @@ class Grav extends Container
if ($uri->isExternal($route)) {
$url = $route;
} else {
$url = rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/');
if ($this['config']->get('system.pages.redirect_trailing_slash', true))
$url = rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/'); // Remove trailing slash
else
$url = rtrim($uri->rootUrl(), '/') .'/'. ltrim($route, '/'); // Support trailing slash default routes
}
header("Location: {$url}", true, $code);
@@ -413,22 +418,25 @@ class Grav extends Container
public function shutdown()
{
if ($this['config']->get('system.debugger.shutdown.close_connection')) {
//stop user abort
// Prevent user abort.
if (function_exists('ignore_user_abort')) {
@ignore_user_abort(true);
}
// close the session
// Close the session.
if (isset($this['session'])) {
$this['session']->close();
}
// flush buffer if gzip buffer was started
if ($this['config']->get('system.cache.gzip')) {
ob_end_flush(); // gzhandler buffer
// Flush gzhandler buffer if gzip was enabled.
ob_end_flush();
} else {
// Otherwise prevent server from compressing the output.
header('Content-Encoding: none');
}
// get lengh and close the connection
// Get length and close the connection.
header('Content-Length: ' . ob_get_length());
header("Connection: close");
@@ -437,7 +445,7 @@ class Grav extends Container
@ob_flush();
flush();
// fix for fastcgi close connection issue
// Fix for fastcgi close connection issue.
if (function_exists('fastcgi_finish_request')) {
@fastcgi_finish_request();
}
@@ -461,9 +469,13 @@ class Grav extends Container
$config = $this['config'];
$uri_extension = $uri->extension();
$fallback_types = $config->get('system.media.allowed_fallback_types', null);
$supported_types = $config->get('media');
// Only allow whitelisted types to fallback
if (!in_array($uri_extension, $config->get('system.pages.fallback_types'))) {
// Check whitelist first, then ensure extension is a valid media type
if (!empty($fallback_types) && !in_array($uri_extension, $fallback_types)) {
return;
} elseif (!array_key_exists($uri_extension, $supported_types)) {
return;
}

View File

@@ -131,7 +131,7 @@ class Truncator {
$inner .= $txt;
if ($remaining < 0) {
if (static::ellipsable($node)) {
$inner = preg_replace('/(?:[\s\pP]+|(?:&(?:[a-z]+|#[0-9]+);?))*$/', '', $inner).$opts['ellipsis'];
$inner = preg_replace('/(?:[\s\pP]+|(?:&(?:[a-z]+|#[0-9]+);?))*$/u', '', $inner).$opts['ellipsis'];
$opts['ellipsis'] = '';
$opts['was_truncated'] = true;
}

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

@@ -1,12 +1,8 @@
<?php
namespace Grav\Common\Markdown;
use Grav\Common\Config\Config;
use Grav\Common\Debugger;
use Grav\Common\GravTrait;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Uri;
use Grav\Common\Utils;
/**
* A trait to add some custom processing to the identifyLink() method in Parsedown and ParsedownExtra
@@ -208,10 +204,52 @@ 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
$excerpt['element']['attributes']['href'] = Uri::convertUrl($this->page, Uri::buildUrl($url), $type);
$excerpt['element']['attributes']['href'] = Uri::convertUrl($this->page, Uri::buildUrl($url), $type, true);
}
}

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

@@ -2,8 +2,6 @@
namespace Grav\Common\Page;
use Grav\Common\Getters;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Grav\Common\GravTrait;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Medium\MediumFactory;

View File

@@ -1,13 +1,6 @@
<?php
namespace Grav\Common\Page\Medium;
use Grav\Common\Config\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Data;
class AudioMedium extends Medium
{
use StaticResizeTrait;

View File

@@ -4,11 +4,20 @@ namespace Grav\Common\Page\Medium;
use Grav\Common\GravTrait;
use Gregwar\Image\Exceptions\GenerationError;
use RocketTheme\Toolbox\Event\Event;
use Gregwar\Image\Image;
class ImageFile extends \Gregwar\Image\Image
class ImageFile extends Image
{
use GravTrait;
/**
* Clear previously applied operations
*/
public function clearOperations()
{
$this->operations = [];
}
/**
* This is the same as the Gregwar Image class except this one fires a Grav Event on creation of new cached file
*
@@ -46,6 +55,7 @@ class ImageFile extends \Gregwar\Image\Image
$cacheFile .= $this->prettyName;
}
$cacheFile .= '.'.$type;
// If the files does not exists, save it
@@ -70,7 +80,8 @@ class ImageFile extends \Gregwar\Image\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

@@ -52,7 +52,6 @@ class ImageMedium extends Medium
'forceResize' => [ 0, 1 ],
'cropResize' => [ 0, 1 ],
'crop' => [ 0, 1, 2, 3 ],
'cropResize' => [ 0, 1 ],
'zoomCrop' => [ 0, 1 ]
];
@@ -136,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();
@@ -260,6 +259,7 @@ class ImageMedium extends Medium
if ($this->image) {
$this->image();
$this->image->clearOperations(); // Clear previously applied operations
$this->filter();
}
@@ -311,19 +311,23 @@ class ImageMedium extends Medium
}
/**
* Sets the quality of the image
* Sets or gets the quality of the image
*
* @param int $quality 0-100 quality
* @return Medium
*/
public function quality($quality)
public function quality($quality = null)
{
if (!$this->image) {
$this->image();
if ($quality) {
if (!$this->image) {
$this->image();
}
$this->quality = $quality;
return $this;
}
$this->quality = $quality;
return $this;
return $this->quality;
}
/**
@@ -359,6 +363,48 @@ class ImageMedium extends Medium
return empty($this->sizes) ? '100vw' : $this->sizes;
}
/**
* Allows to set the width attribute from Markdown or Twig
* Examples: ![Example](myimg.png?width=200&height=400)
* ![Example](myimg.png?resize=100,200&width=100&height=200)
* ![Example](myimg.png?width=auto&height=auto)
* ![Example](myimg.png?width&height)
* {{ page.media['myimg.png'].width().height().html }}
* {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }}
*
* @param mixed $value A value or 'auto' or empty to use the width of the image
* @return $this
*/
public function width($value = 'auto')
{
if (!$value || $value == 'auto')
$this->attributes['width'] = $this->get('width');
else
$this->attributes['width'] = $value;
return $this;
}
/**
* Allows to set the height attribute from Markdown or Twig
* Examples: ![Example](myimg.png?width=200&height=400)
* ![Example](myimg.png?resize=100,200&width=100&height=200)
* ![Example](myimg.png?width=auto&height=auto)
* ![Example](myimg.png?width&height)
* {{ page.media['myimg.png'].width().height().html }}
* {{ page.media['myimg.png'].resize(100,200).width(100).height(200).html }}
*
* @param mixed $value A value or 'auto' or empty to use the height of the image
* @return $this
*/
public function height($value = 'auto')
{
if (!$value || $value == 'auto')
$this->attributes['height'] = $this->get('height');
else
$this->attributes['height'] = $value;
return $this;
}
/**
* Forward the call to the image processing method.
*
@@ -470,4 +516,28 @@ class ImageMedium extends Medium
$this->__call($method, $params);
}
}
/**
* Return the image higher quality version
*
* @return ImageMedium the alternative version with higher quality
*/
public function higherQualityAlternative()
{
if ($this->alternatives) {
$max = reset($this->alternatives);
foreach($this->alternatives as $alternative)
{
if($alternative->quality() > $max->quality())
{
$max = $alternative;
}
}
return $max;
} else {
return $this;
}
}
}

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();
@@ -209,9 +209,14 @@ class Medium extends Data implements RenderableInterface
$style = '';
foreach ($this->styleAttributes as $key => $value) {
$style .= $key . ': ' . $value . ';';
if (is_numeric($key)) // Special case for inline style attributes, refer to style() method
$style .= $value;
else
$style .= $key . ': ' . $value . ';';
}
if ($style) {
$attributes['style'] = $style;
}
$attributes['style'] = $style;
if (empty($attributes['title'])) {
if (!empty($title)) {
@@ -388,6 +393,51 @@ 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)
*
* @param string $style
* @return $this
*/
public function style($style)
{
$this->styleAttributes[] = rtrim($style, ';') . ';';
return $this;
}
/**
* Allow any action to be called on this medium from twig or markdown
*
@@ -440,4 +490,5 @@ class Medium extends Data implements RenderableInterface
return $this->_thumbnail;
}
}

View File

@@ -1,13 +1,6 @@
<?php
namespace Grav\Common\Page\Medium;
use Grav\Common\Config\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Data;
class VideoMedium extends Medium
{
use StaticResizeTrait;

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;
@@ -41,6 +42,7 @@ class Page
protected $folder;
protected $path;
protected $extension;
protected $url_extension;
protected $id;
protected $parent;
@@ -128,6 +130,7 @@ class Page
$this->modularTwig($this->slug[0] == '_');
$this->setPublishState();
$this->published();
$this->urlExtension();
// some extension logic
if (empty($extension)) {
@@ -136,6 +139,7 @@ class Page
$this->extension($extension);
}
// extract page language from page extension
$language = trim(basename($this->extension(), 'md'), '.') ?: null;
$this->language($language);
@@ -349,7 +353,6 @@ class Page
if (isset($this->header->last_modified)) {
$this->last_modified = (bool) $this->header->last_modified;
}
}
return $this->header;
@@ -379,7 +382,7 @@ class Page
*/
public function modifyHeader($key, $value)
{
$this->header->$key = $value;
$this->header->{$key} = $value;
}
/**
@@ -390,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);
}
@@ -726,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;
}
/**
@@ -756,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;
}
/**
@@ -824,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']);
}
}
@@ -957,6 +962,17 @@ class Page
return $this->extension;
}
public function urlExtension()
{
// if not set in the page get the value from system config
if (empty($this->url_extension)) {
$this->url_extension = trim(isset($this->header->append_url_extension) ? $this->header->append_url_extension : self::getGrav()['config']->get('system.pages.append_url_extension', false));
}
return $this->url_extension;
}
/**
* Gets and sets the expires field. If not set will return the default
*
@@ -1137,7 +1153,7 @@ class Page
$this->metadata = [];
// Set the Generator tag
$this->metadata['generator'] = array('name'=>'generator', 'content'=>'GravCMS ' . GRAV_VERSION);
$this->metadata['generator'] = array('name'=>'generator', 'content'=>'GravCMS');
// Get initial metadata for the page
$metadata = self::getGrav()['config']->get('site.metadata');
@@ -1262,7 +1278,7 @@ class Page
$rootUrl = $uri->rootUrl($include_host) . $pages->base();
$url = $rootUrl.'/'.trim($route, '/');
$url = $rootUrl.'/'.trim($route, '/') . $this->urlExtension();
// trim trailing / if not root
if ($url !== '/') {
@@ -1471,7 +1487,17 @@ class Page
*/
public function filePathClean()
{
return str_replace(ROOT_DIR, '', $this->filePath());
$path = str_replace(ROOT_DIR, '', $this->filePath());
return $path;
}
/**
* Returns the clean path to the page file
*/
public function relativePagePath()
{
$path = str_replace('/'.$this->name, '', $this->filePathClean());
return $path;
}
/**
@@ -1782,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();
}
}
@@ -1820,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
@@ -1928,7 +1956,7 @@ class Page
* @return mixed
* @internal
*/
protected function evaluate($value)
public function evaluate($value)
{
// Parse command.
if (is_string($value)) {
@@ -1979,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;
@@ -2118,7 +2146,7 @@ class Page
*/
protected function doRelocation($reorder)
{
if (empty($this->_original) ) {
if (!$this->_original) {
return;
}
@@ -2159,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);
@@ -2167,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()) {
@@ -2181,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
*/
@@ -272,6 +272,10 @@ class Pages
{
// Fetch page if there's a defined route to it.
$page = isset($this->routes[$url]) ? $this->get($this->routes[$url]) : null;
// Try without trailing slash
if (!$page && Utils::endsWith($url, '/')) {
$page = isset($this->routes[rtrim($url, '/')]) ? $this->get($this->routes[rtrim($url, '/')]) : null;
}
// Are we in the admin? this is important!
$not_admin = !isset($this->grav['admin']);
@@ -482,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.
*
@@ -494,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

@@ -4,7 +4,6 @@ namespace Grav\Common;
use Grav\Common\Config\Config;
use Grav\Common\Data\Blueprints;
use Grav\Common\Data\Data;
use Grav\Common\GravTrait;
use Grav\Common\File\CompiledYamlFile;
use RocketTheme\Toolbox\Event\EventDispatcher;
use RocketTheme\Toolbox\Event\EventSubscriberInterface;
@@ -105,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);
@@ -121,7 +124,13 @@ 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 this is a valid plugin
if (!$file->exists()) {
return null;
}
$obj = new Data($file->content(), $blueprint);
// Override with user configuration.

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.
@@ -171,6 +193,8 @@ class Themes extends Iterator
exit("Theme '$name' does not exist, unable to display page.");
}
$this->config->set('theme', $config->get('themes.' . $name));
if (empty($class)) {
$class = new Theme($grav, $config, $name);
}
@@ -282,11 +306,11 @@ class Themes extends Iterator
$class = substr($class, strlen($prefix));
// Replace namespace tokens to directory separators
$path = ltrim(preg_replace('#\\\|_(?!.+\\\)#', '/', $class), '/');
$path = strtolower(ltrim(preg_replace('#\\\|_(?!.+\\\)#', '/', $class), '/'));
$file = $locator->findResource("themes://{$path}/{$path}.php");
// Load class
if (stream_resolve_include_path($file)) {
if (file_exists($file)) {
return include_once($file);
}
}

View File

@@ -4,8 +4,6 @@ namespace Grav\Common\Twig;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Grav\Common\Page\Page;
use Grav\Common\Inflector;
use Grav\Common\Utils;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\Event\Event;

View File

@@ -1,8 +1,6 @@
<?php
namespace Grav\Common\Twig;
use Grav\Common\GravTrait;
/**
* The Twig Environment class is a wrapper that handles configurable permissions
* for the Twig cache files

View File

@@ -70,6 +70,7 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFilter('randomize', [$this,'randomizeFilter']),
new \Twig_SimpleFilter('modulus', [$this,'modulusFilter']),
new \Twig_SimpleFilter('rtrim', [$this, 'rtrimFilter']),
new \Twig_SimpleFilter('pad', [$this, 'padFilter']),
new \Twig_SimpleFilter('safe_email', [$this,'safeEmailFilter']),
new \Twig_SimpleFilter('safe_truncate', ['\Grav\Common\Utils','safeTruncate']),
new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils','safeTruncateHTML']),
@@ -94,14 +95,17 @@ class TwigExtension extends \Twig_Extension
new \Twig_simpleFunction('authorize', [$this, 'authorize']),
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']),
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']),
new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('ta', [$this, 'translateArray']),
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']),
];
}
@@ -351,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}";
@@ -534,6 +544,22 @@ class TwigExtension extends \Twig_Extension
return Utils::generateRandomString($count);
}
/**
* Pad a string to a certain length with another string
*
* @param $input
* @param $pad_length
* @param string $pad_string
* @param int $pad_type
*
* @return string
*/
public static function padFilter($input, $pad_length, $pad_string = " ", $pad_type = STR_PAD_RIGHT)
{
return str_pad($input, (int) $pad_length, $pad_string, $pad_type);
}
/**
* Cast a value to array
*
@@ -595,4 +621,22 @@ class TwigExtension extends \Twig_Extension
return false;
}
/**
* Used to add a nonce to a form. Call {{ nonce_field('action') }} specifying a string representing the action.
*
* For maximum protection, ensure that the string representing the action is as specific as possible.
*
* @todo evaluate if adding referrer or not
*
* @param string action the action
* @param string nonceParamName a custom nonce param name
*
* @return string the nonce input field
*/
public function nonceFieldFunc($action, $nonceParamName = 'nonce')
{
$string = '<input type="hidden" id="' . $nonceParamName . '" name="' . $nonceParamName . '" value="' . Utils::getNonce($action) .'" />';
return $string;
}
}

View File

@@ -2,7 +2,6 @@
namespace Grav\Common;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
/**
* The URI object provides information about the current URL
@@ -140,6 +139,7 @@ class Uri
$valid_page_types = implode('|', $config->get('system.pages.types'));
// Strip the file extension for valid page types
if (preg_match("/\.(".$valid_page_types.")$/", $parts['basename'])) {
$uri = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS). '/' .$parts['filename'];
}
@@ -485,12 +485,14 @@ class Uri
/**
* Converts links from absolute '/' or relative (../..) to a grav friendly format
*
* @param $page the current page to use as reference
* @param Page|the $page the current page to use as reference
* @param string $markdown_url the URL as it was written in the markdown
* @param string $type the type of URL, image | link
* @param null $relative if null, will use system default, if true will use relative links internally
*
* @return string the more friendly formatted url
*/
public static function convertUrl(Page $page, $markdown_url, $type = 'link')
public static function convertUrl(Page $page, $markdown_url, $type = 'link', $relative = null)
{
$grav = Grav::instance();
@@ -504,7 +506,13 @@ class Uri
}
$pages_dir = $grav['locator']->findResource('page://');
$base_url = rtrim($grav['base_url'] . $grav['pages']->base(), '/') . $language_append;
if (is_null($relative)) {
$base = $grav['base_url'];
} else {
$base = $relative ? $grav['base_url_relative'] : $grav['base_url_absolute'];
}
$base_url = rtrim($base . $grav['pages']->base(), '/') . $language_append;
// if absolute and starts with a base_url move on
if (pathinfo($markdown_url, PATHINFO_DIRNAME) == '.' && $page->url() == '/') {
@@ -572,4 +580,20 @@ class Uri
return $normalized_url;
}
}
/**
* Adds the nonce to a URL for a specific action
*
* @param string $url the url
* @param string $action the action
* @param string $nonceParamName the param name to use
*
* @return string the url with the nonce
*/
public static function addNonce($url, $action, $nonceParamName = 'nonce')
{
$urlWithNonce = $url . '/' . $nonceParamName . Grav::instance()['config']->get('system.param_sep', ':') . Utils::getNonce($action);
return $urlWithNonce;
}
}

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);
@@ -40,6 +43,9 @@ class User extends Data
if (!isset($content['username'])) {
$content['username'] = $username;
}
if (!isset($content['state'])) {
$content['state'] = 'enabled';
}
$user = new User($content, $blueprint);
$user->file($file);
@@ -50,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)
{
@@ -80,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
@@ -139,7 +148,32 @@ class User extends Data
return false;
}
return Utils::isPositive($this->get("access.{$action}"));
if (isset($this->state) && $this->state !== 'enabled') {
return false;
}
$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
@@ -100,6 +102,9 @@ abstract class Utils
return (object)array_merge((array)$obj1, (array)$obj2);
}
/**
* @return array
*/
public static function dateFormats()
{
$now = new DateTime();
@@ -214,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);
@@ -300,6 +307,19 @@ abstract class Utils
return $root . implode('/', $ret);
}
/**
* @param $function
*
* @return bool
*/
public static function isFunctionDisabled($function)
{
return in_array($function, explode(',', ini_get('disable_functions')));
}
/**
* @return array
*/
public static function timezones()
{
$timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL);
@@ -327,6 +347,12 @@ abstract class Utils
}
/**
* @param array $source
* @param $fn
*
* @return array
*/
public static function arrayFilterRecursive(Array $source, $fn)
{
$result = array();
@@ -346,6 +372,11 @@ abstract class Utils
return $result;
}
/**
* @param $string
*
* @return bool
*/
public static function pathPrefixedByLangCode($string)
{
$languages_enabled = self::getGrav()['config']->get('system.languages.supported', []);
@@ -357,6 +388,11 @@ abstract class Utils
return false;
}
/**
* @param $date
*
* @return int
*/
public static function date2timestamp($date)
{
$config = self::getGrav()['config'];
@@ -377,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
*
@@ -384,7 +439,98 @@ abstract class Utils
*
* @return boolean
*/
public static function isPositive($value) {
public static function isPositive($value)
{
return in_array($value, [true, 1, '1', 'yes', 'on', 'true'], true);
}
/**
* Generates a nonce string to be hashed. Called by self::getNonce()
*
* @param string $action
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
*
* @return string the nonce string
*/
private static function generateNonceString($action, $plusOneTick = false)
{
if (isset(self::getGrav()['user'])) {
$user = self::getGrav()['user'];
$username = $user->username;
if (isset($_SERVER['REMOTE_ADDR'])) {
$username .= $_SERVER['REMOTE_ADDR'];
}
} else {
$username = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
}
$token = session_id();
$i = self::nonceTick();
if ($plusOneTick) {
$i++;
}
return ( $i . '|' . $action . '|' . $username . '|' . $token . '|' . self::getGrav()['config']->get('security.salt'));
}
/**
* Get the time-dependent variable for nonce creation.
*
* @todo now a tick lasts a day. Once the day is passed, the nonce is not valid any more. Find a better way
* to ensure nonces issued near the end of the day do not expire in that small amount of time
*
* @return int the time part of the nonce. Changes once every 24 hours
*/
private static function nonceTick()
{
$secondsInHalfADay = 60 * 60 * 12;
return (int)ceil(time() / ( $secondsInHalfADay ));
}
/**
* 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.
*
* @param string $action the action the nonce is tied to (e.g. save-user-admin or move-page-homepage)
* @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours)
*
* @return string the nonce
*/
public static function getNonce($action, $plusOneTick = false)
{
// Don't regenerate this again if not needed
if (isset(static::$nonces[$action])) {
return static::$nonces[$action];
}
$nonce = md5(self::generateNonceString($action, $plusOneTick));
static::$nonces[$action] = $nonce;
return static::$nonces[$action];
}
/**
* Verify the passed nonce for the give action
*
* @param string $nonce the nonce to verify
* @param string $action the action to verify the nonce to
*
* @return boolean verified or not
*/
public static function verifyNonce($nonce, $action)
{
//Nonce generated 0-12 hours ago
if ($nonce == self::getNonce($action)) {
return true;
}
//Nonce generated 12-24 hours ago
$plusOneTick = true;
if ($nonce == self::getNonce($action, $plusOneTick)) {
return true;
}
//Invalid nonce
return false;
}
}

View File

@@ -2,23 +2,17 @@
namespace Grav\Console\Cli;
use Grav\Common\Backup\ZipBackup;
use Grav\Console\ConsoleTrait;
use Grav\Console\ConsoleCommand;
use RocketTheme\Toolbox\File\JsonFile;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class BackupCommand
* @package Grav\Console\Cli
*/
class BackupCommand extends Command
class BackupCommand extends ConsoleCommand
{
use ConsoleTrait;
protected $source;
protected $progress;
@@ -42,21 +36,16 @@ class BackupCommand extends Command
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$this->progress = new ProgressBar($output);
$this->progress = new ProgressBar($this->output);
$this->progress->setFormat('Archiving <cyan>%current%</cyan> files [<green>%bar%</green>] %elapsed:6s% %memory:6s%');
self::getGrav()['config']->init();
$destination = ($input->getArgument('destination')) ? $input->getArgument('destination') : null;
$destination = ($this->input->getArgument('destination')) ? $this->input->getArgument('destination') : null;
$log = JsonFile::instance(self::getGrav()['locator']->findResource("log://backup.log", true, true));
$backup = ZipBackup::backup($destination, [$this, 'output']);
@@ -66,16 +55,13 @@ class BackupCommand extends Command
]);
$log->save();
$output->writeln('');
$output->writeln('');
$this->output->writeln('');
$this->output->writeln('');
}
/**
* @param $folder
* @param $zipFile
* @param $exclusiveLength
* @param $progress
* @param $args
*/
public function output($args)
{

View File

@@ -2,20 +2,14 @@
namespace Grav\Console\Cli;
use Grav\Common\Filesystem\Folder;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Grav\Console\ConsoleCommand;
/**
* Class CleanCommand
* @package Grav\Console\Cli
*/
class CleanCommand extends Command
class CleanCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
* @var array
*/
@@ -127,28 +121,34 @@ class CleanCommand extends Command
'vendor/rockettheme/toolbox/.travis.yml',
'vendor/rockettheme/toolbox/composer.json',
'vendor/rockettheme/toolbox/phpunit.xml',
'vendor/symfony/console/Symfony/Component/Console/composer.json',
'vendor/symfony/console/Symfony/Component/Console/phpunit.xml.dist',
'vendor/symfony/console/Symfony/Component/Console/.gitignore',
'vendor/symfony/console/Symfony/Component/Console/.git',
'vendor/symfony/console/Symfony/Component/Console/Tests',
'vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/.git',
'vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/.gitignore',
'vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/composer.json',
'vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/phpunit.xml.dist',
'vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests',
'vendor/symfony/yaml/Symfony/Component/Yaml/composer.json',
'vendor/symfony/yaml/Symfony/Component/Yaml/phpunit.xml.dist',
'vendor/symfony/yaml/Symfony/Component/Yaml/.gitignore',
'vendor/symfony/yaml/Symfony/Component/Yaml/.git',
'vendor/symfony/yaml/Symfony/Component/Yaml/Tests',
'vendor/tracy/tracy/.gitattributes',
'vendor/tracy/tracy/.travis.yml',
'vendor/tracy/tracy/composer.json',
'vendor/tracy/tracy/.gitignore',
'vendor/tracy/tracy/.git',
'vendor/tracy/tracy/examples',
'vendor/tracy/tracy/tests',
'vendor/symfony/console/composer.json',
'vendor/symfony/console/phpunit.xml.dist',
'vendor/symfony/console/.gitignore',
'vendor/symfony/console/.git',
'vendor/symfony/console/Tester',
'vendor/symfony/console/Tests',
'vendor/symfony/event-dispatcher/.git',
'vendor/symfony/event-dispatcher/.gitignore',
'vendor/symfony/event-dispatcher/composer.json',
'vendor/symfony/event-dispatcher/phpunit.xml.dist',
'vendor/symfony/event-dispatcher/Tests',
'vendor/symfony/polyfill-iconv/.git',
'vendor/symfony/polyfill-iconv/.gitignore',
'vendor/symfony/polyfill-iconv/composer.json',
'vendor/symfony/polyfill-mbstring/.git',
'vendor/symfony/polyfill-mbstring/.gitignore',
'vendor/symfony/polyfill-mbstring/composer.json',
'vendor/symfony/var-dumper/.git',
'vendor/symfony/var-dumper/.gitignore',
'vendor/symfony/var-dumper/composer.json',
'vendor/symfony/var-dumper/phpunit.xml.dist',
'vendor/symfony/var-dumper/Test',
'vendor/symfony/var-dumper/Tests',
'vendor/symfony/yaml/composer.json',
'vendor/symfony/yaml/phpunit.xml.dist',
'vendor/symfony/yaml/.gitignore',
'vendor/symfony/yaml/.git',
'vendor/symfony/yaml/Tests',
'vendor/twig/twig/.editorconfig',
'vendor/twig/twig/.travis.yml',
'vendor/twig/twig/.gitignore',
@@ -172,14 +172,10 @@ class CleanCommand extends Command
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$this->cleanPaths();
}

View File

@@ -2,29 +2,24 @@
namespace Grav\Console\Cli;
use Grav\Common\Cache;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class ClearCacheCommand
* @package Grav\Console\Cli
*/
class ClearCacheCommand extends Command
class ClearCacheCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
*
*/
protected function configure()
{
$this
->setName("clear-cache")
->setDescription("Clears Grav cache")
->setName('clear-cache')
->setAliases(['clearcache'])
->setDescription('Clears Grav cache')
->addOption('all', null, InputOption::VALUE_NONE, 'If set will remove all including compiled, twig, doctrine caches')
->addOption('assets-only', null, InputOption::VALUE_NONE, 'If set will remove only assets/*')
->addOption('images-only', null, InputOption::VALUE_NONE, 'If set will remove only images/*')
@@ -33,14 +28,10 @@ class ClearCacheCommand extends Command
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$this->cleanPaths();
}

View File

@@ -1,23 +1,16 @@
<?php
namespace Grav\Console\Cli;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
/**
* Class ComposerCommand
* @package Grav\Console\Cli
*/
class ComposerCommand extends Command
class ComposerCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
* @var
*/
@@ -59,24 +52,19 @@ class ComposerCommand extends Command
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$action = $this->input->getOption('install') ? 'install' : ($this->input->getOption('update') ? 'update' : 'install');
$action = $input->getOption('install') ? 'install' : ($input->getOption('update') ? 'update' : 'install');
if ($input->getOption('install')) {
if ($this->input->getOption('install')) {
$action = 'install';
}
// Updates composer first
$output->writeln("\nInstalling vendor dependencies");
$output->writeln($this->composerUpdate(GRAV_ROOT, $action));
$this->output->writeln("\nInstalling vendor dependencies");
$this->output->writeln($this->composerUpdate(GRAV_ROOT, $action));
}
}

View File

@@ -1,22 +1,17 @@
<?php
namespace Grav\Console\Cli;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
/**
* Class InstallCommand
* @package Grav\Console\Cli
*/
class InstallCommand extends Command
class InstallCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
* @var
*/
@@ -51,24 +46,18 @@ class InstallCommand extends Command
'destination',
InputArgument::OPTIONAL,
'Where to install the required bits (default to current project)'
)
->setDescription("Installs the dependencies needed by Grav. Optionally can create symbolic links")
->setHelp('The <info>install</info> command installs the dependencies needed by Grav. Optionally can create symbolic links');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$dependencies_file = '.dependencies';
$this->destination = ($input->getArgument('destination')) ? $input->getArgument('destination') : ROOT_DIR;
$this->destination = ($this->input->getArgument('destination')) ? $this->input->getArgument('destination') : ROOT_DIR;
// fix trailing slash
$this->destination = rtrim($this->destination, DS) . DS;
@@ -78,7 +67,7 @@ class InstallCommand extends Command
$local_config_file = exec('eval echo ~/.grav/config');
if (file_exists($local_config_file)) {
$this->local_config = Yaml::parse($local_config_file);
$output->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
$this->output->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
}
}
@@ -88,22 +77,22 @@ class InstallCommand extends Command
} elseif (file_exists($this->destination . $dependencies_file)) {
$this->config = Yaml::parse($this->destination . $dependencies_file);
} else {
$output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
$this->output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
}
// If yaml config, process
if ($this->config) {
if (!$input->getOption('symlink')) {
if (!$this->input->getOption('symlink')) {
// Updates composer first
$output->writeln("\nInstalling vendor dependencies");
$output->writeln($this->composerUpdate(GRAV_ROOT, 'install'));
$this->output->writeln("\nInstalling vendor dependencies");
$this->output->writeln($this->composerUpdate(GRAV_ROOT, 'install'));
$this->gitclone();
} else {
$this->symlink();
}
} else {
$output->writeln('<red>ERROR</red> invalid YAML in ' . $dependencies_file);
$this->output->writeln('<red>ERROR</red> invalid YAML in ' . $dependencies_file);
}

View File

@@ -1,29 +1,25 @@
<?php
namespace Grav\Console\Cli;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class NewProjectCommand
* @package Grav\Console\Cli
*/
class NewProjectCommand extends Command
class NewProjectCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
*
*/
protected function configure()
{
$this
->setName("new-project")
->setName('new-project')
->setAliases(['newproject'])
->addArgument(
'destination',
InputArgument::REQUIRED,
@@ -35,37 +31,32 @@ class NewProjectCommand extends Command
InputOption::VALUE_NONE,
'Symlink the required bits'
)
->setDescription("Creates a new Grav project with all the dependencies installed")
->setHelp("The <info>new-project</info> command is a combination of the `setup` and `install` commands.\nCreates a new Grav instance and performs the installation of all the required dependencies.");
->setDescription('Creates a new Grav project with all the dependencies installed')
->setHelp('The <info>new-project</info> command is a combination of the `setup` and `install` commands.\nCreates a new Grav instance and performs the installation of all the required dependencies.');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$sandboxCommand = $this->getApplication()->find('sandbox');
$installCommand = $this->getApplication()->find('install');
$sandboxArguments = new ArrayInput(array(
'command' => 'sandbox',
'destination' => $input->getArgument('destination'),
'-s' => $input->getOption('symlink')
'destination' => $this->input->getArgument('destination'),
'-s' => $this->input->getOption('symlink')
));
$installArguments = new ArrayInput(array(
'command' => 'install',
'destination' => $input->getArgument('destination'),
'-s' => $input->getOption('symlink')
'destination' => $this->input->getArgument('destination'),
'-s' => $this->input->getOption('symlink')
));
$sandboxCommand->run($sandboxArguments, $output);
$installCommand->run($installArguments, $output);
$sandboxCommand->run($sandboxArguments, $this->output);
$installCommand->run($installArguments, $this->output);
}
}

View File

@@ -1,160 +1,34 @@
<?php
namespace Grav\Console\Cli;
use Grav\Common\Data\Blueprints;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Filesystem\Folder;
use Grav\Common\User\User;
use Grav\Console\ConsoleTrait;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Grav\Console\ConsoleCommand;
/**
* Class CleanCommand
*
* @package Grav\Console\Cli
*/
class NewUserCommand extends Command
class NewUserCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
* Configure the command
*/
protected function configure()
{
$this
->setName("newuser")
->setDescription("Creates a new user")
->setHelp('The <info>newuser</info> creates a new user file in user/accounts/ folder');
->setName('newuser')
->setDescription('DEPRECATED: Creates a new user')
->setHelp('The <info>newuser</info> from `bin/grav` has been deprecated. Please refer to `bin/plugin login new-user')
;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$helper = $this->getHelper('question');
$data = [];
$this->output->writeln('<green>Create new user</green>');
$this->output->writeln('');
// Get username and validate
$question = new Question('Enter a <yellow>username</yellow>: ', 'admin');
$question->setValidator(function ($value) {
if (!preg_match('/^[a-z0-9_-]{3,16}$/', $value)) {
throw new RuntimeException(
'Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed'
);
}
if (file_exists(self::getGrav()['locator']->findResource('user://accounts/' . $value . YAML_EXT))) {
throw new RuntimeException(
'Username "'.$value.'" already exists, please pick another username'
);
}
return $value;
});
$username = $helper->ask($this->input, $this->output, $question);
// Get password and validate
$password = $this->askForPassword($helper, 'Enter a <yellow>password</yellow>: ', function ($password1) use ($helper) {
if (!preg_match('/(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/', $password1)) {
throw new RuntimeException('Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters');
}
// Since input is hidden when prompting for passwords, the user is asked to repeat the password
return $this->askForPassword($helper, 'Repeat the <yellow>password</yellow>: ', function ($password2) use ($password1) {
if (strcmp($password2, $password1)) {
throw new RuntimeException('Passwords did not match.');
}
return $password2;
});
});
$data['password'] = $password;
// Get email and validate
$question = new Question('Enter an <yellow>email</yellow>: ');
$question->setValidator(function ($value) {
if (!preg_match('/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/', $value)) {
throw new RuntimeException(
'Not a valid email address'
);
}
return $value;
});
$data['email'] = $helper->ask($this->input, $this->output, $question);
// Choose permissions
$question = new ChoiceQuestion(
'Please choose a set of <yellow>permissions</yellow>:',
array('a'=>'admin access', 's'=>'site access', 'b'=>'admin and site access'),
'a'
);
$question->setErrorMessage('permissions %s is invalid.');
$permissions_choice = $helper->ask($this->input, $this->output, $question);
switch ($permissions_choice) {
case 'a':
$data['access']['admin'] = ['login' => true, 'super' => true];
break;
case 's':
$data['access']['site'] = ['login' => true];
break;
case 'b':
$data['access']['admin'] = ['login' => true, 'super' => true];
$data['access']['site'] = ['login' => true];
}
// Get fullname
$question = new Question('Enter a <yellow>fullname</yellow>: ');
$question->setValidator(function ($value) {
if ($value === null || trim($value) == '') {
throw new RuntimeException(
'Fullname is required'
);
}
return $value;
});
$data['fullname'] = $helper->ask($this->input, $this->output, $question);
// Get title
$question = new Question('Enter a <yellow>title</yellow>: ');
$data['title'] = $helper->ask($this->input, $this->output, $question);
// Create user object and save it
$user = new User($data);
$file = CompiledYamlFile::instance(self::getGrav()['locator']->findResource('user://accounts/' . $username . YAML_EXT, true, true));
$user->file($file);
$user->save();
$this->output->writeln('');
$this->output->writeln('<green>Success!</green> User <cyan>'. $username .'</cyan> created.');
}
/**
* Get password and validate.
*
* @param Helper $helper
* @param string $question
* @param callable $validator
*
* @return string
*/
protected function askForPassword(Helper $helper, $question, callable $validator)
{
$question = new Question($question);
$question->setValidator($validator);
$question->setHidden(true);
$question->setHiddenFallback(true);
return $helper->ask($this->input, $this->output, $question);
$this->output->writeln('<red>DEPRECATED COMMAND</red>');
$this->output->writeln(' <white>`bin/grav new-user`</white> has been <red>deprecated</red> in favor of the new <white>`bin/plugin login new-user`</white>');
}
}

View File

@@ -1,23 +1,17 @@
<?php
namespace Grav\Console\Cli;
use Grav\Console\ConsoleCommand;
use Grav\Common\Filesystem\Folder;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class SandboxCommand
* @package Grav\Console\Cli
*/
class SandboxCommand extends Command
class SandboxCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
* @var array
*/
@@ -54,7 +48,7 @@ class SandboxCommand extends Command
'/.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',
@@ -96,18 +90,14 @@ class SandboxCommand extends Command
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$this->destination = $input->getArgument('destination');
$this->destination = $this->input->getArgument('destination');
// Symlink the Core Stuff
if ($input->getOption('symlink')) {
if ($this->input->getOption('symlink')) {
// Create Some core stuff if it doesn't exist
$this->createDirectories();

View File

@@ -0,0 +1,35 @@
<?php
namespace Grav\Console;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class ConsoleCommand
*
* @package Grav\Console
*/
class ConsoleCommand extends Command
{
use ConsoleTrait;
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$this->serve();
}
/**
*
*/
protected function serve() { }
}

View File

@@ -2,20 +2,16 @@
namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class IndexCommand
*
* @package Grav\Console\Gpm
*/
class IndexCommand extends Command
class IndexCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
* @var
*/
@@ -25,6 +21,16 @@ class IndexCommand extends Command
*/
protected $gpm;
/**
* @var
*/
protected $options;
/**
* @var array
*/
protected $sortKeys = ['name', 'slug', 'author', 'date'];
/**
*
*/
@@ -38,30 +44,68 @@ class IndexCommand extends Command
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');
->setHelp('The <info>index</info> command lists the plugins and themes available for installation')
;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$this->options = $this->input->getOptions();
$this->gpm = new GPM($this->input->getOption('force'));
$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
@@ -93,15 +137,15 @@ class IndexCommand extends Command
*/
private function versionDetails($package)
{
$list = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}();
$package = isset($list[$package->slug]) ? $list[$package->slug] : $package;
$type = ucfirst(preg_replace("/s$/", '', $package->package_type));
$list = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}();
$package = isset($list[$package->slug]) ? $list[$package->slug] : $package;
$type = ucfirst(preg_replace("/s$/", '', $package->package_type));
$updatable = $this->gpm->{'is' . $type . 'Updatable'}($package->slug);
$installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug);
$local = $this->gpm->{'getInstalled' . $type}($package->slug);
$local = $this->gpm->{'getInstalled' . $type}($package->slug);
if (!$installed || !$updatable) {
$version = $installed ? $local->version : $package->version;
$version = $installed ? $local->version : $package->version;
$installed = !$installed ? ' (<magenta>not installed</magenta>)' : ' (<cyan>installed</cyan>)';
return str_pad(" [v<green>" . $version . "</green>]", 35) . $installed;
@@ -116,4 +160,65 @@ class IndexCommand extends Command
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

@@ -2,21 +2,16 @@
namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class InfoCommand
* @package Grav\Console\Gpm
*/
class InfoCommand extends Command
class InfoCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
* @var
*/
@@ -49,20 +44,16 @@ class InfoCommand extends Command
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$this->gpm = new GPM($this->input->getOption('force'));
$foundPackage = $this->gpm->findPackage($input->getArgument('package'));
$foundPackage = $this->gpm->findPackage($this->input->getArgument('package'));
if (!$foundPackage) {
$this->output->writeln("The package <cyan>'" . $input->getArgument('package') . "'</cyan> was not found in the Grav repository.");
$this->output->writeln("The package <cyan>'" . $this->input->getArgument('package') . "'</cyan> was not found in the Grav repository.");
$this->output->writeln('');
$this->output->writeln("You can list all the available packages by typing:");
$this->output->writeln(" <green>" . $this->argv . " index</green>");
@@ -70,7 +61,7 @@ class InfoCommand extends Command
exit;
}
$this->output->writeln("Found package <cyan>'" . $input->getArgument('package') . "'</cyan> under the '<green>" . ucfirst($foundPackage->package_type) . "</green>' section");
$this->output->writeln("Found package <cyan>'" . $this->input->getArgument('package') . "'</cyan> under the '<green>" . ucfirst($foundPackage->package_type) . "</green>' section");
$this->output->writeln('');
$this->output->writeln("<cyan>" . $foundPackage->name . "</cyan> [" . $foundPackage->slug . "]");
$this->output->writeln(str_repeat('-', strlen($foundPackage->name) + strlen($foundPackage->slug) + 3));

View File

@@ -5,14 +5,10 @@ use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Common\Inflector;
use Grav\Common\Utils;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Yaml\Yaml;
@@ -23,10 +19,8 @@ define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/
* Class InstallCommand
* @package Grav\Console\Gpm
*/
class InstallCommand extends Command
class InstallCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
* @var
*/
@@ -87,15 +81,10 @@ class InstallCommand extends Command
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$this->gpm = new GPM($this->input->getOption('force'));
$this->destination = realpath($this->input->getOption('destination'));

View File

@@ -5,22 +5,17 @@ use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Common\GPM\Upgrader;
use Grav\Console\ConsoleTrait;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
/**
* Class SelfupgradeCommand
* @package Grav\Console\Gpm
*/
class SelfupgradeCommand extends Command
class SelfupgradeCommand extends ConsoleCommand
{
use ConsoleTrait;
/**
* @var
*/
@@ -75,14 +70,10 @@ class SelfupgradeCommand extends Command
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function serve()
{
$this->setupConsole($input, $output);
$this->upgrader = new Upgrader($this->input->getOption('force'));
$update = $this->upgrader->getAssets()['grav-update'];

Some files were not shown because too many files have changed in this diff Show More