Compare commits

...

428 Commits

Author SHA1 Message Date
Andy Miller
11869ad4ec Merge branch 'release/0.9.35' 2015-08-06 18:38:48 -06:00
Andy Miller
11fc34cfed version update 2015-08-06 18:38:35 -06:00
Andy Miller
b5717c2cbb Merge branch 'develop' of https://github.com/getgrav/grav into develop
# By Djamil Legato (1) and Sommerregen (1)
# Via Andy Miller (1) and Djamil Legato (1)
* 'develop' of https://github.com/getgrav/grav:
  Fixed GPM issue when using cURL throwing an `Undefined offset: 1` exception
  Fix #248 (Plugin language interference) and fixed summary
2015-08-06 18:31:21 -06:00
Andy Miller
0c085a5aab Override save to not store the username field 2015-08-06 18:31:08 -06:00
Andy Miller
5cee23cbfa Added new unsetRoute() to allow route() to rebuild 2015-08-06 18:30:50 -06:00
Andy Miller
1e168b3100 made username disabled 2015-08-06 18:30:24 -06:00
Djamil Legato
018f7a6dec Fixed GPM issue when using cURL throwing an Undefined offset: 1 exception 2015-08-06 11:31:37 -07:00
Andy Miller
a62e88f22b Merge pull request #249 from Sommerregen/bugfix/plugin-language-interference
Fix #248 (Plugin language interference) and fixed summary
2015-08-06 12:20:52 -06:00
Sommerregen
dc5ba9eff4 Fix #248 (Plugin language interference) and fixed summary 2015-08-06 19:59:20 +02:00
Andy Miller
4bebdfe0c7 fixed metadata merging (hopefully!) 2015-08-05 22:27:26 -06:00
Andy Miller
f8fd065192 removed unused body_classes in modular type 2015-08-05 22:26:57 -06:00
Andy Miller
9d38d0818b fix robots field 2015-08-05 22:26:41 -06:00
Andy Miller
a1ad9b7f4d list of pages should show all except root 2015-08-05 18:06:50 -06:00
Andy Miller
29cb55e91c visible toggle and help commands on new page 2015-08-05 18:06:07 -06:00
Andy Miller
0687d2ff78 added body_classes to modular type 2015-08-05 18:05:34 -06:00
Andy Miller
cd04572b78 using new templates field type 2015-08-05 18:04:57 -06:00
Andy Miller
6bb47124a9 added body classes 2015-08-05 18:03:34 -06:00
Andy Miller
1a21186ba1 removed duplicate hidden type field 2015-08-05 18:03:16 -06:00
Andy Miller
82d1193090 removed required from title 2015-08-05 18:02:48 -06:00
Andy Miller
ea76ac024a Merge pull request #244 from notklaatu/develop
added conf settings for running grav in sud dir, on nginx
2015-08-05 09:39:21 -06:00
Klaatu
ff52d61322 added conf settings for running grav in sud dir, on nginx 2015-08-05 11:44:38 +12:00
Andy Miller
83e970731e Merge branch 'release/0.9.34' into develop 2015-08-04 16:53:33 -06:00
Andy Miller
c6ea5eb5e9 Merge branch 'release/0.9.34' 2015-08-04 16:53:32 -06:00
Andy Miller
aa7a4111e6 version update 2015-08-04 16:53:16 -06:00
Andy Miller
2b01f832bd Merge branch 'develop' of https://github.com/getgrav/grav into develop
# By Sommerregen
# Via Andy Miller (1) and Sommerregen (1)
* 'develop' of https://github.com/getgrav/grav:
  Add language property to page
2015-08-04 09:41:21 -06:00
Andy Miller
1401102396 more extensive extension() method 2015-08-04 09:40:59 -06:00
Andy Miller
8963d024a6 more dynamic extension() method 2015-08-04 09:40:28 -06:00
Andy Miller
bb5e7b508f Merge pull request #243 from Sommerregen/feature/add-language-property-to-page
Add language property to page
2015-08-03 16:46:13 -06:00
Andy Miller
bfc9efea92 added new arrayFilterRecursive 2015-08-03 16:11:02 -06:00
Andy Miller
75feea3e75 back to Symfony 2.7.3 2015-08-03 16:10:16 -06:00
Sommerregen
e142f5ee61 Add language property to page 2015-08-03 22:08:07 +02:00
Djamil Legato
9746c2db5d Move yaml missing 2015-08-03 12:43:24 -07:00
Andy Miller
311598e3a6 Merge pull request #242 from flaviocopes/patch-1
Fix moving the page to the root
2015-08-03 08:15:26 -06:00
Andy Miller
109d19f02e changed order 2015-08-03 08:01:27 -06:00
Flavio Copes
bc5ea13821 Fix moving the page to the root
$parent->route() returned null when moving to the root
2015-08-03 10:19:56 +02:00
Andy Miller
e9cc34f481 Merge pull request #241 from aptly-io/fix_twig_vars_sideeffect
Fix #240 I'm ok with this.
2015-08-02 20:07:31 -06:00
franchan
9f254b6c84 Fix #240
$page->content() fires the onPageContentRaw event.
A plugin handler for this event might want to update the twig variable
with additional information during twig rendering.

However, $twig_vars takes a copy of $Twig->twig_vars and
therefore never sees any later changes in $Twig->twig_vars.

By moving the method call $page->content() earlier,
potential twig variable changes from a plugin get now inside this
local variable copy.
2015-08-02 23:52:48 +02:00
Andy Miller
4f442d1edc support admin overrides of blueprints 2015-08-02 12:13:08 -06:00
Andy Miller
a1a10ab23d more tweaks for blueprints 2015-08-01 17:40:58 -06:00
Andy Miller
21beefd387 added array() function to cast as an array 2015-08-01 13:37:32 -06:00
Andy Miller
a7e03c9d5c Stopped storing metadata in page header 2015-08-01 13:37:12 -06:00
Andy Miller
49781f9717 made metadata optional 2015-08-01 13:36:52 -06:00
Andy Miller
ca12c69741 fix for system lang files 2015-08-01 10:20:15 -06:00
Djamil Legato
333814eab0 Reenabled toggleables for Published 2015-07-31 13:10:51 -07:00
Djamil Legato
c18311eeb2 Moved blog and blog item blueprints out of Grav and into Antimatter theme 2015-07-31 13:10:24 -07:00
Andy Miller
3caa0d1ef5 Merge pull request #237 from flaviocopes/feature/separate-sessions-for-site-and-admin
Feature/separate sessions for site and admin
2015-07-31 11:05:56 -06:00
Flavio Copes
dc56f85881 Separate sessions for site and admin 2015-07-31 18:21:43 +02:00
Flavio Copes
532e035724 Load uri before session 2015-07-31 18:21:06 +02:00
Andy Miller
c3431e1ead Merge branch 'develop' of https://github.com/getgrav/grav into develop
# By Flavio Copes
# Via Andy Miller (3) and Flavio Copes (1)
* 'develop' of https://github.com/getgrav/grav:
  Move $session_timeout & $session_path inside the if statement
  Correctly instantiate the Inflector
  Force english in admin
  Refresh session timeout
2015-07-31 08:35:46 -06:00
Andy Miller
3016e77897 loose validation so un-formed values are not lost 2015-07-31 08:35:35 -06:00
Andy Miller
cf69ba0d66 Merge pull request #236 from flaviocopes/patch-4
Correctly instantiate the Inflector
2015-07-31 08:34:27 -06:00
Andy Miller
4e82891779 Merge pull request #232 from flaviocopes/patch-2
Refresh session timeout
2015-07-31 08:33:25 -06:00
Flavio Copes
7fa3e7bf28 Move $session_timeout & $session_path inside the if statement 2015-07-31 16:06:02 +02:00
Flavio Copes
656c23a891 Correctly instantiate the Inflector
$this->grav is not what we expect here. Got a fatal error on `$ bin/gpm` execution
2015-07-31 16:01:52 +02:00
Andy Miller
00f758dd98 Merge pull request #235 from flaviocopes/patch-3
Force english in admin
2015-07-31 07:52:52 -06:00
Flavio Copes
8962d2b1e4 Force english in admin 2015-07-31 15:48:47 +02:00
Flavio Copes
eefb761e98 Refresh session timeout 2015-07-30 19:17:04 +02:00
Andy Miller
9363da137f Merge branch 'develop' of https://github.com/getgrav/grav into develop
# By Djamil Legato
# Via Djamil Legato
* 'develop' of https://github.com/getgrav/grav:
  Added support for select multiple attribute in array filter
2015-07-29 22:00:30 -06:00
Andy Miller
c4daae2f95 added new blueprintName() method that takes into account blueprint value passed via a form 2015-07-29 22:00:03 -06:00
Andy Miller
b201b21e46 added blueprint hidden field to standard blueprints 2015-07-29 21:59:24 -06:00
Andy Miller
c959fd9c48 rolled back to composer 2.7.1 due to broken CLI choice method 2015-07-29 21:58:53 -06:00
Djamil Legato
0845b5e28a Added support for select multiple attribute in array filter 2015-07-29 20:43:13 -07:00
Andy Miller
b9f9570033 minor reformatting 2015-07-29 12:19:56 -06:00
Andy Miller
baa0e73703 updated default yaml 2015-07-29 11:31:50 -06:00
Andy Miller
e97aaccfee added a cache_all option for media images 2015-07-28 20:57:16 -06:00
Andy Miller
f58cfd8571 Merge branch 'feature/blueprint-extend' into develop
Conflicts:
	system/blueprints/config/site.yaml
	system/blueprints/config/system.yaml
2015-07-28 18:36:02 -06:00
Andy Miller
1a43d31c50 whitespace removal 2015-07-28 16:23:49 -06:00
Andy Miller
d244442e70 fixed translations with multiple plugins 2015-07-28 12:11:07 -06:00
Djamil Legato
a009c56271 Supported Language and Taxonomies are now using Selectize tags in admin 2015-07-28 11:08:37 -07:00
Andy Miller
f30af37faf php tidy 2015-07-28 11:10:38 -06:00
Andy Miller
bb4eea7999 missing use statement - issue #230 2015-07-28 04:54:16 -06:00
Andy Miller
e26b80d873 removed some extraneous messages 2015-07-27 08:32:29 -06:00
Andy Miller
b23aa1bc4c tweaks to system blueprint 2015-07-24 17:29:37 -06:00
Andy Miller
e22655b440 updated site.yaml blueprint 2015-07-24 15:16:36 -06:00
Andy Miller
8e57839271 missing some init() checks 2015-07-24 10:55:57 -06:00
Andy Miller
16f779c8f5 changed Inflector from static to class + added multilang support 2015-07-24 10:26:54 -06:00
Andy Miller
bd08d787f2 typo 2015-07-24 08:20:46 -06:00
Andy Miller
df99fe0073 blueprint updates 2015-07-24 08:20:36 -06:00
Andy Miller
7a2fceaee9 renamed translations.fallback property 2015-07-24 08:08:50 -06:00
Andy Miller
bb69656bd7 added base languages config 2015-07-24 08:08:23 -06:00
Andy Miller
5be0618b4d more blueprints progress 2015-07-24 07:50:47 -06:00
Andy Miller
ad00252a4e base translations settings 2015-07-24 07:50:35 -06:00
Andy Miller
84eee30d1f fix for string/array conversion error 2015-07-24 07:50:18 -06:00
Andy Miller
8975c2936c remove unused use that is causing an error 2015-07-23 11:36:43 -06:00
Andy Miller
b64bfc9ab0 more progress on system blueprint 2015-07-23 11:36:26 -06:00
Andy Miller
cc9f5ed096 More validation updates 2015-07-22 15:31:32 -06:00
Andy Miller
2161daa398 Prettier validation messages 2015-07-22 15:31:05 -06:00
Andy Miller
f1a41394ab updates for account validation 2015-07-22 15:30:27 -06:00
Andy Miller
6d5bb6f887 Merge branch 'release/0.9.33' 2015-07-21 13:48:10 -06:00
Andy Miller
9bcf9a7dcd Merge branch 'release/0.9.33' into develop 2015-07-21 13:48:10 -06:00
Andy Miller
6d69f03111 version update 2015-07-21 13:48:00 -06:00
Andy Miller
0b709ddb1a cleanup 2015-07-21 11:50:18 -06:00
Andy Miller
6ba780ba3b support translate taking string or array for key 2015-07-21 11:50:08 -06:00
Andy Miller
f0f24142b2 fix for broken auto-file-extension setting 2015-07-20 18:28:58 -06:00
Andy Miller
a716da14f7 Allow extension to be set manually, else set it from the file 2015-07-20 16:35:33 -06:00
Andy Miller
d8a80479c2 more explicit check for page extension 2015-07-20 16:35:21 -06:00
Andy Miller
8d4a55a3a4 added Vary: Accept-Encoding option. Off by default 2015-07-19 13:22:15 -06:00
Andy Miller
0c0aa94ded routable() now takes published() into account. Re issue #227 2015-07-17 17:31:36 -06:00
Andy Miller
0fd4991dd4 Fix for issue #225 - zip skipping required empty folders 2015-07-17 17:06:20 -06:00
Andy Miller
5f32ea64eb Extended ImageFile to provide event on creation function 2015-07-17 16:06:23 -06:00
Andy Miller
dc6705c74a added a new onImageMediumSaved event - useful for compression services 2015-07-17 14:04:19 -06:00
Andy Miller
4479f251a6 removed pageinit timing - was not doing much 2015-07-17 14:03:30 -06:00
Andy Miller
ac076fb892 fix for twig cache permissions 2015-07-15 10:18:23 -06:00
Andy Miller
503b38c17f fix for #224 - summary delimiter not getting proper position in certain conditions 2015-07-14 22:58:06 -06:00
Andy Miller
7b6680cde2 Merge branch 'release/0.9.32' into develop 2015-07-14 15:03:03 -06:00
Andy Miller
7029edfd58 Merge branch 'release/0.9.32' 2015-07-14 15:03:02 -06:00
Andy Miller
3b3ac68f47 version update 2015-07-14 15:02:49 -06:00
Andy Miller
5e28d04aa6 fixed a typo 2015-07-14 09:06:32 -06:00
Andy Miller
102140b917 Support 'en' translations independent from language support 2015-07-13 21:02:24 -06:00
Andy Miller
17d7e19e84 Added support for http_accept_language #219 2015-07-13 13:01:32 -06:00
Andy Miller
d7b810e87e fix for session active lang and homepage redirects 2015-07-13 12:14:52 -06:00
Andy Miller
3cf44b4c62 added translateArray() functionality 2015-07-13 11:57:20 -06:00
Andy Miller
96a9962895 minor language tweaks 2015-07-13 11:56:47 -06:00
Andy Miller
b2cd4db395 Default generator updated 2015-07-13 11:55:12 -06:00
Andy Miller
015ef2c6ce Fix for #218 - ignore root level page 2015-07-13 11:48:56 -06:00
Andy Miller
0ba1af244d Merge branch 'release/0.9.31' 2015-07-09 17:31:21 -06:00
Andy Miller
547a049140 Merge branch 'release/0.9.31' into develop 2015-07-09 17:31:21 -06:00
Andy Miller
05e0013990 version update 2015-07-09 17:30:26 -06:00
Andy Miller
b9d6f75923 Updated all bin/grav CLI commands to use ConsoleTrait 2015-07-09 17:23:56 -06:00
Andy Miller
07f4cb0892 updated to latest composer.par 2015-07-09 17:23:33 -06:00
Andy Miller
e48be33c6c unified backup command to mimic admin functionality 2015-07-09 17:08:29 -06:00
Andy Miller
b3ba54894e removed useless call 2015-07-09 17:07:52 -06:00
Andy Miller
14753d499d Fix for markdown filter error: Issue #115 2015-07-09 14:47:24 -06:00
Andy Miller
bb348fff94 fix for translations 2015-07-09 11:56:46 -06:00
Andy Miller
c097907098 safety check 2015-07-09 09:44:10 -06:00
Andy Miller
3719bac746 use an array for clarity 2015-07-08 20:05:26 -06:00
Andy Miller
85add10bef better handling of unsupported media types download 2015-07-08 20:04:15 -06:00
Andy Miller
8a7a9d4a45 added a configurable array for unsupported file types to inline rather than download 2015-07-08 20:03:43 -06:00
Andy Miller
875885f647 changed pages.types to be an array 2015-07-08 20:03:04 -06:00
Andy Miller
3af85ed14f added xml, json, css, and js to supported media "file" types 2015-07-08 20:01:55 -06:00
Andy Miller
34f6020e7f Merge branch 'release/0.9.30' 2015-07-08 13:26:01 -06:00
Andy Miller
2a6b96937c Merge branch 'release/0.9.30' into develop 2015-07-08 13:26:01 -06:00
Andy Miller
8861918827 version update 2015-07-08 13:25:50 -06:00
Andy Miller
6212aef8b1 added changelog 2015-07-08 12:59:28 -06:00
Andy Miller
f13c4e916a added option to store active lang in session 2015-07-08 11:39:08 -06:00
Andy Miller
60e183a8c0 Added a language-safe redirect option that appends lang if set 2015-07-08 11:23:51 -06:00
Andy Miller
939449c1b0 Added an option to always redirect to the default route if the route doesn't match default 2015-07-08 11:23:21 -06:00
Andy Miller
5970e5bf5c added ability to specify language to translate 2015-07-07 21:40:07 -06:00
Andy Miller
aee5a38313 don't hard-fail on missing language home alias 2015-07-07 21:27:17 -06:00
Andy Miller
19b2de7040 Use new location for Language file 2015-07-07 18:57:13 -06:00
Andy Miller
20d7ed5421 New Language files 2015-07-07 18:56:59 -06:00
Andy Miller
0dd53aea02 more robust active() and activeChild() methods to support multiple routes 2015-07-07 18:56:40 -06:00
Andy Miller
801d5d361a Add session support into Grav core (was in login plugin) 2015-07-07 14:21:30 -06:00
Andy Miller
dc7362c70b various sensiolab fixes 2015-07-06 21:50:05 -06:00
Andy Miller
fc3c0e7fa5 Merge branch 'feature/better_language_yaml_support' into develop 2015-07-06 21:30:51 -06:00
Andy Miller
e172f5c82d add toggle for translations.fallback 2015-07-06 21:20:15 -06:00
Andy Miller
3a7426ed04 more robust logic 2015-07-06 21:08:45 -06:00
Andy Miller
3ab1feb7b1 handle array for values 2015-07-06 20:53:04 -06:00
Andy Miller
1c90ee6db3 more support for translations in plugins/themes 2015-07-06 20:47:04 -06:00
Andy Miller
046136bc37 improved with plugin merge 2015-07-06 17:19:22 -06:00
Andy Miller
1d0234550e first whack at providing language yaml support 2015-07-05 21:48:13 -06:00
Andy Miller
cca3a039ad set some config defaults 2015-07-05 18:43:48 -06:00
Andy Miller
e73d101c97 fixed page expires again 2015-07-05 14:55:22 -06:00
Andy Miller
389e53207e Merge branch 'feature/lang_test' into develop 2015-07-05 14:53:46 -06:00
Andy Miller
5bdce4fb28 And the actual jQuery file! 2015-07-05 14:52:48 -06:00
Andy Miller
b06e3b3281 updated jQuery reference to v2.1.4 2015-07-05 14:50:54 -06:00
Andy Miller
a4d39ce424 Added translation Twig function and filter 2015-07-05 13:58:33 -06:00
Andy Miller
79f5aaa032 added a default route alias - particularly useful for multilane switching 2015-07-04 10:11:40 -06:00
Andy Miller
740ea2e86d removed unused method for now 2015-07-04 09:40:41 -06:00
Andy Miller
364f95ec45 add active lang to the base_url stuff 2015-07-03 16:16:36 -06:00
Andy Miller
6668a7b8ab save standard routing in aliases if default overrides 2015-07-03 15:41:39 -06:00
Andy Miller
571f27d518 minor modifications 2015-07-03 14:05:15 -06:00
Andy Miller
0fa70b5b35 better fallback approach 2015-07-03 08:20:21 -06:00
Andy Miller
be8d424404 added some fallback logic - still needs work! 2015-07-02 22:06:07 -06:00
Andy Miller
08e239fcf4 Added lang template paths for theme by default 2015-07-02 15:53:05 -06:00
Andy Miller
947cb79558 optimized enabled test 2015-07-02 15:50:05 -06:00
Andy Miller
1f1df9afc7 support lang specific homepage 2015-07-02 11:19:44 -06:00
Andy Miller
ef40bb25fd added a timer around pageInitialized event 2015-07-02 09:46:57 -06:00
Andy Miller
e2db025aa0 minor updates 2015-07-01 20:50:36 -06:00
Andy Miller
63369247ab fix for regex and last language 2015-07-01 15:56:56 -06:00
Andy Miller
8c695df9da fixed template name based on current lang 2015-07-01 11:07:09 -06:00
Andy Miller
1c9d41ad58 some more multilane progress 2015-07-01 10:59:08 -06:00
Andy Miller
e7721d21a2 Merge branch 'develop' into feature/lang_test
* develop:
  broke-out fallback into a protected method
  fix for content mismatch error
  add canonical routes like an alias
  add canonical route support
2015-06-30 16:56:04 -06:00
Andy Miller
547f72919f broke-out fallback into a protected method 2015-06-30 16:22:43 -06:00
Andy Miller
2b6740dc7b fix for content mismatch error 2015-06-30 16:14:06 -06:00
Andy Miller
1a91cf7033 add canonical routes like an alias 2015-06-30 14:59:48 -06:00
Andy Miller
1e9418b87b add canonical route support 2015-06-30 14:59:24 -06:00
Andy Miller
bedf1555af invalid method name 2015-06-29 22:04:21 -06:00
Andy Miller
5d070f4d9e Merge branch 'develop' into feature/lang_test
* develop:
  Fix for #212 - media timestamp typo
  get route() dynamically and add any route aliases
  added accessor for routeAliases
  small optimization
  initial default override support
  Potential fix for Insight (sensio labs)
  lost default page timeout in last release.. resetting

Conflicts:
	system/config/system.yaml
2015-06-29 22:01:40 -06:00
Andy Miller
7eff48bd4f Merge branch 'feature/custom_page_routes' into develop 2015-06-29 21:56:50 -06:00
Andy Miller
dfccfb3cf3 Fix for #212 - media timestamp typo 2015-06-29 15:38:53 -06:00
Andy Miller
aeebc13d83 get route() dynamically and add any route aliases 2015-06-29 15:30:53 -06:00
Andy Miller
0a42ace4ba added accessor for routeAliases 2015-06-29 15:30:32 -06:00
Andy Miller
7b4f0a29ac small optimization 2015-06-29 15:30:18 -06:00
Andy Miller
b529456745 initial default override support 2015-06-29 12:51:05 -06:00
Djamil Legato
f1a929624f Potential fix for Insight (sensio labs) 2015-06-28 12:12:44 -07:00
Andy Miller
65285ad11e lost default page timeout in last release.. resetting 2015-06-28 09:24:20 -06:00
Andy Miller
eb421bc95e more progress on matching lang keys 2015-06-28 09:23:48 -06:00
Andy Miller
917a5c3082 some initial lang work 2015-06-26 12:25:22 -06:00
Andy Miller
ea491fbde5 Merge pull request #211 from barryanders/develop
Spelling corrections.
2015-06-26 09:41:02 -06:00
Barry Anders
a98d01ba65 Spelling corrections. 2015-06-26 10:20:49 -05:00
Andy Miller
7e2fa8295a fixed ordering on a couple of filters 2015-06-25 16:25:45 -06:00
Andy Miller
adf758a569 updated some config files 2015-06-25 16:25:31 -06:00
Andy Miller
49941105dc Fix issue where page-based css and js were being downloaded rather than processed 2015-06-23 18:58:24 -06:00
Andy Miller
c07afab981 support arrays in startsWith() and endsWith() 2015-06-23 18:57:54 -06:00
Andy Miller
c2b07fec1f Merge branch 'release/0.9.29' 2015-06-22 17:10:37 -06:00
Andy Miller
7f8baf326e Merge branch 'release/0.9.29' into develop 2015-06-22 17:10:37 -06:00
Andy Miller
9601061f25 version update 2015-06-22 17:10:24 -06:00
Andy Miller
b1317e56ec Disabled last_modified and etag page header by default 2015-06-22 15:30:01 -06:00
Andy Miller
353832c386 Fix for directory relative 'down' links 2015-06-22 12:56:11 -06:00
Andy Miller
8bf04d4593 Merge pull request #207 from Sommerregen/feature/enable-custom-summaries
Modified `$page->summary()` to allow custom summary assignments
2015-06-21 13:27:56 -06:00
Sommerregen
c9f0500c6f Refactored code. Added $page->setSummary(<string>) method. 2015-06-21 11:03:30 +02:00
Andy Miller
0dc465bb41 PR #145 - Set all but debugger to specific versions 2015-06-20 12:11:00 -06:00
Andy Miller
34e50aab21 remove replicated code... 2015-06-20 11:54:46 -06:00
Andy Miller
5a2411a0e6 Merge pull request #206 from Sommerregen/feature/add-build-pages-event
Add `onBuildPagesInitialized` event for memory & time consuming plugins
2015-06-20 11:51:12 -06:00
Andy Miller
178f66c940 Merge pull request #189 from Sommerregen/feature/params-option-merge-config
Added params option for mergeConfig method
2015-06-20 11:45:32 -06:00
Sommerregen
6d3a7a3989 Fixed variable name 2015-06-20 18:24:19 +02:00
Sommerregen
2400eaf04e Resolving conflicts after rebase 2015-06-20 14:42:28 +02:00
Sommerregen
752a3ca5bd Incorporated Gertt's modifications into PR 2015-06-20 14:39:24 +02:00
Sommerregen
6dbb6eb432 Changed "mergeConfig" head to match previous method signature 2015-06-20 14:38:57 +02:00
Sommerregen
e580bb9998 Added params option for mergeConfig method 2015-06-20 14:38:33 +02:00
Sommerregen
d309491f06 Modified $page->summary() to allow custom summary assignments 2015-06-20 13:54:57 +02:00
Sommerregen
0e35048143 Add onBuildPagesInitialized event for memory and time consuming plugins 2015-06-20 13:53:07 +02:00
Andy Miller
c919ed36b4 Error handling around bad regex .. just to be safer 2015-06-17 17:53:32 -06:00
Andy Miller
6d2a7c53dc very simple, yet surprisingly powerful Regex style redirect and route support 2015-06-17 17:39:44 -06:00
Djamil Legato
a8582fc131 Hello Travis! 2015-06-17 12:58:23 -07:00
Djamil Legato
76b7bd855d initial travis setup to trigger detection 2015-06-16 23:06:57 -07:00
Andy Miller
5656bb3caf Pre-load ArrayInput to avoid Exception if it gets moved/replaced during upgrade 2015-06-16 20:51:38 -06:00
Andy Miller
57bd4d8f22 Merge branch 'release/0.9.28' 2015-06-16 16:15:42 -06:00
Andy Miller
e71cd5a7ad Merge branch 'release/0.9.28' into develop 2015-06-16 16:15:42 -06:00
Andy Miller
4132388dda version update 2015-06-16 16:15:25 -06:00
Andy Miller
1edabe3b00 added page level overrides of etag and last_modified header flags 2015-06-16 10:25:27 -06:00
Andy Miller
019fdd65e9 added toggles to enable/disable last_modified and etag headers 2015-06-16 09:58:56 -06:00
Andy Miller
fa432cd32f Another incorrect slug name 2015-06-15 16:03:22 -06:00
Andy Miller
4935679659 fix #202 - incorrect slug name causing issues 2015-06-15 15:00:49 -06:00
Andy Miller
835c64c173 comment fix 2015-06-15 14:59:44 -06:00
Andy Miller
4d6ecbe618 Moved convertUrl() from ParsedownGravTrait into Uri as a static method 2015-06-14 17:54:22 -06:00
Andy Miller
63456aad85 reset how symfony libs versions are set in composer 2015-06-13 06:45:49 -06:00
Andy Miller
bec2ee91b5 updated symphony versions 2015-06-13 06:44:17 -06:00
Andy Miller
027c9cdd04 updated to latest cache library - should be a little faster! 2015-06-12 22:32:23 -06:00
Andy Miller
8ecea3a8c1 some more fixes for image handling in certain scenarios 2015-06-12 15:54:20 -06:00
Andy Miller
69e6d57346 Merge pull request #200 from Seebz/issue-199
Fix for issue #199 - Looks good! Thanks
2015-06-08 14:47:06 +01:00
Seebz
8127d9cf31 Fix for issue #199 2015-06-04 16:46:18 +02:00
Gert
bc1a9b31fa add @config directive to get config values from blueprints 2015-06-03 16:21:49 +02:00
Gert
ff5658e803 update blueprints 2015-06-01 21:23:53 +02:00
Gert
3d986cdd91 Merge branch 'develop' of github.com:getgrav/grav into feature/blueprint-extend
* 'develop' of github.com:getgrav/grav:
  optimization to image handling supporting url encoded filenames and removed a regex
  Fix for #196 - `+` in an image filename
2015-06-01 11:43:39 +02:00
Andy Miller
32810efcd9 optimization to image handling supporting url encoded filenames and removed a regex 2015-05-27 14:55:50 +02:00
Andy Miller
76b463792e Fix for #196 - + in an image filename 2015-05-26 16:18:59 +03:00
Gert
b4a0a31539 fix pages.process defaults 2015-05-25 17:33:57 +02:00
Gert
acbc7efdc8 remove theme selection from general settings 2015-05-25 17:23:24 +02:00
Gert
8475e0803a Merge branch 'develop' of github.com:getgrav/grav into feature/blueprint-extend
* 'develop' of github.com:getgrav/grav:
  removed unneeded flags from reggae
  fix for dot files
  fix for path detection on windows [fix #194]
  Fix for issue #194 - query string handling
2015-05-25 17:18:41 +02:00
Andy Miller
f779fc57df removed unneeded flags from reggae 2015-05-22 11:43:23 +03:00
Andy Miller
7afef9073c fix for dot files 2015-05-21 22:44:11 +02:00
Gert
370b5db34e remove unexisting option 2015-05-20 22:09:43 +02:00
Gert
6adabb5f71 add ignore field for removing fields on extended blueprints 2015-05-20 21:55:04 +02:00
Gert
df9a0eeab2 set context for default blueprint extends 2015-05-20 21:30:35 +02:00
Gert
e6d58b780e load system blueprints as a fallback for pages 2015-05-20 20:33:40 +02:00
Gert
e4b65d5d7f add check for themes without blueprints/templates folders 2015-05-20 18:18:32 +02:00
Gert
bf61a123cc implement blueprints for the list type 2015-05-20 17:13:22 +02:00
Gert
71f0757015 remove debug line 2015-05-20 16:52:41 +02:00
Gert
f1e57e0e9c modular type needs to have modular/ prefix 2015-05-20 16:45:54 +02:00
Gert
1147516dcc fix for path detection on windows [fix #194] 2015-05-20 16:07:39 +02:00
Andy Miller
3f1661965b Fix for issue #194 - query string handling 2015-05-18 18:54:08 +02:00
Gert
adb0b3ab18 Merge branch 'develop' of github.com:getgrav/grav into feature/blueprint-extend
* 'develop' of github.com:getgrav/grav:
  use PHP_BINARY constant
  fix composer execution for various scenarios
  Remove unnecessary white space
  Move default location to class constant
  Improve composer location identification
  Fix line break in command
  Propagate composer check to all occurences
  Use global composer install when available
2015-05-15 22:59:31 +02:00
Gert
8afad07146 Merge branch 'fix/composer-execution' into develop
* fix/composer-execution:
  use PHP_BINARY constant
  fix composer execution for various scenarios
2015-05-15 21:05:33 +02:00
Gert
81bce07a6e use PHP_BINARY constant 2015-05-15 20:16:42 +02:00
Gert
e883b57ac6 fix composer execution for various scenarios 2015-05-15 19:54:33 +02:00
Gert
921685ff88 fix blueprint type changing when extends 2015-05-15 17:32:21 +02:00
Andy Miller
ae2f95b1ae Merge pull request #192 from eschmar/feature/composer-global
Fix composer location method
2015-05-15 16:44:44 +02:00
Gert
4f77ef26b5 fix default values for process header 2015-05-15 11:40:38 +02:00
Marcel Eschmann
d8df9ffb53 Remove unnecessary white space 2015-05-15 00:20:08 +02:00
Marcel Eschmann
3c51c0acd4 Move default location to class constant 2015-05-15 00:03:48 +02:00
Marcel Eschmann
c085540143 Improve composer location identification 2015-05-14 23:56:55 +02:00
Marcel Eschmann
d239dd56d5 Fix line break in command 2015-05-14 23:32:16 +02:00
Andy Miller
8f9eb3b48b Merge pull request #191 from eschmar/feature/composer-global
Use global composer install when available
2015-05-14 23:15:25 +02:00
Marcel Eschmann
1f906e6a50 Propagate composer check to all occurences 2015-05-14 21:37:47 +02:00
Marcel Eschmann
e0a4efe181 Use global composer install when available 2015-05-14 21:10:05 +02:00
Gert
78e9c8fa1a Merge branch 'develop' of github.com:getgrav/grav into feature/blueprint-extend
* 'develop' of github.com:getgrav/grav:
  add method to set raw markdown on page
  fix modularTypes key properties
2015-05-13 19:41:18 +02:00
Gert
9382dc9c10 add method to set raw markdown on page 2015-05-13 18:52:13 +02:00
Gert
aa85f20aa9 fix modularTypes key properties 2015-05-13 18:50:13 +02:00
Gert
4ae01d48ae fix prefix split on empty prefix 2015-05-13 18:48:52 +02:00
Gert
fe3082c6c9 update taxonomy blueprint 2015-05-13 15:39:37 +02:00
Gert
ab6c257ba6 use new taxonomy field + for all pages 2015-05-12 00:49:31 +02:00
Gert
628ae561d5 more sensible approach to blueprint::extra prefix handling 2015-05-11 12:33:03 +02:00
Gert
56b5a65b24 Merge branch 'develop' of github.com:getgrav/grav into feature/blueprint-extend
* 'develop' of github.com:getgrav/grav:
  fix for alternative media resolutions
  version update
  updated changelog
  composer.par updated to latest
  make sure lookup path does not become empty (root sites)
  ignore .DS_Store in media lookup
  check if medium was created before setting size
  re-add size property to media
2015-05-11 12:29:52 +02:00
Gert
73654a99f9 fix for alternative media resolutions 2015-05-11 11:43:23 +02:00
Andy Miller
ea8add59b1 Merge branch 'release/0.9.27' 2015-05-09 13:14:33 -06:00
Andy Miller
5078ae62c0 Merge branch 'release/0.9.27' into develop 2015-05-09 13:14:33 -06:00
Andy Miller
e026ba32f4 version update 2015-05-09 13:14:23 -06:00
Andy Miller
e9ebe3b533 updated changelog 2015-05-09 12:53:29 -06:00
Andy Miller
cea130700d composer.par updated to latest 2015-05-09 12:36:22 -06:00
Andy Miller
0ae7ebac68 Merge branch 'feature/media-sizes' into develop 2015-05-09 12:25:55 -06:00
Gert
326f1bc890 update item blueprint 2015-05-09 16:47:16 +02:00
Gert
cabec818e2 make sure lookup path does not become empty (root sites) 2015-05-09 16:08:33 +02:00
Gert
ff04b33efd provide default blueprints for common page types 2015-05-09 15:58:17 +02:00
Gert
4f1a71b145 fix extra prefix implementation for extra data of blueprints 2015-05-09 15:58:07 +02:00
Gert
f7f8aa108a allow custom context for extending blueprints 2015-05-09 15:57:55 +02:00
Gert
718d443d52 trim trailing slashes from path during page lookup [fixes #190] 2015-05-09 15:07:15 +02:00
Andy Miller
57c5885216 don't cache twig template when you pass params 2015-05-07 16:01:11 -06:00
Andy Miller
14767a3e11 added twig filters for starts_with and ends_with 2015-05-07 14:13:05 -06:00
Sommerregen
fb31caefef Changed "mergeConfig" head to match previous method signature 2015-05-06 23:01:35 +02:00
Sommerregen
7ceb0dd065 Added params option for mergeConfig method 2015-05-05 21:23:54 +02:00
Andy Miller
b2a78d587c added check to see if on same page 2015-05-05 12:48:15 -06:00
Gert
16c3a3690b ignore .DS_Store in media lookup 2015-05-05 20:33:47 +02:00
Gert
7f8e8f67a5 check if medium was created before setting size 2015-05-05 19:49:14 +02:00
Gert
9792c9a84e re-add size property to media 2015-05-04 17:42:53 +02:00
Gert
13e9e6f5e1 modular page cannot be added to root 2015-05-04 17:12:24 +02:00
Gert
e62133233c remove permission management from user settings 2015-05-04 15:59:06 +02:00
Gert
3ec855e28f fix typo in type 2015-05-04 15:58:39 +02:00
Gert
4b56a05f57 media blueprints 2015-05-04 15:44:25 +02:00
Gert
845da953e1 timestamp blueprints under caching header 2015-05-04 15:39:33 +02:00
Gert
565a76c317 error handler blueprints 2015-05-04 15:37:07 +02:00
Gert
e01a116173 asset timestamps blueprint 2015-05-04 15:36:17 +02:00
Gert
4e07c294c5 gzip compression blueprint 2015-05-04 15:29:46 +02:00
Gert
58bb5a6993 publish_dates blueprint 2015-05-04 15:23:08 +02:00
Gert
2c51dd5fe1 default ordering fix 2015-05-04 15:18:55 +02:00
Gert
ce8513d3ff timezon and param_sep blueprints 2015-05-04 15:18:31 +02:00
Gert
08c4fd02d2 fix markdown extra blueprint 2015-05-04 15:13:29 +02:00
Gert
2e680cd35a email validate 2015-05-04 14:56:34 +02:00
Andy Miller
f12ef84a98 Added a function to parseLinks - used by YouTube plugin as testbed 2015-05-02 14:21:13 -06:00
Andy Miller
0563b2b6e5 fixed missing method name 2015-05-02 11:31:10 -06:00
Andy Miller
c68c39df27 fix for .. page references 2015-05-01 06:23:49 -06:00
Andy Miller
41c00d7fbe fix for absolute url's below this page 2015-05-01 06:23:31 -06:00
Andy Miller
750dfb60dc Fix to properly normalize the font rewrite path 2015-04-30 22:51:39 -06:00
Andy Miller
4305bbabd5 fix for theme name same as base_url and asset pipeline 2015-04-30 18:05:30 -06:00
Andy Miller
ecc12be531 restored gpm install functions 2015-04-30 16:54:23 -06:00
Andy Miller
b65280f3c9 added other close bits back for gzip close connection 2015-04-28 17:34:59 -06:00
Andy Miller
e077b3d04c Merge branch 'feature/gert_gzip' into develop 2015-04-28 17:21:43 -06:00
Andy Miller
91a963f580 tweaks 2015-04-28 17:21:30 -06:00
Gert
8404ba7a09 gzip 2015-04-29 01:07:24 +02:00
Gertt
86b907c86c Merge pull request #186 from getgrav/feature/config-fixes
Feature/config fixes
2015-04-28 23:58:44 +02:00
Gertt
ca899072d4 Merge pull request #185 from getgrav/feature/common_backup
Feature/common backup
2015-04-28 23:49:51 +02:00
Gert
bbfc63e943 ignore all .git folders 2015-04-28 23:35:58 +02:00
Andy Miller
9bce9ce026 removed unused 'use' statements 2015-04-28 11:31:28 -06:00
Andy Miller
3a25f028df Commented these out for now 2015-04-28 11:30:03 -06:00
Andy Miller
491c6d6a1f fixed missing classes 2015-04-28 11:28:08 -06:00
Andy Miller
64bb6ea2ad remove another unused var 2015-04-28 11:26:30 -06:00
Andy Miller
084e59dc90 remove unused var 2015-04-28 11:26:18 -06:00
Andy Miller
f7ea2e95e4 strict comparison 2015-04-28 11:24:46 -06:00
Andy Miller
7acdf231a4 removed php config overrides 2015-04-28 11:21:47 -06:00
Andy Miller
5d38e0fa14 add a menu sample in site.yaml 2015-04-27 21:41:52 -06:00
Gert
e364616730 add server directives to block access to backup folder 2015-04-27 21:53:08 +02:00
Gert
5ccefee288 backup to backup folder 2015-04-27 21:45:42 +02:00
Gert
93f4ad6e5a upload limit setting 2015-04-27 19:29:32 +02:00
Gert
c481acbb71 implement ignore for backups 2015-04-27 16:24:17 +02:00
Gert
bbdb0189f1 use site name for backup archive name 2015-04-27 16:13:48 +02:00
Gert
e1d655a3ac move backup code into Grav\Common 2015-04-27 12:40:30 +02:00
Andy Miller
d849f8a03e Added page level summary header overrides 2015-04-26 18:45:47 -06:00
Andy Miller
67c3d64275 optimize composer 2015-04-26 18:13:53 -06:00
Andy Miller
1546783371 upgrade already has vendor libs 2015-04-26 18:10:56 -06:00
Andy Miller
21cc9f86f3 added new composer update command 2015-04-26 18:00:34 -06:00
Andy Miller
2936d26f8d added --prefer-dist to these commands 2015-04-26 17:37:34 -06:00
Andy Miller
ae453dbc71 restore deprecated for whoops - causing composer issue 2015-04-26 17:21:38 -06:00
Andy Miller
d25699397f add composer update to gpm selfupgrade 2015-04-26 17:21:12 -06:00
Andy Miller
d461fac089 optimize install of vendor libs 2015-04-26 17:20:52 -06:00
Andy Miller
4bda629a6a handle condition of errors resulting in blank page. 2015-04-26 16:58:45 -06:00
Andy Miller
8a2817a305 Added check for curl in GPM 2015-04-26 16:39:58 -06:00
Andy Miller
8887f8862d Improved and cleaned up a couple of folder methods 2015-04-26 15:30:10 -06:00
Andy Miller
4883a408c7 updated composer.phar 2015-04-25 18:41:45 -06:00
Andy Miller
7b75171f79 Merge branch 'release/0.9.26' 2015-04-24 17:27:51 -06:00
Andy Miller
dc42288fa0 Merge branch 'release/0.9.26' into develop 2015-04-24 17:27:51 -06:00
Andy Miller
f576f914ae version update 2015-04-24 17:27:31 -06:00
Andy Miller
88faa5040c fix infinite loop bug in path() 2015-04-24 17:19:28 -06:00
Andy Miller
1e5365ba51 fix for home path not dispatching correctly 2015-04-24 17:13:14 -06:00
Andy Miller
eb93dacba0 Merge branch 'release/0.9.25' 2015-04-24 14:07:01 -06:00
Andy Miller
1e79cbc945 Merge branch 'release/0.9.25' into develop 2015-04-24 14:07:01 -06:00
Andy Miller
d350bd31cb version update 2015-04-24 14:06:44 -06:00
Andy Miller
89b9b4e9b7 Some optimizations and fixes 2015-04-22 14:12:45 -06:00
Andy Miller
9549e5aa4c moved to stable version of toolbox (now in packagist) 2015-04-22 12:31:16 -06:00
Andy Miller
1517d0e40b fix for relative images at root 2015-04-21 19:21:49 -06:00
Andy Miller
f13593aac7 Added E-Tag, Last-Modified, Cache-Control and Page-based Expires 2015-04-21 15:57:17 -06:00
Andy Miller
b8023b2444 Reset default expires time to 0 seconds 2015-04-21 15:39:55 -06:00
Andy Miller
bb49098a05 Merge branch 'feature/modified_timestamp_tweak' into develop 2015-04-21 14:11:32 -06:00
Andy Miller
c368fbcda9 tweaks 2015-04-21 14:08:00 -06:00
Andy Miller
1c1bf86e9a more optimizations.. using regexiterator now 2015-04-21 13:55:53 -06:00
Andy Miller
5f1b190ba9 rework to only check .md files 2015-04-21 13:25:42 -06:00
Andy Miller
0860f53d68 doc tag fix 2015-04-21 12:29:45 -06:00
Andy Miller
1ffd1cb6e7 Fixes for absolute images 2015-04-20 21:40:06 -06:00
Andy Miller
030d230312 Support page defaults merged with system config - #174 2015-04-20 21:38:26 -06:00
Andy Miller
974f9d52a4 Refactored link handling to better absolute handles pages and url elements. Fixes issue #173 2015-04-20 19:05:53 -06:00
Andy Miller
1a238ea1b1 fix for spaces in relative dir 2015-04-20 13:33:54 -06:00
Gert
a049441048 Merge branch 'develop' of github.com:getgrav/grav into develop
* 'develop' of github.com:getgrav/grav:
  Should fix: spaces in webroot for `bin/grav install` #164
2015-04-20 20:54:07 +02:00
Gert
20e771f121 add metod to get all pages 2015-04-20 20:53:39 +02:00
Gert
cff4e225e6 fix bug in collection filtering 2015-04-20 20:53:26 +02:00
Andy Miller
2c69f539ae Merge branch 'develop' of https://github.com/getgrav/grav into develop
# By Gert (3) and Kyle Shockey (1)
# Via Gert (1) and others
* 'develop' of https://github.com/getgrav/grav:
  Update README.md
  Merge branch develop into feature/password_improvement
  protect against timing attacks
  fix flaws in authentication
2015-04-20 12:41:37 -06:00
Andy Miller
548081471c Should fix: spaces in webroot for bin/grav install #164 2015-04-20 12:41:26 -06:00
Ryan Matthew Pierson
3688cfa397 Merge pull request #171 from kyleshockey/patch-1
README.md grammar changes
2015-04-17 14:02:20 -05:00
Kyle Shockey
20b2856dee Update README.md
grammar changes
2015-04-17 10:59:03 -07:00
Gert
7e94e46459 use gravtrait in plugins 2015-04-16 17:54:48 +02:00
Gert
f3b4efb661 Make plugins and themes load the merged config from all files 2015-04-16 17:43:03 +02:00
Gert
7947ba2442 fix config reload to actually reload from files 2015-04-16 17:42:29 +02:00
Gert
ef63e993d2 Merge branch 'feature/password_improvements' into develop
* feature/password_improvements:
  Merge branch develop into feature/password_improvement
  protect against timing attacks
  fix flaws in authentication
2015-04-16 12:49:19 +02:00
Gert
29ae5b7aae Merge branch develop into feature/password_improvement 2015-04-16 12:48:37 +02:00
Andy Miller
6baf7e0b35 Merge branch 'release/0.9.24' 2015-04-15 15:55:48 -06:00
Andy Miller
2e7ece17d7 Merge branch 'release/0.9.24' into develop 2015-04-15 15:55:48 -06:00
Andy Miller
2a56f21d13 version update 2015-04-15 15:55:28 -06:00
Andy Miller
d253c3c6c5 updated changelog 2015-04-15 15:54:36 -06:00
Andy Miller
289a838ba1 disable timestamps by default 2015-04-15 11:14:04 -06:00
Andy Miller
f6f3e96106 Merge branch 'develop' of https://github.com/getgrav/grav into develop
* 'develop' of https://github.com/getgrav/grav:
  hide notice about ob_flush()
2015-04-15 09:34:37 -06:00
Andy Miller
7d22305678 hide notice about ob_flush() 2015-04-15 06:44:34 -06:00
Andy Miller
7571d1d562 Merge branch 'feature/media_querystring' into develop 2015-04-14 22:51:44 -06:00
Andy Miller
b4c06f537d added page expires 2015-04-14 22:50:45 -06:00
Andy Miller
830c723bae Revert "added page expires"
This reverts commit f02f3d507dc0959866e6bdef67cc1cf29c0f173e.
2015-04-14 22:50:26 -06:00
Andy Miller
b83ab07374 added page expires 2015-04-14 21:59:31 -06:00
Gert
8200cb9336 querystring and cache timestamp support in media 2015-04-15 03:10:08 +02:00
Andy Miller
b1a38306af Added asset timestamps 2015-04-14 15:46:11 -06:00
Andy Miller
6ec0f4782f Merge pull request #169 from TomasVotruba/patch-1
cs
2015-04-13 11:44:06 -06:00
Tomáš Votruba
d25f9bcf1b cs 2015-04-13 19:41:52 +02:00
Gert
cd3fd5a7b7 protect against timing attacks 2015-04-13 14:44:56 +02:00
Gert
1ab1378259 fix flaws in authentication 2015-04-13 13:10:56 +02:00
Andy Miller
21f87ade2d Merge branch 'feature/improved_download_support' into develop 2015-04-11 18:27:09 -06:00
Andy Miller
8f54e5739f fix for any file with parameters 2015-04-10 14:28:37 -06:00
Andy Miller
21a6594573 Added a onBeforeDownload() event to provide logging/access check, etc. 2015-04-10 14:14:51 -06:00
Andy Miller
01ce80fb1a fixed direct operations on media objects 2015-04-10 13:30:36 -06:00
Andy Miller
174672c411 Support chunked downloads and non-media filetypes 2015-04-10 13:22:10 -06:00
Andy Miller
4785103081 Added download() and getMimeType() static methods 2015-04-10 13:21:53 -06:00
Andy Miller
7030422b11 New baseman option in Uri class 2015-04-10 13:21:23 -06:00
Andy Miller
2a06dc9bea customizable page types 2015-04-10 13:21:03 -06:00
Andy Miller
66927043de Merge branch 'release/0.9.23' 2015-04-09 13:01:01 -06:00
Andy Miller
7d16bafd52 Merge branch 'release/0.9.23' into develop 2015-04-09 13:01:01 -06:00
Andy Miller
ee340e2d6f version update 2015-04-09 13:00:50 -06:00
Andy Miller
66d9fd1a5e Merge branch 'feature/gpm_array_object_fixes' into develop 2015-04-09 12:51:55 -06:00
Andy Miller
7c28de6ae5 fixes for download 2015-04-09 12:41:03 -06:00
Andy Miller
c95f602ea2 fixes for changelog 2015-04-09 12:37:46 -06:00
Gert
6361280d99 remove supressed warnings 2015-04-09 20:13:10 +02:00
Andy Miller
b3a9d7cd41 fixes for broken gpm selfupgrade 2015-04-08 22:29:50 -06:00
Gert
071989c554 Merge branch 'develop' of github.com:getgrav/grav into develop
* 'develop' of github.com:getgrav/grav:
  Fix Folder::getRelativePath() so that it works with backslashes
  version update
2015-04-08 22:56:12 +02:00
Gert
f956d7113f use FilesystemIterator to solve windows path issue more elegantly 2015-04-08 22:55:27 +02:00
Matias Griese
f0472fdd76 Fix Folder::getRelativePath() so that it works with backslashes 2015-04-08 21:37:19 +03:00
Andy Miller
01899676a4 Merge branch 'release/0.9.22' into develop 2015-04-08 12:22:35 -06:00
Andy Miller
b13d572ca8 Merge branch 'release/0.9.22' 2015-04-08 12:22:34 -06:00
Andy Miller
a588e08405 version update 2015-04-08 12:22:23 -06:00
Andy Miller
6c93483220 updated changelog 2015-04-08 11:53:00 -06:00
Gert
733c13102b remove double ; 2015-04-08 11:43:40 -06:00
Gert
688d6fe17a normalize grav path 2015-04-08 11:43:28 -06:00
Gert
25ff1f230f fix mixed slashes in paths 2015-04-08 11:43:20 -06:00
Andy Miller
094b58130a Fix for issue #167 - latest version check out of order 2015-04-08 11:42:23 -06:00
Andy Miller
d9188e76ed Merge branch 'release/0.9.21' into develop 2015-04-07 13:53:44 -06:00
97 changed files with 5195 additions and 1228 deletions

2
.gitignore vendored
View File

@@ -7,6 +7,8 @@ vendor/
.sass-cache
# Grav Specific
backup/*
!backup/.*
cache/*
!cache/.*
assets/*

View File

@@ -44,7 +44,7 @@ RewriteRule .* index.php [L]
## Begin - Security
# Block all direct access for these folders
RewriteRule ^(cache|bin|logs)/(.*) error [L]
RewriteRule ^(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]
## End - Security

78
.travis.yml Normal file
View File

@@ -0,0 +1,78 @@
language: php
php: 5.6
branches:
only:
- master
- build_test
notifications:
email:
on_success: never
on_failure: always
hipchat:
# hipchat_api@grav
rooms:
- secure: "bqO0wM1B7bJnQw2fuhquSXEqI9gw6WmFytIh9sEWXzbYTzTUP5t0PcKOd3FT2BNMRaDxPJLVl+vG/oqmqDUBkEmOGcG504IQjeNzZqnMz0tXQMIcCc22Las9tFfc4Jf6RVi/qGomFtHGE9Wgii+TAN4zqZaufbNjwd8SyjO0+W8="
template:
- '%{repository}#%{build_number} (%{branch}): Grav Core and Skeletons packages have been uploaded. (<a href="%{build_url}">Details</a>)'
format: html
env:
global:
# Colors!
- TEXTRESET=$(tput sgr0) # reset the foreground colour
- RED=$(tput setaf 1)
- GREEN=$(tput setaf 2)
- YELLOW=$(tput setaf 3)
- BLUE=$(tput setaf 4)
- BOLD=$(tput bold)
# User
- GH_USER="getgrav"
# Paths
- RT_DEVTOOLS=$HOME/devtools
- GOPATH="$HOME/go"
- PATH="$GOPATH/bin:$PATH"
# GH_TOKEN [API Key]
- secure: "jS+c+g2v33vypG4VtqiSDW2qQ4dGJZlrUKBRCztoy1yrOrYRPvc5Vzi/AS3fDmZ4yizukEwmUNNzyZQcgFvLPpmCCml46Dovp8R9OXhbNe8OnULmaSn2Zkr71oblMYu6ZP+RpYvNq0BIdSB3u2TiFriHMiTIkX9UwZNaUCOX1ig="
# BB_TOKEN value => "user:pass@"
- secure: "einUtSEkUWy2IrqLXyVjwUU+mwaaoiOXRRVdLBpA3Zye6bZx8cm5h/5AplkPWhM/NmCJoW/MwNZHHkFhlr3mDRov5iOxVmTTYfnXB+I5lxYTSgduOLLErS7mU8hfADpVDU8bHNU44fNGD3UEiG1PD4qQBX4DMlqIFmR20mjs81k="
# GH_API_USER [for curl]
- secure: "Xbk/V9aIys0NxccJGR3Zrm2GRxDnA0RuazBs1puIboTYDhbi0Z7JTL+mOx3xp5Kfoniad/xAuijQESTM9MMrKqq/qCzhAMaC1+vcL4pCHZH4NSG6DBxB9BPkKVFq+1llu5FTEf8bkxHzwGR0l1ARW6TVRcgTHr5B58bCEIwEOrI="
# Latest Release version
- TRAVIS_TAG=$(curl --fail --user ${GH_API_USER} -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4)
before_install:
- composer self-update
- go get github.com/aktau/github-release
- git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null;
- if [ ! -z "$TRAVIS_TAG" ]; then
cd "${RT_DEVTOOLS}";
./build-grav.sh skeletons.txt;
fi
script:
- if [ ! -z "$TRAVIS_TAG" ]; then
FILES="$RT_DEVTOOLS/grav-dist/*.zip";
for file in ${FILES[@]}; do
NAME=${file##*/};
REPO="$(echo ${NAME} | rev | cut -f 2- -d "-" | rev)";
if [[ $REPO == 'grav' || $REPO == 'grav-update' ]]; then
REPO="grav";
fi;
API="$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/${GH_USER}/${REPO}/releases/latest)";
ASSETS="$(echo "${API}" | node gh-assets.js)";
TAG="$(echo "${API}" | grep tag_name | head -n 1 | cut -d '"' -f 4)";
if [ $REPO == "grav" ]; then
TAG=$TRAVIS_TAG;
fi;
if [ ! -z "$ASSETS" ]; then
for asset in ${ASSETS[@]}; do
asset_id=$(echo ${asset} | cut -d ':' -f 1);
asset_name=$(echo ${asset} | cut -d ':' -f 2);
if [ "${NAME}" == "${asset_name}" ]; then
echo -e "\nAsset ${BOLD}${BLUE}${NAME}${TEXTRESET} already exists in ${YELLOW}${REPO}${TEXTRESET}@${BOLD}${YELLOW}${TAG}${TEXTRESET}... deleting id ${BOLD}${RED}${asset_id}${TEXTRESET}...";
curl -X DELETE --fail --user "${GH_API_USER}" "https://api.github.com/repos/${GH_USER}/${REPO}/releases/assets/${asset_id}";
fi;
done;
fi;
echo "Uploading package ${BOLD}${BLUE}${NAME}${TEXTRESET} to ${YELLOW}${REPO}${TEXTRESET}@${YELLOW}${TAG}${TEXTRESET}";
github-release upload --security-token $GH_TOKEN --user ${GH_USER} --repo $REPO --tag $TAG --name "$NAME" --file "$file";
done;
fi

View File

@@ -1,3 +1,230 @@
# v0.9.35
## 08/06/2015
1. [](#new)
* Added `body_classes` field
* Added `visiblity` toggle and help tooltips on new page form
* Added new `Page.unsetRoute()` method to allow admin to regenerate the route
1. [](#improved)
* User save no longer stores username each time
* Page list form field now shows all pages except root
* Removed required option from page title
* Added configuration settings for running Nginx in sub directory
1. [](#bugfix)
* Fixed issue with GPM and cURL throwing `Undefined offset: 1` error
* Fixed deep translation merging
* Fixed broken **metadata** merging with site defaults
* Fixed broken **summary** field
* Fixed broken robots field
* Fixed GPM issue when using cURL, throwing an `Undefined offset: 1` exception
* Removed duplicate hidden page `type` field
# v0.9.34
## 08/04/2015
1. [](#new)
* Added new `cache_all` system setting + media `cache()` method
* Added base languages configuration
* Added property language to page to help plugins identify page language
* New `Utils::arrayFilterRecursive()` method
2. [](#improved)
* Improved Session handling to support site and admin independently
* Allow Twig variables to be modified in other events
* Blueprint updates in preparation for Admin plugin
* Changed `Inflector` from static to object and added multi-language support
* Support for admin override of a page's blueprints
3. [](#bugfix)
* Removed unused `use` in `VideoMedium` that was causing error
* Array fix in `User.authorise()` method
* Fix for typo in `translations_fallback`
* Fixed moving page to the root
# v0.9.33
## 07/21/2015
1. [](#new)
* Added new `onImageMediumSaved()` event (useful for post-image processing)
* Added `Vary: Accept-Encoding` option
2. [](#improved)
* Multilang-safe delimeter position
* Refactored Twig classes and added optional umask setting
* Removed `pageinit()` timing
* `Page->routable()` now takes `published()` state into account
* Improved how page extension is set
* Support `Language->translate()` method taking string and array
3. [](#bugfix)
* Fixed `backup` command to include empty folders
# v0.9.32
## 07/14/2015
1. [](#new)
* Detect users preferred language via `http_accept_language` setting
* Added new `translateArray()` language method
2. [](#improved)
* Support `en` translations by default for plugins & themes
* Improved default generator tag
* Minor language tweaks and fixes
3. [](#bugfix)
* Fix for session active language and homepage redirects
* Ignore root-level page rather than throwing error
# v0.9.31
## 07/09/2015
1. [](#new)
* Added xml, json, css and js to valid media file types
2. [](#improved)
* Better handling of unsupported media type downloads
* Improved `bin/grav backup` command to mimic admin plugin location/name
3. [](#bugfix)
* Critical fix for broken language translations
* Fix for Twig markdown filter error
* Safety check for download extension
# v0.9.30
## 07/08/2015
1. [](#new)
* BIG NEWS! Extensive Multi-Language support is all new in 0.9.30!
* Translation support via Twig filter/function and PHP method
* Page specific default route
* Page specific route aliases
* Canonical URL route support
* Added built-in session support
* New `Page.rawRoute()` to get a consistent folder-based route to a page
* Added option to always redirect to default page on alias URL
* Added language safe redirect function for use in core and plugins
2. [](#improved)
* Improved `Page.active()` and `Page.activeChild()` methods to support route aliases
* Various spelling corrections in `.php` comments, `.md` and `.yaml` files
* `Utils::startsWith()` and `Utils::endsWith()` now support needle arrays
* Added a new timer around `pageInitialized` event
* Updated jQuery library to v2.1.4
3. [](#bugfix)
* In-page CSS and JS files are now handled properly
* Fix for `enable_media_timestamp` not working properly
# v0.9.29
## 06/22/2015
1. [](#new)
* New and improved Regex-powered redirect and route alias logic
* Added new `onBuildPagesInitialized` event for memory critical or time-consuming plugins
* Added a `setSummary()` method for pages
2. [](#improved)
* Improved `MergeConfig()` logic for more control
* Travis skeleton build trigger implemented
* Set composer.json versions to stable versions where possible
* Disabled `last_modified` and `etag` page headers by default (causing too much page caching)
3. [](#bugfix)
* Preload classes during `bin/gpm selfupgrade` to avoid issues with updated classes
* Fix for directory relative _down_ links
# v0.9.28
## 06/16/2015
1. [](#new)
* Added method to set raw markdown on a page
* Added ability to enabled system and page level `etag` and `last_modified` headers
2. [](#improved)
* Improved image path processing
* Improved query string handling
* Optimization to image handling supporting URL encoded filenames
* Use global `composer` when available rather than Grv provided one
* Use `PHP_BINARY` contant rather than `php` executable
* Updated Doctrine Cache library
* Updated Symfony libraries
* Moved `convertUrl()` method to Uri object
3. [](#bugfix)
* Fix incorrect slug causing problems with CLI `uninstall`
* Fix Twig runtime error with assets pipeline in sufolder installations
* Fix for `+` in image filenames
* Fix for dot files causing issues with page processing
* Fix for Uri path detection on Windows platform
* Fix for alternative media resolutions
* Fix for modularTypes key properties
# v0.9.27
## 05/09/2015
1. [](#new)
* Added new composer CLI command
* Added page-level summary header overrides
* Added `size` back for Media objects
* Refactored Backup command in preparation for admin plugin
* Added a new `parseLinks` method to Plugins class
* Added `starts_with` and `ends_with` Twig filters
2. [](#improved)
* Optimized install of vendor libraries for speed improvement
* Improved configuration handling in preparation for admin plugin
* Cache optimization: Don't cache Twig templates when you pass dynamic params
* Moved `Utils::rcopy` to `Folder::rcopy`
* Improved `Folder::doDelete`
* Added check for required Curl in GPM
* Updated included composer.phar to latest version
* Various blueprint fixes for admin plugin
* Various PSR and code cleanup tasks
3. [](#bugfix)
* Fix issue with Gzip not working with `onShutDown()` event
* Fix for URLs with trailing slashes
* Handle condition where certain errors resulted in blank page
* Fix for issue with theme name equal to base_url and asset pipeline
* Fix to properly normalize font rewrite path
* Fix for absolute URLs below the current page
* Fix for `..` page references
# v0.9.26
## 04/24/2015
3. [](#bugfix)
* Fixed issue with homepage routes failing with 'dirname' error
# v0.9.25
## 04/24/2015
1. [](#new)
* Added support for E-Tag, Last-Modified, Cache-Control and Page-based expires headers
2. [](#improved)
* Refactored media image handling to make it more flexible and support absolute paths
* Refactored page modification check process to make it faster
* User account improvements in preparation for admin plugin
* Protect against timing attacks
* Reset default system expires time to 0 seconds (can override if you need to)
3. [](#bugfix)
* Fix issues with spaces in webroot when using `bin/grav install`
* Fix for spaces in relative directory
* Bug fix in collection filtering
# v0.9.24
## 04/15/2015
1. [](#new)
* Added support for chunked downloads of Assets
* Added new `onBeforeDownload()` event
* Added new `download()` and `getMimeType()` methods to Utils class
* Added configuration option for supported page types
* Added assets and media timestamp options (off by default)
* Added page expires configuration option
2. [](#bugfix)
* Fixed issue with Nginx/Gzip and `ob_flush()` throwing error
* Fixed assets actions on 'direct media' URLs
* Fix for 'direct assets` with any parameters
# v0.9.23
## 04/09/2015
1. [](#bugfix)
* Fix for broken GPM `selfupgrade` (Grav 0.9.21 and 0.9.22 will need to manually upgrade to this version)
# v0.9.22
## 04/08/2015
1. [](#bugfix)
* Fix to normalize GRAV_ROOT path for Windows
* Fix to normalize Media image paths for Windows
* Fix for GPM `selfupgrade` when you are on latest version
# v0.9.21
## 04/07/2015
@@ -89,7 +316,7 @@
* Improved the markdown Lightbox functionality to better mimic Twig version
* Fullsize Lightbox can now have filters applied
* Added a new `mergeConfig()` method to Plugin class to merge system + page header configuration
* Added a new `disable()` method to Plugin class to programatically disable a plugin
* Added a new `disable()` method to Plugin class to programmatically disable a plugin
* Updated Parsedown and Parsedown Extra to address bugs
* Various PSR fixes
3. [](#bugfix)
@@ -142,7 +369,7 @@
* Added `publish_date` in page headers to automatically publish page
* Added `unpublish_date` in page headers to automatically unpublish page
* Added `dateRange()` capability for collections
* Added ability to dynamically control Cache lifetime programatically
* Added ability to dynamically control Cache lifetime programmatically
* Added ability to sort by anything in the page header. E.g. `sort: header.taxonomy.year`
* Added various helper methods to collections: `copy, nonVisible, modular, nonModular, published, nonPublished, nonRoutable`
2. [](#improved)
@@ -317,7 +544,7 @@
* Broke cache types out into multiple directories in the cache folder
* Removed vendor libs from github repository
* Various PSR cleanup of code
* Various Blueprint updates to support upcoming Admin plugin
* Various Blueprint updates to support upcoming admin plugin
* Added ability to filter page children for normal/modular/all
* Added `sort_by_key` twig filter
* Added `visible()` and `routable()` filters to page collections
@@ -390,7 +617,7 @@
* Addition of Dependency Injection Container
* Refactored plugins to use Symfony Event Dispatcher
* New Asset Manager to provide unified management of JavaScript and CSS
* Asset Pipelining to provide unification, minify, and optimazation of JavaScript and CSS
* Asset Pipelining to provide unification, minify, and optimization of JavaScript and CSS
* Grav Media support directly in Markdown syntax
* Additional Grav Generator meta tag in default themes
* Added support for PHP Stream Wrapper for resource location

View File

@@ -2,9 +2,9 @@
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad/mini.png)](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/getgrav/grav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principals to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself.
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principles to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself.
The underlying architecture of Grav has been designed to use well-established and _best-in-class_ technologies, where applicable, to ensure that Grav is simple to use and easy to extend. Some of these key technologies include:
The underlying architecture of Grav is designed to use well-established and _best-in-class_ technologies, to ensure that Grav is simple to use and easy to extend. Some of these key technologies include:
* [Twig Templating](http://twig.sensiolabs.org/): for powerful control of the user interface
* [Markdown](http://en.wikipedia.org/wiki/Markdown): for easy content creation

0
backup/.gitkeep Normal file
View File

Binary file not shown.

13
bin/gpm
View File

@@ -6,10 +6,17 @@ 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('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install');
echo "\n\n";
}
@@ -26,6 +33,10 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}
if (!function_exists('curl_version')) {
exit('FATAL: GPM requires PHP Curl module to be installed');
}
$grav = Grav::instance(array('loader' => $autoload));
$grav['config']->init();
$grav['streams'];

View File

@@ -6,10 +6,17 @@ 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('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install');
echo "\n\n";
}
@@ -28,6 +35,7 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
$app = new Application('Grav CLI Application', '0.1.0');
$app->addCommands(array(
new Grav\Console\Cli\InstallCommand(),
new Grav\Console\Cli\ComposerCommand(),
new Grav\Console\Cli\SandboxCommand(),
new Grav\Console\Cli\CleanCommand(),
new Grav\Console\Cli\ClearCacheCommand(),

View File

@@ -9,27 +9,20 @@
"php": ">=5.4.0",
"twig/twig": "~1.16",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~2.6",
"symfony/console": "~2.6",
"symfony/event-dispatcher": "~2.6",
"doctrine/cache": "~1.3",
"symfony/yaml": "2.7.3",
"symfony/console": "2.7.3",
"symfony/event-dispatcher": "2.7.3",
"doctrine/cache": "~1.4",
"maximebf/debugbar": "dev-master",
"filp/whoops": "1.2.*@dev",
"monolog/monolog": "~1.0",
"gregwar/image": "~2.0",
"gregwar/image": "~2.0",
"ircmaxell/password-compat": "1.0.*",
"mrclay/minify": "dev-master",
"donatj/phpuseragentparser": "dev-master",
"mrclay/minify": "~2.2",
"donatj/phpuseragentparser": "~0.3",
"pimple/pimple": "~3.0",
"rockettheme/toolbox": "dev-develop"
"rockettheme/toolbox": "1.0.*"
},
"repositories": [
{
"type": "vcs",
"no-api": true,
"url": "https://github.com/rockettheme/toolbox"
}
],
"autoload": {
"psr-4": {
"Grav\\": "system/src/Grav"

View File

@@ -25,7 +25,22 @@ http {
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
}
@@ -44,6 +59,10 @@ http {
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;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -13,58 +13,101 @@ form:
label: Site Title
size: large
placeholder: "Site wide title"
help: Default title for your site
help: "Default title for your site, often used in themes"
author.name:
type: text
size: large
label: Default Author
help: "A default author name, often used in themes or page content"
author.email:
type: text
size: large
label: Default Email
help: "A default email to reference in themes or pages"
validate:
type: email
taxonomies:
type: text
type: selectize
size: large
label: Taxonomy Types
classes: fancy
help: "Taxonomy types must be defined here if you wish to use them in pages"
validate:
type: commalist
metadata:
type: array
label: Metadata
placeholder_key: Name
placeholder_value: Content
blog:
summary:
type: section
title: Blog
title: Page Summary
fields:
blog.route:
type: text
size: large
label: Blog URL
summary.enabled:
type: toggle
label: Enabled
highlight: 1
help: "Enable page summary (the summary returns the same as the page content)"
options:
1: Yes
0: No
validate:
type: bool
summary.size:
type: text
size: x-small
label: Summary Size
help: "The amount of characters of a page to use as a content summary"
validate:
type: int
min: 0
max: 65536
routes:
summary.format:
type: toggle
label: Format
classes: fancy
help: "short = use the first occurrence of delimiter or size; long = summary delimiter will be ignored"
highlight: short
options:
'short': 'Short'
'long': 'Long'
summary.delimiter:
type: text
size: x-small
label: Delimiter
help: "The summary delimiter (default '===')"
metadata:
type: section
title: Routes
title: Metadata
fields:
metadata:
type: array
label: Metadata
help: "Default metadata values that will be displayed on every page unless overridden by the page"
placeholder_key: Name
placeholder_value: Content
routes:
type: section
title: Redirects & Routes
fields:
redirects:
type: array
label: Custom Redirects
help: "routes to redirect to other pages. Standard Regex replacement is valid"
placeholder_key: /your/alias
placeholder_value: /your/redirect
routes:
type: array
label: Custom
label: Custom Routes
help: "routes to alias to other pages. Standard Regex replacement is valid"
placeholder_key: /your/alias
placeholder_value: /your/route

View File

@@ -14,7 +14,7 @@ form:
type: pages
size: medium
classes: fancy
label: Home Page
label: Home page
show_all: false
show_modular: false
show_root: false
@@ -23,61 +23,65 @@ form:
pages.theme:
type: themeselect
classes: fancy
selectize: true
size: medium
label: Default Theme
help: "Set the theme (defaults to 'default')"
pages.markdown_extra:
type: toggle
label: Markdown Extra
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
label: Default theme
help: "Set the default theme for Grav to use (default is Antimatter)"
pages.process:
type: checkboxes
label: Process
help: "Control how pages are processed. Can be set per-page rather than globally"
default: [markdown: true, twig: true]
options:
markdown: Markdown
twig: Twig
use: keys
timezone:
type: select
label: Timezone
size: medium
classes: fancy
help: "Override the default timezone the server"
@data-options: '\Grav\Common\Utils::timezones'
default: ''
options:
'': 'Default (Server Timezone)'
pages.dateformat.short:
type: select
size: medium
classes: fancy
label: Short Date Format
help: "Set the short date format"
default: 'jS M Y'
label: Short date format
help: "Set the short date format that can be used by themes"
default: "jS M Y"
options:
'F jS \\a\\t g:ia': "January 1st at 11:59pm"
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
'd-m-y G:i': "01-01-14 23:59"
'jS M Y': "10th Feb 2014"
"F jS \\a\\t g:ia": "January 1st at 11:59pm"
"l jS of F g:i A": "Monday 1st of January at 11:59 PM"
"D, m M Y G:i:s": "Mon, 01 Jan 2014 23:59:00"
"d-m-y G:i": "01-01-14 23:59"
"jS M Y": "10th Feb 2014"
pages.dateformat.long:
type: select
size: medium
classes: fancy
label: Long Date Format
help: "Set the long date format"
label: Long date format
help: "Set the long date format that can be used by themes"
options:
'F jS \a\t g:ia': "January 1st at 11:59pm"
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
'd-m-y G:i': "01-01-14 23:59"
'jS M Y': "10th Feb 2014"
"F jS \\a\\t g:ia": "January 1st at 11:59pm"
"l jS of F g:i A": "Monday 1st of January at 11:59 PM"
"D, m M Y G:i:s": "Mon, 01 Jan 2014 23:59:00"
"d-m-y G:i": "01-01-14 23:59"
"jS M Y": "10th Feb 2014"
pages.order.by:
type: select
size: medium
classes: fancy
label: Default Ordering
label: Default ordering
help: "Pages in a list will render using this order unless it is overridden"
options:
default: Default - based on folder name
folder: Folder - based on prefix-less folder name
@@ -86,9 +90,10 @@ form:
pages.order.dir:
type: toggle
label: Default Order Direction
label: Default order direction
highlight: asc
default: desc
help: "The direction of pages in a list"
options:
asc: Ascending
desc: Descending
@@ -96,23 +101,16 @@ form:
pages.list.count:
type: text
size: x-small
label: Default Item Count
help: "Default max pages count"
label: Default page count
help: "Default maximum pages count in a list"
validate:
type: number
min: 1
events:
type: section
title: Events
underline: true
fields:
pages.events.page:
pages.publish_dates:
type: toggle
label: Page events
label: Date-based publishing
help: "Automatically (un)publish posts based on their date"
highlight: 1
options:
1: Yes
@@ -120,9 +118,47 @@ form:
validate:
type: bool
pages.events.twig:
pages.events:
type: checkboxes
label: Events
help: "Enable or Disable specific events. Disabling these can break plugins"
default: [page: true, twig: true]
options:
page: Page Events
twig: Twig Events
use: keys
pages.redirect_default_route:
type: toggle
label: Twig events
label: Redirect default route
help: "Automatically redirect to a page's default route"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
languages:
type: section
title: Languages
underline: true
fields:
languages.supported:
type: selectize
size: large
label: Supported
help: "Comma separated list of 2 letter language codes (for example 'en,fr,de')"
classes: fancy
validate:
type: commalist
languages.translations:
type: toggle
label: Translations enabled
help: "Support translations in Grav, plugins and extensions"
highlight: 1
options:
1: Yes
@@ -130,6 +166,142 @@ form:
validate:
type: bool
languages.translations_fallback:
type: toggle
label: Translations fallback
help: "Fallback through supported translations if active language doesn't exist"
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
languages.session_store_active:
type: toggle
label: Active language in session
help: "Support translations in Grav, plugins and extensions"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
languages.home_redirect.include_lang:
type: toggle
label: Home redirect include language
help: "Include language in home redirect (/en)"
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
languages.home_redirect.include_route:
type: toggle
label: Home redirect include route
help: "Include route in home redirect (/blog)"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
http_headers:
type: section
title: HTTP Headers
underline: true
fields:
pages.expires:
type: text
size: small
label: Expires
help: "Sets the expires header. The value is in seconds."
validate:
type: number
min: 1
pages.last_modified:
type: toggle
label: Last modified
help: "Sets the last modified header that can help optimize proxy and browser caching"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.etag:
type: toggle
label: ETag
help: "Sets the etag header to help identify when a page has been modified"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.vary_accept_encoding:
type: toggle
label: Vary accept encoding
help: "Sets the `Vary: Accept Encoding` header to help with proxy and CDN caching"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
markdown:
type: section
title: Markdown
underline: true
fields:
pages.markdown.extra:
type: toggle
label: Markdown extra
help: "Enable default support for Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.markdown.auto_line_breaks:
type: toggle
label: Auto line breaks
help: "Enable support for automatic line breaks in markdown"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.markdown.auto_url_links:
type: toggle
label: Auto URL links
help: "Enable automatic conversion of URLs into HTML hyperlinks"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.markdown.escape_markup:
type: toggle
label: Escape markup
help: "Escape markup tags into HTML entities"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
caching:
type: section
title: Caching
@@ -139,6 +311,7 @@ form:
cache.enabled:
type: toggle
label: Caching
help: "Global ON/OFF switch to enable/disable Grav caching"
highlight: 1
options:
1: Yes
@@ -150,7 +323,8 @@ form:
type: select
size: small
classes: fancy
label: Cache Check Method
label: Cache check method
help: "Select the method that Grav uses to check if page files have been modified."
options:
file: File
folder: Folder
@@ -161,6 +335,7 @@ form:
size: small
classes: fancy
label: Cache driver
help: "Choose which cache driver Grav should use. 'Auto Detect' attempts to find the best for you"
options:
auto: Auto detect
file: File
@@ -172,9 +347,30 @@ form:
cache.prefix:
type: text
size: x-small
label: Cache Prefix
label: Cache prefix
help: "An identifier for part of the Grav key. Don't change unless you know what your doing."
placeholder: "Derived from base URL (override by entering random string)"
cache.lifetime:
type: text
size: small
label: Lifetime
help: "Sets the cache lifetime in seconds. 0 = infinite"
validate:
type: number
cache.gzip:
type: toggle
label: Gzip compression
help: "Enable GZip compression of the Grav page for increased performance."
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
twig:
type: section
title: Twig Templating
@@ -184,6 +380,7 @@ form:
twig.cache:
type: toggle
label: Twig caching
help: "Control the Twig caching mechanism. Leave this enabled for best performance."
highlight: 1
options:
1: Yes
@@ -194,7 +391,8 @@ form:
twig.debug:
type: toggle
label: Twig debug
highlight: 1
help: "Allows the option of not loading the Twig Debugger extension"
highlight: 0
options:
1: Yes
0: No
@@ -204,6 +402,7 @@ form:
twig.auto_reload:
type: toggle
label: Detect changes
help: "Twig will automatically recompile the Twig cache if it detects any changes in Twig templates"
highlight: 1
options:
1: Yes
@@ -214,7 +413,8 @@ form:
twig.autoescape:
type: toggle
label: Autoescape variables
highlight: 1
help: "Autoescapes all variables. This will break your site most likely"
highlight: 0
options:
1: Yes
0: No
@@ -229,8 +429,9 @@ form:
fields:
assets.css_pipeline:
type: toggle
label: CSS Pipeline
highlight: 1
label: CSS pipeline
help: "The CSS pipeline is the unification of multiple CSS resources into one file"
highlight: 0
options:
1: Yes
0: No
@@ -239,7 +440,8 @@ form:
assets.css_minify:
type: toggle
label: CSS Minify
label: CSS minify
help: "Minify the CSS during pipelining"
highlight: 1
options:
1: Yes
@@ -249,8 +451,9 @@ form:
assets.css_minify_windows:
type: toggle
label: CSS Minify Windows Override
highlight: 1
label: CSS minify Windows override
help: "Minify Override for Windows platforms. False by default due to ThreadStackSize"
highlight: 0
options:
1: Yes
0: No
@@ -259,7 +462,8 @@ form:
assets.css_rewrite:
type: toggle
label: CSS Rewrite
label: CSS rewrite
help: "Rewrite any CSS relative URLs during pipelining"
highlight: 1
options:
1: Yes
@@ -269,8 +473,9 @@ form:
assets.js_pipeline:
type: toggle
label: JavaScript Pipeline
highlight: 01
label: JavaScript pipeline
help: "The JS pipeline is the unification of multiple JS resources into one file"
highlight: 0
options:
1: Yes
0: No
@@ -279,7 +484,53 @@ form:
assets.js_minify:
type: toggle
label: JavaScript Minify
label: JavaScript minify
help: "Minify the JS during pipelining"
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
assets.enable_asset_timestamp:
type: toggle
label: Enable timestamps on assets
help: "Enable asset timestamps"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
assets.collections:
type: array
label: Collections
placeholder_key: collection_name
placeholder_value: collection_path
errors:
type: section
title: Error handler
underline: true
fields:
errors.display:
type: toggle
label: Display errors
help: "Display full backtrace-style error page"
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
errors.log:
type: toggle
label: Log errors
help: "Log errors to /logs folder"
highlight: 1
options:
1: Yes
@@ -296,56 +547,18 @@ form:
debugger.enabled:
type: toggle
label: Debugger
highlight: 1
help: "Enable Grav debugger and following settings"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
debugger.mode:
type: select
size: small
classes: fancy
label: Mode
options:
detect: Auto-Detect
development: Development
production: Production
debugger.strict:
debugger.twig:
type: toggle
label: Strict
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
debugger.max_depth:
type: select
size: small
classes: fancy
label: Detail Level
placeholder: "How many nested levels to display for objects or arrays"
options:
1: 1 level
2: 2 levels
3: 3 levels
4: 4 levels
5: 5 levels
6: 6 levels
7: 7 levels
8: 8 levels
9: 9 levels
10: 10 levels
validate:
type: number
debugger.log.enabled:
type: toggle
label: Logging
label: Debug Twig
help: "Enable debugging of Twig templates"
highlight: 1
options:
1: Yes
@@ -355,10 +568,130 @@ form:
debugger.shutdown.close_connection:
type: toggle
label: Shutdown Close Connection
label: Shutdown close connection
help: "Close the connection before calling onShutdown(). false for debugging"
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
media:
type: section
title: Media
underline: true
fields:
images.default_image_quality:
type: text
label: Default image quality
help: "Default image quality to use when resampling or caching images (85%)"
classes: x-small
validate:
type: number
min: 1
max: 100
images.cache_all:
type: toggle
label: Cache all images
help: "Run all images through Grav's cache system even if they have no media manipulations"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
images.debug:
type: toggle
label: Image debug watermark
help: "Show an overlay over images indicating the pixel depth of the image when working with retina for example"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
media.upload_limit:
type: text
label: File upload limit
help: "Set maximum upload size in bytes (0 is unlimited)"
classes: small
validate:
type: number
media.enable_media_timestamp:
type: toggle
label: Enable timestamps on media
help: "Appends a timestamp based on last modified date to each media item"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
session:
type: section
title: Session
underline: true
fields:
session.enabled:
type: toggle
label: Enabled
help: "Enable session support within Grav"
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
session.timeout:
type: text
size: small
label: Timeout
help: "Sets the session timeout in seconds"
validate:
type: number
min: 1
session.name:
type: text
size: small
label: Name
help: "An identifier used to form the name of the session cookie"
advanced:
type: section
title: Advanced
underline: true
fields:
absolute_urls:
type: toggle
label: Absolute URLs
highlight: 0
help: "Absolute or relative URLs for `base_url`"
options:
1: Yes
0: No
validate:
type: bool
param_sep:
type: select
label: Parameter separator
classes: fancy
help: "Separater for passed parameters that can be changed for Apache on Windows"
default: ''
options:
':': ': (default)'
';': '; (for Apache running on Windows)'

View File

@@ -0,0 +1,282 @@
title: Default
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: Content
fields:
header.title:
type: text
style: vertical
label: Title
content:
type: markdown
label: Content
validate:
type: textarea
uploads:
type: uploads
label: Page Media
options:
type: tab
title: Options
fields:
publishing:
type: section
title: Publishing
underline: true
fields:
header.published:
type: toggle
label: Published
help: "By default, a page is published unless you explicitly set published: false or via a publish_date being in the future, or unpublish_date in the past"
highlight: 1
size: medium
options:
'': Global
1: Yes
0: No
validate:
type: bool
header.date:
type: datetime
label: Date
toggleable: true
help: "The date variable allows you to specifically set a date associated with this page."
header.published_date:
type: datetime
label: Published Date
toggleable: true
help: "Can provide a date to automatically trigger publication."
header.unpublished_date:
type: datetime
label: Unublished Date
toggleable: true
help: "can provide a date to automatically trigger un-publication."
meta:
type: section
title: Metadata
underline: true
fields:
header.metadata.description:
type: textarea
toggleable: true
label: Description
default:
validate:
max: 155
header.metadata.keywords:
type: text
toggleable: true
label: Keywords
validate:
max: 120
header.metadata.author:
type: text
toggleable: true
label: Author
validate:
max: 120
header.metadata.robots:
type: text
toggleable: true
label: Robots
validate:
max: 120
taxonomies:
type: section
title: Taxonomies
underline: true
fields:
header.taxonomy:
type: taxonomy
label: Taxonomy
multiple: true
validate:
type: array
advanced:
type: tab
title: Advanced
fields:
columns:
type: columns
fields:
column1:
type: column
fields:
settings:
type: section
title: Settings
underline: true
header.body_classes:
type: text
label: Body Classes
folder:
type: text
label: Folder Name
validate:
type: slug
route:
type: select
label: Parent
classes: fancy
@data-options: '\Grav\Common\Page\Pages::parents'
@data-default: '\Grav\Plugin\admin::route'
options:
'/': '- Root -'
type:
type: templates
classes: fancy
label: Display Template
default: default
@data-options: '\Grav\Common\Page\Pages::types'
column2:
type: column
fields:
order:
type: order
label: Ordering
sitemap:
overrides:
type: section
title: Overrides
underline: true
fields:
header.menu:
type: text
label: Menu
toggleable: true
help: "The string to be used in a menu. If not set, <b>Title</b> will be used."
header.slug:
type: text
label: Slug
toggleable: true
help: "The slug variable allows you to specifically set the page's portion of the URL"
validate:
message: A slug must contain only lowercase alphanumeric characters and dashes
rule: slug
header.process:
type: checkboxes
label: Process
toggleable: true
@config-default: system.pages.process
default:
markdown: true
twig: false
options:
markdown: Markdown
twig: Twig
use: keys
header.child_type:
type: select
toggleable: true
label: Default Child Type
default: default
placeholder: Use Global
@data-options: '\Grav\Common\Page\Pages::types'
header.visible:
type: toggle
label: Visible
help: "Determines if a page is visible in the navigation."
highlight: 1
options:
'': Global
1: Enabled
0: Disabled
validate:
type: bool
header.routable:
type: toggle
label: Routable
help: If this page is reachable by a URL
highlight: 1
default: ''
options:
'': Global
1: Enabled
0: Disabled
validate:
type: bool
header.cache_enable:
type: toggle
label: Caching
highlight: 1
options:
'': Global
1: Enabled
0: Disabled
validate:
type: bool
header.order_by:
type: hidden
header.order_manual:
type: hidden
validate:
type: commalist
blueprint:
type: blueprint

View File

@@ -0,0 +1,47 @@
title: Modular
@extends:
type: default
context: blueprints://pages
form:
fields:
tabs:
type: tabs
active: 1
fields:
content:
fields:
header.content.items:
type: select
label: Items
default: @self.modular
options:
@self.modular: Children
header.content.order.by:
type: select
label: Order By
default: date
options:
folder: Folder
title: Title
date: Date
default: Default
header.content.order.dir:
type: select
label: Order
default: desc
options:
asc: Ascending
desc: Descending
header.process:
type: ignore
content:
type: ignore
uploads:
type: ignore

View File

@@ -52,3 +52,7 @@ form:
default: 1
validate:
type: bool
blueprint:
type: blueprint

View File

@@ -61,7 +61,7 @@ form:
@data-options: '\Grav\Common\Page\Pages::parents'
@data-default: '\Grav\Plugin\admin::route'
options:
'/': '- Root -'
'': '- Select -'
validate:
required: true
@@ -82,3 +82,5 @@ form:
type: order
label: Ordering
blueprint:
type: blueprint

View File

@@ -0,0 +1,17 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
route:
type: select
label: Parent
classes: fancy
@data-options: '\Grav\Common\Page\Pages::parents'
@data-default: '\Grav\Plugin\admin::route'
options:
'/': '- Root -'

View File

@@ -15,18 +15,18 @@ form:
title:
type: text
label: Page Title
help: "The title of the page"
validate:
required: true
folder:
type: text
label: Folder Name
help: "The folder name that will be stored in the filesystem for this page"
validate:
type: slug
required: true
route:
type: select
label: Parent Page
@@ -40,9 +40,28 @@ form:
type:
type: select
help: "The page type that translates into which twig template renders the page"
classes: fancy
label: Display Template
default: default
@data-options: '\Grav\Common\Page\Pages::types'
validate:
required: true
visible:
type: toggle
label: Visible
help: "Determines if a page is visible in the navigation."
highlight: ''
default: ''
options:
'': Auto
1: Yes
0: No
validate:
type: bool
required: true
blueprint:
type: blueprint

View File

@@ -1,42 +0,0 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
title:
type: text
label: Title
validate:
required: true
folder:
type: text
label: Folder
validate:
type: slug
required: true
route:
type: select
label: Parent
classes: fancy
@data-options: '\Grav\Common\Page\Pages::parents'
@data-default: '\Grav\Plugin\admin::route'
options:
'/': '- Root -'
validate:
required: true
type:
type: select
classes: fancy
label: Display Template
default: default
@data-options: '\Grav\Common\Page\Pages::types'
validate:
required: true

View File

@@ -82,3 +82,5 @@ form:
type: order
label: Ordering
blueprint:
type: blueprint

View File

@@ -12,13 +12,16 @@ form:
type: text
size: large
label: Username
disabled: true
readonly: true
email:
type: text
type: email
size: large
label: Email
validate:
type: email
message: Must be a valid email address
required: true
password:
@@ -27,8 +30,8 @@ form:
label: Password
validate:
required: true
message: Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters
pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
fullname:
type: text
@@ -41,46 +44,3 @@ form:
type: text
size: large
label: Title
admin:
type: section
title: Admin Access
fields:
access.admin.super:
type: toggle
label: Super user
default: 0
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
access.admin.login:
type: toggle
label: Admin login
default: 0
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
site:
type: section
title: Site Access
fields:
access.site.login:
type: toggle
label: Site login
default: 1
highlight: 1
options:
1: Yes
0: No
validate:
type: bool

View File

@@ -75,6 +75,10 @@ txt:
type: file
thumb: media/thumb-txt.png
mime: text/plain
xml:
type: file
thumb: media/thumb-xml.png
mime: application/xml
doc:
type: file
thumb: media/thumb-doc.png
@@ -95,3 +99,16 @@ gz:
type: file
thumb: media/thumb-gz.png
mime: application/gzip
css:
type: file
thumb: media/thumb-css.png
mime: text/css
js:
type: file
thumb: media/thumb-js.png
mime: application/javascript
json:
type: file
thumb: media/thumb-json.png
mime: application/json

View File

@@ -1,18 +1,34 @@
title: Grav # Name of the site
author:
name: John Appleseed # Default author name
email: 'john@email.com' # Default author email
taxonomies: [category,tag] # Arbitrary list of taxonomy types
blog:
route: '/blog' # Route to blog
metadata:
description: 'My Grav Site' # Site description
summary:
enabled: true # enable or disable summary of page
format: short # long = summary delimiter will be ignored; short = use the first occurence of delimter or size
format: short # long = summary delimiter will be ignored; short = use the first occurrence of delimiter or size
size: 300 # Maximum length of summary (characters)
delimiter: === # The summary delimiter
redirects:
/redirect-test: / # Redirect test goes to home page
/old/(.*): /new/$1 # Would redirect /old/my-page to /new/my-page
routes:
/something/else: '/blog/sample-3' # Alias for /blog/sample-3
/another/one/here: '/blog/sample-3' # Another alias for /blog/sample-3
/new/*: '/blog/*' # Wildcard any /new/my-page URL to /blog/my-page Route
/new/(.*): '/blog/$1' # Regex any /new/my-page URL to /blog/my-page Route
blog:
route: '/blog' # Custom value added (accessible via system.blog.route)
#menu: # Sample Menu Example
# - text: Source
# icon: github
# url: https://github.com/getgrav/grav
# - icon: twitter
# url: http://twitter.com/getgrav

View File

@@ -2,13 +2,23 @@ absolute_urls: false # Absolute or relative URLs for `base_url
timezone: '' # Valid values: http://php.net/manual/en/timezones.php
param_sep: ':' # Parameter separator, use ';' for Apache on windows
languages:
supported: [] # List of languages supported. eg: [en, fr, de]
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
home_redirect:
include_lang: true # Include language in home redirect (/en)
include_route: false # Include route in home redirect (/blog)
home:
alias: '/home' # Default path for home, ie /
pages:
theme: antimatter # Default theme (defaults to "antimatter" theme)
order:
by: defaults # Order pages by "default", "alpha" or "date"
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
@@ -30,6 +40,12 @@ pages:
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
cache:
enabled: true # Set to true to enable caching
@@ -55,8 +71,9 @@ assets: # Configuration for Assets Manager (JS, C
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.3.min.js
jquery: system://assets/jquery/jquery-2.1.4.min.js
errors:
display: true # Display full backtrace-style error page
@@ -70,4 +87,18 @@ debugger:
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
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
session:
enabled: true # Enable Session support
timeout: 1800 # Timeout in seconds
name: grav-site # Name prefix of the session cookie
security:
default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK

View File

@@ -2,12 +2,12 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '0.9.21');
define('GRAV_VERSION', '0.9.35');
define('DS', '/');
// Directories and Paths
if (!defined('GRAV_ROOT')) {
define('GRAV_ROOT', getcwd());
define('GRAV_ROOT', str_replace(DIRECTORY_SEPARATOR, DS, getcwd()));
}
define('ROOT_DIR', GRAV_ROOT . '/');
define('USER_PATH', 'user/');

58
system/languages/en.yaml Normal file
View File

@@ -0,0 +1,58 @@
INFLECTOR_PLURALS:
'/(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:
'/(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: ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep']
INFLECTOR_IRREGULAR:
'person': 'people'
'man': 'men'
'child': 'children'
'sex': 'sexes'
'move': 'moves'
INFLECTOR_ORDINALS:
'default': 'th'
'first': 'st'
'second': 'nd'
'third': 'rd'

View File

@@ -47,7 +47,7 @@ class Assets
* Closure used by the pipeline to fetch assets.
*
* Useful when file_get_contents() function is not available in your PHP
* instalation or when you want to apply any kind of preprocessing to
* installation or when you want to apply any kind of preprocessing to
* your assets before they get pipelined.
*
* The closure will receive as the only parameter a string with the path/URL of the asset and
@@ -71,6 +71,7 @@ class Assets
// Some configuration variables
protected $config;
protected $base_url;
protected $timestamp = '';
// Default values for pipeline settings
protected $css_minify = true;
@@ -82,7 +83,6 @@ class Assets
protected $css_no_pipeline = array();
protected $js_no_pipeline = array();
public function __construct(array $options = array())
{
// Forward config options
@@ -154,6 +154,12 @@ class Assets
}
}
// Set timestamp
if (isset($config['enable_asset_timestamp']) && $config['enable_asset_timestamp'] === true) {
$this->timestamp = '?' . self::getGrav()['cache']->getKey();
}
return $this;
}
@@ -422,11 +428,11 @@ class Assets
$output .= '<link href="' . $this->pipeline(CSS_ASSET) . '"' . $attributes . ' />' . "\n";
foreach ($this->css_no_pipeline as $file) {
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' />' . "\n";
}
} else {
foreach ($this->css as $file) {
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' />' . "\n";
}
}
@@ -480,11 +486,11 @@ class Assets
if ($this->js_pipeline) {
$output .= '<script src="' . $this->pipeline(JS_ASSET) . '"' . $attributes . ' ></script>' . "\n";
foreach ($this->js_no_pipeline as $file) {
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ' . $file['loading']. '></script>' . "\n";
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading']. '></script>' . "\n";
}
} else {
foreach ($this->js as $file) {
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ' . $file['loading'].'></script>' . "\n";
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading'].'></script>' . "\n";
}
}
@@ -503,7 +509,7 @@ class Assets
/**
* Minifiy and concatenate CSS / JS files.
* Minify and concatenate CSS / JS files.
*
* @return string
*/
@@ -735,7 +741,7 @@ class Assets
/**
* Determine whether a link is local or remote.
*
* Undestands both "http://" and "https://" as well as protocol agnostic links "//"
* Understands both "http://" and "https://" as well as protocol agnostic links "//"
*
* @param string $link
*
@@ -819,7 +825,8 @@ class Assets
} else {
// Fix to remove relative dir if grav is in one
if (($this->base_url != '/') && (strpos($this->base_url, $link) == 0)) {
$relative_path = str_replace($this->base_url, '/', $link);
$base_url = '#' . preg_quote($this->base_url, '#') . '#';
$relative_path = ltrim(preg_replace($base_url, '/', $link, 1), '/');
}
$relative_dir = dirname($relative_path);
@@ -875,18 +882,7 @@ class Assets
return $matches[0];
}
$newpath = array();
$paths = explode('/', $old_url);
foreach ($paths as $path) {
if ($path == '..') {
$relative_path = dirname($relative_path);
} else {
$newpath[] = $path;
}
}
$new_url = rtrim($this->base_url, '/') . $relative_path . '/' . implode('/', $newpath);
$new_url = $this->base_url . ltrim(Utils::normalizePath($relative_path . '/' . $old_url), '/');
return str_replace($old_url, $new_url, $matches[0]);
},
@@ -924,7 +920,7 @@ class Assets
*
* @param string $directory
* @param string $pattern (regex)
* @param string $ltrim Will be trimed from the left of the file path
* @param string $ltrim Will be trimmed from the left of the file path
*
* @return array
*/

View File

@@ -0,0 +1,125 @@
<?php
namespace Grav\Common\Backup;
use Grav\Common\GravTrait;
use Grav\Common\Filesystem\Folder;
/**
* The ZipBackup class lets you create simple zip-backups of a grav site
*
* @author RocketTheme
* @license MIT
*/
class ZipBackup
{
use GravTrait;
protected static $ignorePaths = [
'backup',
'cache',
'images',
'logs'
];
protected static $ignoreFolders = [
'.git',
'.idea'
];
public static function backup($destination = null, callable $messager = null)
{
if (!$destination) {
$destination = self::getGrav()['locator']->findResource('backup://', true);
if (!$destination)
throw new \RuntimeException('The backup folder is missing.');
Folder::mkdir($destination);
}
$name = self::getGrav()['config']->get('site.title', basename(GRAV_ROOT));
if (is_dir($destination)) {
$date = date('YmdHis', time());
$filename = $name . '-' . $date . '.zip';
$destination = rtrim($destination, DS) . DS . $filename;
}
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => 'Creating new Backup "' . $destination . '"'
]);
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => ''
]);
$zip = new \ZipArchive();
$zip->open($destination, \ZipArchive::CREATE);
static::folderToZip(GRAV_ROOT, $zip, strlen(rtrim(GRAV_ROOT, DS) . DS), $messager);
$messager && $messager([
'type' => 'progress',
'percentage' => false,
'complete' => true
]);
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => ''
]);
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => 'Saving and compressing archive...'
]);
$zip->close();
return $destination;
}
/**
* @param $folder
* @param $zipFile
* @param $exclusiveLength
* @param $messager
*/
private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, callable $messager = null)
{
$handle = opendir($folder);
while (false !== $f = readdir($handle)) {
if ($f != '.' && $f != '..') {
$filePath = "$folder/$f";
// Remove prefix from file path before add to zip.
$localPath = substr($filePath, $exclusiveLength);
if (in_array($f, static::$ignoreFolders)) {
continue;
} elseif (in_array($localPath, static::$ignorePaths)) {
$zipFile->addEmptyDir($f);
continue;
}
if (is_file($filePath)) {
$zipFile->addFile($filePath, $localPath);
$messager && $messager([
'type' => 'progress',
'percentage' => false,
'complete' => false
]);
} elseif (is_dir($filePath)) {
// Add sub-directory.
$zipFile->addEmptyDir($localPath);
static::folderToZip($filePath, $zipFile, $exclusiveLength, $messager);
}
}
}
closedir($handle);
}
}

View File

@@ -271,7 +271,7 @@ class Cache extends Getters
/**
* Set the cache lifetime programatically
* Set the cache lifetime programmatically
*
* @param int $future timestamp
*/

View File

@@ -0,0 +1,55 @@
<?php
namespace Grav\Common;
/**
* Offers composer helper methods.
*
* @author eschmar
* @license MIT
*/
class Composer
{
/** @const Default composer location */
const DEFAULT_PATH = "bin/composer.phar";
/**
* Returns the location of composer.
*
* @return string
*/
public static function getComposerLocation()
{
if (!function_exists('shell_exec') || strtolower(substr(PHP_OS, 0, 3)) === 'win') {
return self::DEFAULT_PATH;
}
// check for global composer install
$path = trim(shell_exec("command -v composer"));
// fall back to grav bundled composer
if (!$path || !preg_match('/(composer|composer\.phar)$/', $path)) {
$path = self::DEFAULT_PATH;
}
return $path;
}
public static function getComposerExecutor()
{
$executor = PHP_BINARY . ' ';
$composer = static::getComposerLocation();
if ($composer !== static::DEFAULT_PATH && is_executable($composer)) {
$file = fopen($composer, 'r');
$firstLine = fgets($file);
fclose($file);
if (!preg_match('/^#!.+php/i', $firstLine)) {
$executor = '';
}
}
return $executor . $composer;
}
}

View File

@@ -60,6 +60,12 @@ class Config extends Data
'' => ['user://themes'],
]
],
'languages' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://languages', 'system/languages'],
]
],
'cache' => [
'type' => 'Stream',
'prefixes' => [
@@ -72,49 +78,52 @@ class Config extends Data
'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 = [];
public function __construct(array $items = array(), Grav $grav = null, $environment = null)
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;
if (isset($items['@class'])) {
if ($items['@class'] != get_class($this)) {
throw new \InvalidArgumentException('Unrecognized config cache file!');
}
// Loading pre-compiled configuration.
$this->timestamp = (int) $items['timestamp'];
$this->checksum = $items['checksum'];
$this->items = (array) $items['data'];
} else {
// Make sure that
if (!isset($items['streams']['schemes'])) {
$items['streams']['schemes'] = [];
}
$items['streams']['schemes'] += $this->streams;
$items = $this->autoDetectEnvironmentConfig($items);
$this->messages[] = $items['streams']['schemes']['config']['prefixes'][''];
parent::__construct($items);
// 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();
}
@@ -125,8 +134,10 @@ class Config extends Data
public function reload()
{
$this->items = $this->setup;
$this->check();
$this->init();
$this->debug();
return $this;
}
@@ -150,6 +161,7 @@ class Config extends Data
foreach ($this->messages as $message) {
$this->grav['debugger']->addMessage($message);
}
$this->messages = [];
}
public function init()
@@ -161,51 +173,57 @@ class Config extends Data
$this->blueprintLookup = $locator->findResources('blueprints://config');
$this->pluginLookup = $locator->findResources('plugins://');
if (!isset($this->checksum)) {
$this->messages[] = 'No cached configuration, compiling new configuration..';
} elseif ($this->checksum() != $this->checksum) {
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
} else {
$this->messages[] = 'Configuration checksum matches, using cached version.';
return;
}
$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()
{
$checkBlueprints = $this->get('system.cache.check.blueprints', false);
$checkConfig = $this->get('system.cache.check.config', true);
$checkSystem = $this->get('system.cache.check.system', true);
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 && !$checkConfig && !$checkSystem) {
$this->messages[] = 'Skip configuration timestamp check.';
return false;
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]));
}
// Generate checksum according to the configuration settings.
if (!$checkConfig) {
$this->messages[] = 'Check configuration timestamps from system.yaml files.';
// Just check changes in system.yaml files and ignore all the other files.
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
} else {
$this->messages[] = 'Check configuration timestamps from all configuration files.';
// Check changes in all configuration files.
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
}
if ($checkBlueprints) {
$this->messages[] = 'Check blueprint timestamps from all blueprint files.';
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
} else {
$cb = [];
}
return md5(json_encode([$cc, $cb]));
return $this->checksum;
}
protected function autoDetectEnvironmentConfig($items)
@@ -255,7 +273,6 @@ class Config extends Data
'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.';
@@ -269,14 +286,71 @@ class Config extends Data
protected function loadCompiledConfig($configs, $plugins, $filename = null)
{
$checksum = md5(json_encode($configs));
$filename = $filename
? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$cache = $file->exists() ? $file->content() : null;
$class = get_class($this);
$checksum = $this->checksum();
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['@class'] != $class
) {
$this->messages[] = 'No cached configuration, compiling new configuration..';
} else if ($cache['checksum'] !== $checksum) {
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
} else {
$this->messages[] = 'Configuration checksum matches, using cached version.';
$this->items = $cache['data'];
return;
}
$configFiles = $this->finder->locateConfigFiles($configs, $plugins);
$checksum .= ':'.md5(json_encode($configFiles));
// 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).
@@ -290,26 +364,40 @@ class Config extends Data
// 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' => $this->checksum(),
'data' => $this->toArray()
];
// Load languages.
$this->languages = new Languages;
if (isset($languageFiles['user/plugins'])) {
foreach ((array) $languageFiles['user/plugins'] as $plugin => $item) {
$lang_file = CompiledYamlFile::instance($item['file']);
$content = $lang_file->content();
$this->languages->mergeRecursive($content);
}
}
if (isset($languageFiles['system/languages'])) {
foreach ((array) $languageFiles['system/languages'] 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 configuration.';
$this->messages[] = 'Saving compiled languages.';
$file->save($cache);
$file->unlock();
}
} else {
$this->languages = new Languages($cache['data']);
}
$this->items = $cache['data'];
}
/**
@@ -380,4 +468,9 @@ class Config extends Data
return $schemes;
}
public function getLanguages()
{
return $this->languages;
}
}

View File

@@ -49,6 +49,18 @@ class ConfigFinder
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.
*
@@ -90,11 +102,11 @@ class ConfigFinder
$list = [];
if (is_dir($folder)) {
$iterator = new \DirectoryIterator($folder);
$iterator = new \FilesystemIterator($folder);
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
if (!$directory->isDir() || $directory->isDot()) {
if (!$directory->isDir()) {
continue;
}
@@ -111,6 +123,34 @@ class ConfigFinder
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.
*

View File

@@ -0,0 +1,27 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\Data\Data;
/**
* The Languages class contains configuration rules.
*
* @author RocketTheme
* @license MIT
*/
class Languages extends Data
{
public function reformat()
{
if (isset($this->items['plugins'])) {
$this->items = array_merge_recursive($this->items, $this->items['plugins']);
unset($this->items['plugins']);
}
}
public function mergeRecursive(array $data)
{
$this->items = array_merge_recursive($this->items, $data);
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace Grav\Common\Data;
use Grav\Common\GravTrait;
use RocketTheme\Toolbox\ArrayTraits\Export;
/**
@@ -11,7 +12,7 @@ use RocketTheme\Toolbox\ArrayTraits\Export;
*/
class Blueprint
{
use Export, DataMutatorTrait;
use Export, DataMutatorTrait, GravTrait;
public $name;
@@ -75,7 +76,7 @@ class Blueprint
try {
$this->validateArray($data, $this->nested);
} catch (\RuntimeException $e) {
throw new \RuntimeException(sprintf('Page validation failed: %s', $e->getMessage()));
throw new \RuntimeException(sprintf('<b>Validation failed:</b> %s', $e->getMessage()));
}
}
@@ -117,7 +118,17 @@ class Blueprint
{
// Initialize data
$this->fields();
return $this->extraArray($data, $this->nested, $prefix);
$rules = $this->nested;
// Drill down to prefix level
if (!empty($prefix)) {
$parts = explode('.', trim($prefix, '.'));
foreach ($parts as $part) {
$rules = isset($rules[$part]) ? $rules[$part] : [];
}
}
return $this->extraArray($data, $rules, $prefix);
}
/**
@@ -286,7 +297,7 @@ class Blueprint
// Item has been defined in blueprints.
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$array += $this->ExtraArray($field, $val, $prefix);
$array += $this->ExtraArray($field, $val, $prefix . $key . '.');
} else {
// Undefined/extra item.
$array[$prefix.$key] = $field;
@@ -313,11 +324,11 @@ class Blueprint
$field['name'] = $prefix . $key;
$field += $params;
if (isset($field['fields'])) {
if (isset($field['fields']) && $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 {
} else if ($field['type'] !== 'ignore') {
// Add rule.
$this->rules[$prefix . $key] = &$field;
$this->addProperty($prefix . $key);
@@ -362,10 +373,20 @@ class Blueprint
}
}
}
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;
}
}
}
// Initialize predefined validation rule.
if (isset($field['validate']['rule'])) {
if (isset($field['validate']['rule']) && $field['type'] !== 'ignore') {
$field['validate'] += $this->getRule($field['validate']['rule']);
}
}

View File

@@ -2,6 +2,7 @@
namespace Grav\Common\Data;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\GravTrait;
/**
* Blueprints class keeps track on blueprint instances.
@@ -11,6 +12,8 @@ use Grav\Common\File\CompiledYamlFile;
*/
class Blueprints
{
use GravTrait;
protected $search;
protected $types;
protected $instances = array();
@@ -55,8 +58,20 @@ class Blueprints
if (isset($blueprints['@extends'])) {
// Extend blueprint by other blueprints.
$extends = (array) $blueprints['@extends'];
foreach ($extends as $extendType) {
$blueprint->extend($this->get($extendType));
if (is_string(key($extends))) {
$extends = [ $extends ];
}
foreach ($extends as $extendConfig) {
$extendType = !is_string($extendConfig) ? empty($extendConfig['type']) ? false : $extendConfig['type'] : $extendConfig;
if (!$extendType) {
continue;
}
$context = is_string($extendConfig) || empty($extendConfig['context']) ? $this : new self(self::getGrav()['locator']->findResource($extendConfig['context']));
$blueprint->extend($context->get($extendType));
}
}

View File

@@ -32,7 +32,7 @@ trait DataMutatorTrait
}
/**
* Sey value by using dot notation for nested arrays/objects.
* Set value by using dot notation for nested arrays/objects.
*
* @example $value = $data->set('this.is.my.nested.variable', true);
*

View File

@@ -28,14 +28,16 @@ class Validation
// Validate type with fallback type text.
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
$method = 'type'.strtr($type, '-', '_');
$name = ucfirst($field['label'] ? $field['label'] : $field['name']);
$message = (string) isset($field['validate']['message']) ? $field['validate']['message'] : 'Invalid input in ' . $name;
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $validate, $field);
} else {
$success = self::typeText($value, $validate, $field);
}
if (!$success) {
$name = $field['label'] ? $field['label'] : $field['name'];
throw new \RuntimeException("invalid input in {$name}");
throw new \RuntimeException($message);
}
// Check individual rules
@@ -43,8 +45,9 @@ class Validation
$method = 'validate'.strtr($rule, '-', '_');
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $params);
if (!$success) {
throw new \RuntimeException('Failed');
throw new \RuntimeException($message);
}
}
}
@@ -489,6 +492,7 @@ class Validation
{
$values = (array) $value;
$options = isset($field['options']) ? array_keys($field['options']) : array();
$multi = isset($field['multiple']) ? $field['multiple'] : false;
if ($options) {
$useKey = isset($field['use']) && $field['use'] == 'keys';
@@ -497,9 +501,39 @@ class Validation
}
}
if ($multi) {
foreach ($values as $key => $value) {
$values[$key] = explode(',', $value[0]);
}
}
return $values;
}
public static function typeList($value, array $params, array $field)
{
if (!is_array($value)) {
return false;
}
if (isset($field['fields'])) {
foreach ($value as $key => $item) {
foreach ($field['fields'] as $subKey => $subField) {
$subKey = trim($subKey, '.');
$subValue = isset($item[$subKey]) ? $item[$subKey] : null;
self::validate($subValue, $subField);
}
}
}
return true;
}
protected static function filterList($value, array $params, array $field)
{
return (array) $value;
}
/**
* Custom input: ignore (will not validate)
*
@@ -513,6 +547,11 @@ class Validation
return true;
}
public static function filterIgnore($value, array $params, array $field)
{
return $value;
}
// HTML5 attributes (min, max and range are handled inside the types)
public static function validateRequired($value, $params)

View File

@@ -25,7 +25,7 @@ trait CompiledFile
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
if ($var === null && $this->raw === null && $this->content === null) {
$key = md5($this->filename);
$file = PhpFile::instance(CACHE_DIR . "/compiled/files/{$key}{$this->extension}.php");
$file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
$modified = $this->modified();
if (!$modified) {

View File

@@ -38,21 +38,20 @@ abstract class Folder
* Recursively find the last modified time under given path by file.
*
* @param string $path
* @param string $extensions
*
* @return int
*/
public static function lastModifiedFile($path)
public static function lastModifiedFile($path, $extensions = 'md|yaml')
{
$last_modified = 0;
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$filterItr = new RecursiveFileFilterIterator($dirItr);
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST);
$itr = new \RegexIterator($itrItr, '/^.+\.'.$extensions.'$/i');
/** @var \RecursiveDirectoryIterator $file */
foreach ($itr as $file) {
if ($file->isDir()) {
continue;
}
foreach ($itr as $filepath => $file) {
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
@@ -72,8 +71,8 @@ abstract class Folder
public static function getRelativePath($path, $base = GRAV_ROOT)
{
if ($base) {
$base = preg_replace('![\\|/]+!', '/', $base);
$path = preg_replace('![\\|/]+!', '/', $path);
$base = preg_replace('![\\\/]+!', '/', $base);
$path = preg_replace('![\\\/]+!', '/', $path);
if (strpos($path, $base) === 0) {
$path = ltrim(substr($path, strlen($base)), '/');
}
@@ -279,6 +278,46 @@ abstract class Folder
}
}
/**
* Recursive copy of one directory to another
*
* @param $src
* @param $dest
*
* @return bool
*/
public static function rcopy($src, $dest)
{
// If the src is not a directory do a simple file copy
if (!is_dir($src)) {
copy($src, $dest);
return true;
}
// If the destination directory does not exist create it
if (!is_dir($dest)) {
if (!mkdir($dest)) {
// If the destination directory could not be created stop processing
return false;
}
}
// Open the source directory to read in files
$i = new \DirectoryIterator($src);
/** @var \DirectoryIterator $f */
foreach ($i as $f) {
if ($f->isFile()) {
copy($f->getRealPath(), "$dest/" . $f->getFilename());
} else {
if (!$f->isDot() && $f->isDir()) {
static::rcopy($f->getRealPath(), "$dest/$f");
}
}
}
return true;
}
/**
* @param string $folder
* @return bool
@@ -291,13 +330,24 @@ abstract class Folder
return @unlink($folder);
}
// Go through all items in filesystem and recursively remove everything.
$files = array_diff(scandir($folder), array('.', '..'));
foreach ($files as $file) {
$path = "{$folder}/{$file}";
(is_dir($path)) ? self::doDelete($path) : @unlink($path);
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
/** @var \DirectoryIterator $fileinfo */
foreach ($files as $fileinfo) {
if ($fileinfo->isDir()) {
if (false === rmdir($fileinfo->getRealPath())) {
return false;
}
} else {
if (false === unlink($fileinfo->getRealPath())) {
return false;
}
}
}
return @rmdir($folder);
return rmdir($folder);
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace Grav\Common\Filesystem;
class RecursiveFileFilterIterator extends \RecursiveFilterIterator
{
public static $FILTERS = ['.DS_Store'];
public function accept()
{
// Ensure any filtered file names are skipped
return !in_array($this->current()->getFilename(), self::$FILTERS, true);
}
}

View File

@@ -349,6 +349,7 @@ class GPM extends Iterator
public function findPackages($searches = [])
{
$packages = ['total' => 0, 'not_found' => []];
$inflector = new Inflector();
foreach ($searches as $search) {
$repository = '';
@@ -361,7 +362,7 @@ class GPM extends Iterator
}
if ($found = $this->findPackage($search)) {
// set override respository if provided
// set override repository if provided
if ($repository) {
$found->override_repository = $repository;
}
@@ -380,7 +381,7 @@ class GPM extends Iterator
}
$not_found = new \stdClass();
$not_found->name = Inflector::camelize($search);
$not_found->name = $inflector->camelize($search);
$not_found->slug = $search;
$not_found->package_type = $type;
$not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]);

View File

@@ -158,9 +158,9 @@ class Installer
/**
* Unnstalls one or more given package
* Uninstalls one or more given package
*
* @param string $package The slug of the package(s)
* @param string $path The slug of the package(s)
* @param array $options Options to use for uninstalling
*
* @return boolean True if everything went fine, False otherwise.

View File

@@ -24,8 +24,8 @@ class Grav extends AbstractPackageCollection
$this->fetch($refresh, $callback);
$this->data = json_decode($this->raw, true);
$this->version = @$this->data['version'] ?: '-';
$this->date = @$this->data['date'] ?: '-';
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
foreach ($this->data['assets'] as $slug => $data) {
$this->items[$slug] = new Package($data);
@@ -38,7 +38,7 @@ class Grav extends AbstractPackageCollection
*/
public function getAssets()
{
return $this->data->assets;
return $this->data['assets'];
}
/**
@@ -50,11 +50,11 @@ class Grav extends AbstractPackageCollection
public function getChangelog($diff = null)
{
if (!$diff) {
return $this->data->changelog;
return $this->data['changelog'];
}
$diffLog = [];
foreach ($this->data->changelog as $version => $changelog) {
foreach ($this->data['changelog'] as $version => $changelog) {
preg_match("/[\d\.]+/", $version, $cleanVersion);
if (!$cleanVersion || version_compare($diff, $cleanVersion[0], ">=")) { continue; }

View File

@@ -160,6 +160,8 @@ class Response
private static function getCurl()
{
$args = func_get_args();
$args = count($args) > 1 ? $args : array_shift($args);
$uri = $args[0];
$options = $args[1];
$callback = $args[2];

View File

@@ -65,7 +65,7 @@ class Upgrader
* Returns the changelog list for each version of Grav
* @param string $diff the version number to start the diff from
*
* @return array return the chagenlog list for each version
* @return array return the changelog list for each version
*/
public function getChangelog($diff = null)
{

View File

@@ -1,16 +1,17 @@
<?php
namespace Grav\Common;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Language\Language;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Pages;
use Grav\Common\Service\ConfigServiceProvider;
use Grav\Common\Service\ErrorServiceProvider;
use Grav\Common\Service\LoggerServiceProvider;
use Grav\Common\Service\StreamsServiceProvider;
use Grav\Common\Twig\Twig;
use RocketTheme\Toolbox\DI\Container;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
use Grav\Common\Page\Medium\Medium;
/**
* Grav
@@ -56,6 +57,8 @@ class Grav extends Container
$container['grav'] = $container;
$container['debugger'] = new Debugger();
$container['debugger']->startTimer('_init', 'Initialize');
@@ -77,8 +80,11 @@ class Grav extends Container
$container['cache'] = function ($c) {
return new Cache($c);
};
$container['session'] = function ($c) {
return new Session($c);
};
$container['plugins'] = function ($c) {
return new Plugins($c);
return new Plugins();
};
$container['themes'] = function ($c) {
return new Themes($c);
@@ -89,44 +95,38 @@ class Grav extends Container
$container['taxonomy'] = function ($c) {
return new Taxonomy($c);
};
$container['language'] = function ($c) {
return new Language($c);
};
$container['pages'] = function ($c) {
return new Page\Pages($c);
};
$container['assets'] = function ($c) {
return new Assets();
};
$container['assets'] = new Assets();
$container['page'] = function ($c) {
/** @var Pages $pages */
$pages = $c['pages'];
// If base URI is set, we want to remove it from the URL.
$path = '/' . ltrim(Folder::getRelativePath($c['uri']->route(), $pages->base()), '/');
/** @var Uri $uri */
$uri = $c['uri'];
$path = rtrim($uri->path(), '/');
$path = $path ?: '/';
$page = $pages->dispatch($path);
// handle redirect if not 'default route' configuration
if ($page && $c['config']->get('system.pages.redirect_default_route') && $page->route() != $path) {
$c->redirectLangSafe($page->route());
}
// if page is not found, try some fallback stuff
if (!$page || !$page->routable()) {
// special case where a media file is requested
$path_parts = pathinfo($path);
$page = $c['pages']->dispatch($path_parts['dirname'], true);
if ($page) {
$media = $page->media()->all();
$media_file = urldecode($path_parts['basename']);
if (isset($media[$media_file])) {
$medium = $media[$media_file];
// loop through actions for the image and call them
foreach ($c['uri']->query(null, true) as $action => $params) {
if (in_array($action, Medium::$valid_actions)) {
call_user_func_array(array(&$medium, $action), explode(',', $params));
}
}
header('Content-type: '. $medium->get('mime'));
echo file_get_contents($medium->path());
die;
}
}
// Try fallback URL stuff...
$c->fallbackUrl($page, $path);
// If no page found, fire event
$event = $c->fireEvent('onPageNotFound');
@@ -159,6 +159,8 @@ class Grav extends Container
$container->register(new StreamsServiceProvider);
$container->register(new ConfigServiceProvider);
$container['inflector'] = new Inflector();
$container['debugger']->stopTimer('_init');
return $container;
@@ -166,12 +168,6 @@ class Grav extends Container
public function process()
{
// Use output buffering to prevent headers from being sent too early.
ob_start();
if ($this['config']->get('system.cache.gzip')) {
ob_start('ob_gzhandler');
}
/** @var Debugger $debugger */
$debugger = $this['debugger'];
@@ -179,11 +175,18 @@ class Grav extends Container
$debugger->startTimer('_config', 'Configuration');
$this['config']->init();
$this['uri']->init();
$this['session']->init();
$this['errors']->resetHandlers();
$debugger->init();
$this['config']->debug();
$debugger->stopTimer('_config');
// Use output buffering to prevent headers from being sent too early.
ob_start();
if ($this['config']->get('system.cache.gzip')) {
ob_start('ob_gzhandler');
}
// Initialize the timezone
if ($this['config']->get('system.timezone')) {
date_default_timezone_set($this['config']->get('system.timezone'));
@@ -219,7 +222,6 @@ class Grav extends Container
$this['pages']->init();
$this->fireEvent('onPagesInitialized');
$debugger->stopTimer('pages');
$this->fireEvent('onPageInitialized');
$debugger->addAssets();
@@ -265,6 +267,25 @@ class Grav extends Container
exit();
}
/**
* Redirect browser to another location taking language into account (preferred)
*
* @param string $route Internal route.
* @param int $code Redirection code (30x)
*/
public function redirectLangSafe($route, $code = 303)
{
/** @var Language $language */
$language = $this['language'];
$config = $this['config'];
if ($language->enabled()) {
return $this->redirect($language->getLanguage() . $route, $code);
} else {
return $this->redirect($route);
}
}
/**
* Returns mime type for the file format.
*
@@ -294,8 +315,32 @@ class Grav extends Container
public function header()
{
$extension = $this['uri']->extension();
/** @var Page $page */
$page = $this['page'];
header('Content-type: ' . $this->mime($extension));
// Calculate Expires Headers if set to > 0
$expires = $page->expires();
if ($expires > 0) {
$expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT';
header('Cache-Control: max-age=' . $expires_date);
header('Expires: '. $expires_date);
}
// Set the last modified time
if ($page->lastModified()) {
$last_modified_date = gmdate('D, d M Y H:i:s', $page->modified()) . ' GMT';
header('Last-Modified: ' . $last_modified_date);
}
// Calculate a Hash based on the raw file
if ($page->eTag()) {
header('ETag: ' . md5($page->raw() . $page->modified()));
}
// Set debugger data in headers
if (!($extension === null || $extension == 'html')) {
$this['debugger']->enabled(false);
@@ -305,6 +350,11 @@ class Grav extends Container
if (isset($this['page']->header()->http_response_code)) {
http_response_code($this['page']->header()->http_response_code);
}
// Vary: Accept-Encoding
if ($this['config']->get('system.pages.vary_accept_encoding', false)) {
header('Vary: Accept-Encoding');
}
}
/**
@@ -328,26 +378,31 @@ class Grav extends Container
public function shutdown()
{
if ($this['config']->get('system.debugger.shutdown.close_connection')) {
//stop user abort
if (function_exists('ignore_user_abort')) {
@ignore_user_abort(true);
}
// 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
}
// get lengh and close the connection
header('Content-Length: ' . ob_get_length());
header("Connection: close\r\n");
header("Connection: close");
ob_end_flush(); // regular buffer
ob_flush();
// flush the regular buffer
ob_end_flush();
@ob_flush();
flush();
// fix for fastcgi close connection issue
if (function_exists('fastcgi_finish_request')) {
@fastcgi_finish_request();
}
@@ -356,4 +411,56 @@ class Grav extends Container
$this->fireEvent('onShutdown');
}
/**
* This attempts to fine media, other files, and download them
* @param $page
* @param $path
*/
protected function fallbackUrl($page, $path)
{
/** @var Uri $uri */
$uri = $this['uri'];
$path_parts = pathinfo($path);
$page = $this['pages']->dispatch($path_parts['dirname'], true);
if ($page) {
$media = $page->media()->all();
$parsed_url = parse_url(urldecode($uri->basename()));
$media_file = $parsed_url['path'];
// if this is a media object, try actions first
if (isset($media[$media_file])) {
$medium = $media[$media_file];
foreach ($uri->query(null, true) as $action => $params) {
if (in_array($action, ImageMedium::$magic_actions)) {
call_user_func_array(array(&$medium, $action), explode(',', $params));
}
}
Utils::download($medium->path(), false);
}
// unsupported media type, try to download it...
$uri_extension = $uri->extension();
if ($uri_extension) {
$extension = $uri_extension;
} else {
if (isset($path_parts['extension'])) {
$extension = $path_parts['extension'];
} else {
$extension = null;
}
}
if ($extension) {
$download = true;
if (in_array(ltrim($extension, '.'), $this['config']->get('system.media.unsupported_inline_types'))) {
$download = false;
}
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
}
}
}
}

View File

@@ -4,6 +4,7 @@ namespace Grav\Common;
/**
* This file was originally part of the Akelos Framework
*/
use Grav\Common\Language\Language;
/**
* Inflector for pluralize and singularize English nouns.
@@ -20,65 +21,55 @@ namespace Grav\Common;
class Inflector
{
use GravTrait;
protected $plural;
protected $singular;
protected $uncountable;
protected $irregular;
protected $ordinals;
public function init()
{
if (empty($this->plural)) {
$language = self::getGrav()['language'];
$this->plural = $language->translate('INFLECTOR_PLURALS', null, true);
$this->singular = $language->translate('INFLECTOR_SINGULAR', null, true);
$this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true);
$this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true);
$this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true);
}
}
/**
* Pluralizes English nouns.
*
* @access static public
* @static
* @param string $word English noun to pluralize
* @return string Plural noun
*/
public static function pluralize($word, $count = 2)
public function pluralize($word, $count = 2)
{
$this->init();
if ($count == 1) {
return $word;
}
$plural = array(
'/(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');
$uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep');
$irregular = array(
'person' => 'people',
'man' => 'men',
'child' => 'children',
'sex' => 'sexes',
'move' => 'moves');
$lowercased_word = strtolower($word);
foreach ($uncountable as $_uncountable) {
foreach ($this->uncountable as $_uncountable) {
if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) {
return $word;
}
}
foreach ($irregular as $_plural => $_singular) {
foreach ($this->irregular as $_plural => $_singular) {
if (preg_match('/('.$_plural.')$/i', $word, $arr)) {
return preg_replace('/('.$_plural.')$/i', substr($arr[0], 0, 1).substr($_singular, 1), $word);
}
}
foreach ($plural as $rule => $replacement) {
foreach ($this->plural as $rule => $replacement) {
if (preg_match($rule, $word)) {
return preg_replace($rule, $replacement, $word);
}
@@ -94,62 +85,28 @@ class Inflector
* @param int $count
* @return string Singular noun.
*/
public static function singularize($word, $count = 1)
public function singularize($word, $count = 1)
{
$this->init();
if ($count != 1) {
return $word;
}
$singular = array (
'/(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' => '',
);
$uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep');
$irregular = array(
'person' => 'people',
'man' => 'men',
'child' => 'children',
'sex' => 'sexes',
'move' => 'moves');
$lowercased_word = strtolower($word);
foreach ($uncountable as $_uncountable) {
foreach ($this->uncountable as $_uncountable) {
if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) {
return $word;
}
}
foreach ($irregular as $_plural => $_singular) {
foreach ($this->irregular as $_plural => $_singular) {
if (preg_match('/('.$_singular.')$/i', $word, $arr)) {
return preg_replace('/('.$_singular.')$/i', substr($arr[0], 0, 1).substr($_plural, 1), $word);
}
}
foreach ($singular as $rule => $replacement) {
foreach ($this->singular as $rule => $replacement) {
if (preg_match($rule, $word)) {
return preg_replace($rule, $replacement, $word);
}
@@ -162,24 +119,22 @@ class Inflector
* Converts an underscored or CamelCase word into a English
* sentence.
*
* The titleize static public function converts text like "WelcomePage",
* The titleize public function converts text like "WelcomePage",
* "welcome_page" or "welcome page" to this "Welcome
* Page".
* If second parameter is set to 'first' it will only
* capitalize the first character of the title.
*
* @access static public
* @static
* @param string $word Word to format as tile
* @param string $uppercase If set to 'first' it will only uppercase the
* first character. Otherwise it will uppercase all
* the words in the title.
* @return string Text formatted as title
*/
public static function titleize($word, $uppercase = '')
public function titleize($word, $uppercase = '')
{
$uppercase = $uppercase == 'first' ? 'ucfirst' : 'ucwords';
return $uppercase(static::humanize(static::underscorize($word)));
return $uppercase($this->humanize($this->underscorize($word)));
}
/**
@@ -189,13 +144,11 @@ class Inflector
* will remove non alphanumeric character from the word, so
* "who's online" will be converted to "WhoSOnline"
*
* @access static public
* @static
* @see variablize
* @param string $word Word to convert to camel case
* @return string UpperCamelCasedWord
*/
public static function camelize($word)
public function camelize($word)
{
return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word)));
}
@@ -208,12 +161,10 @@ class Inflector
*
* This can be really useful for creating friendly URLs.
*
* @access static public
* @static
* @param string $word Word to underscore
* @return string Underscored word
*/
public static function underscorize($word)
public function underscorize($word)
{
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2', $word);
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1_\2', $regex1);
@@ -229,12 +180,10 @@ class Inflector
*
* This can be really useful for creating friendly URLs.
*
* @access static public
* @static
* @param string $word Word to hyphenate
* @return string hyphenized word
*/
public static function hyphenize($word)
public function hyphenize($word)
{
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word);
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1-\2', $regex1);
@@ -252,14 +201,12 @@ class Inflector
* If you need to uppercase all the words you just have to
* pass 'all' as a second parameter.
*
* @access static public
* @static
* @param string $word String to "humanize"
* @param string $uppercase If set to 'all' it will uppercase all the words
* instead of just the first one.
* @return string Human-readable word
*/
public static function humanize($word, $uppercase = '')
public function humanize($word, $uppercase = '')
{
$uppercase = $uppercase == 'all' ? 'ucwords' : 'ucfirst';
return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word)));
@@ -272,15 +219,13 @@ class Inflector
* will remove non alphanumeric character from the word, so
* "who's online" will be converted to "whoSOnline"
*
* @access static public
* @static
* @see camelize
* @param string $word Word to lowerCamelCase
* @return string Returns a lowerCamelCasedWord
*/
public static function variablize($word)
public function variablize($word)
{
$word = static::camelize($word);
$word = $this->camelize($word);
return strtolower($word[0]).substr($word, 1);
}
@@ -290,15 +235,13 @@ class Inflector
*
* Converts "Person" to "people"
*
* @access static public
* @static
* @see classify
* @param string $class_name Class name for getting related table_name.
* @return string plural_table_name
*/
public static function tableize($class_name)
public function tableize($class_name)
{
return static::pluralize(static::underscore($class_name));
return $this->pluralize($this->underscore($class_name));
}
/**
@@ -307,15 +250,13 @@ class Inflector
*
* Converts "people" to "Person"
*
* @access static public
* @static
* @see tableize
* @param string $table_name Table name for getting related ClassName.
* @return string SingularClassName
*/
public static function classify($table_name)
public function classify($table_name)
{
return static::camelize(static::singularize($table_name));
return $this->camelize($this->singularize($table_name));
}
/**
@@ -323,34 +264,34 @@ class Inflector
*
* This method converts 13 to 13th, 2 to 2nd ...
*
* @access static public
* @static
* @param integer $number Number to get its ordinal value
* @return string Ordinal representation of given string.
*/
public static function ordinalize($number)
public function ordinalize($number)
{
$this->init();
if (in_array(($number % 100), range(11, 13))) {
return $number.'th';
return $number.$this->ordinals['default'];
} else {
switch (($number % 10)) {
case 1:
return $number.'st';
return $number.$this->ordinals['first'];
break;
case 2:
return $number.'nd';
return $number.$this->ordinals['second'];
break;
case 3:
return $number.'rd';
return $number.$this->ordinals['third'];
break;
default:
return $number.'th';
return $number.$this->ordinals['default'];
break;
}
}
}
public static function monthize($days)
public function monthize($days)
{
$now = new \DateTime();
$end = new \DateTime();

View File

@@ -0,0 +1,418 @@
<?php
namespace Grav\Common\Language;
use Grav\Common\Grav;
/**
* Language and translation functionality for Grav
*/
class Language
{
protected $grav;
protected $enabled = true;
protected $languages = [];
protected $page_extensions = [];
protected $fallback_languages = [];
protected $default;
protected $active = null;
protected $config;
protected $http_accept_language;
/**
* Constructor
*
* @param \Grav\Common\Grav $grav
*/
public function __construct(Grav $grav)
{
$this->grav = $grav;
$this->config = $grav['config'];
$this->languages = $this->config->get('system.languages.supported', []);
$this->init();
}
/**
* Initialize the default and enabled languages
*/
public function init()
{
$this->default = reset($this->languages);
if (empty($this->languages)) {
$this->enabled = false;
}
}
/**
* Ensure that languages are enabled
*
* @return bool
*/
public function enabled()
{
return $this->enabled;
}
/**
* Gets the array of supported languages
*
* @return array
*/
public function getLanguages()
{
return $this->languages;
}
/**
* Sets the current supported languages manually
*
* @param $langs
*/
public function setLanguages($langs)
{
$this->languages = $langs;
$this->init();
}
/**
* Gets a pipe-separated string of available languages
*
* @return string
*/
public function getAvailable()
{
return implode('|', $this->languages);
}
/**
* Gets language, active if set, else default
*
* @return mixed
*/
public function getLanguage()
{
return $this->active ? $this->active : $this->default;
}
/**
* Gets current default language
*
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* Sets default language manually
*
* @param $lang
*
* @return bool
*/
public function setDefault($lang)
{
if ($this->validate($lang)) {
$this->default = $lang;
return $lang;
}
return false;
}
/**
* Gets current active language
*
* @return mixed
*/
public function getActive()
{
return $this->active;
}
/**
* Sets active language manually
*
* @param $lang
*
* @return bool
*/
public function setActive($lang)
{
if ($this->validate($lang)) {
$this->active = $lang;
return $lang;
}
return false;
}
/**
* Sets the active language based on the first part of the URL
*
* @param $uri
*
* @return mixed
*/
public function setActiveFromUri($uri)
{
$regex = '/(\/(' . $this->getAvailable() . ')).*/';
// if languages set
if ($this->enabled()) {
// try setting from prefix of URL (/en/blah/blah)
if (preg_match($regex, $uri, $matches)) {
$this->active = $matches[2];
$uri = preg_replace("/\\" . $matches[1] . "/", '', $matches[0], 1);
// store in session if different
if ($this->config->get('system.session.enabled', false)
&& $this->config->get('system.languages.session_store_active', true)
&& $this->grav['session']->active_language != $this->active
) {
$this->grav['session']->active_language = $this->active;
}
} else {
// try getting from session, else no active
if ($this->config->get('system.session.enabled', false) &&
$this->config->get('system.languages.session_store_active', true)) {
$this->active = $this->grav['session']->active_language ?: null;
}
// if still null, try from http_accept_language header
if ($this->active === null && $this->config->get('system.languages.http_accept_language')) {
$preferred = $this->getBrowserLanguages();
foreach ($preferred as $lang) {
if ($this->validate($lang)) {
$this->active = $lang;
break;
}
}
}
}
}
return $uri;
}
/**
* Gets an array of valid extensions with active first, then fallback extensions
*
* @return array
*/
public function getFallbackPageExtensions($file_ext = null)
{
if (empty($this->page_extensions)) {
if (empty($file_ext)) {
$file_ext = CONTENT_EXT;
}
if ($this->enabled()) {
$valid_lang_extensions = [];
foreach ($this->languages as $lang) {
$valid_lang_extensions[] = '.' . $lang . $file_ext;
}
if ($this->active) {
$active_extension = '.' . $this->active . $file_ext;
$key = array_search($active_extension, $valid_lang_extensions);
unset($valid_lang_extensions[$key]);
array_unshift($valid_lang_extensions, $active_extension);
}
$this->page_extensions = array_merge($valid_lang_extensions, (array)$file_ext);
} else {
$this->page_extensions = (array)$file_ext;
}
}
return $this->page_extensions;
}
/**
* Gets an array of languages with active first, then fallback languages
*
* @return array
*/
public function getFallbackLanguages()
{
if (empty($this->fallback_languages)) {
if ($this->enabled()) {
$fallback_languages = $this->languages;
if ($this->active) {
$active_extension = $this->active;
$key = array_search($active_extension, $fallback_languages);
unset($fallback_languages[$key]);
array_unshift($fallback_languages, $active_extension);
}
$this->fallback_languages = $fallback_languages;
}
}
return $this->fallback_languages;
}
/**
* Ensures the language is valid and supported
*
* @param $lang
*
* @return bool
*/
public function validate($lang)
{
if (in_array($lang, $this->languages)) {
return true;
}
return false;
}
/**
* Translate a key and possibly arguments into a string using current lang and fallbacks
*
* @param $args first argument is the lookup key value
* other arguments can be passed and replaced in the translation with sprintf syntax
* @param Array $languages
* @param bool $array_support
* @param bool $html_out
*
* @return string
*/
public function translate($args, Array $languages = null, $array_support = false, $html_out = false)
{
if (is_array($args)) {
$lookup = array_shift($args);
} else {
$lookup = $args;
$args = [];
}
if ($this->config->get('system.languages.translations', true)) {
if (isset($this->grav['admin'])) {
$languages = ['en'];
} elseif ($this->enabled() && $lookup) {
if (empty($languages)) {
if ($this->config->get('system.languages.translations_fallback', true)) {
$languages = $this->getFallbackLanguages();
} else {
$languages = (array)$this->getDefault();
}
}
} else {
$languages = ['en'];
}
foreach ((array)$languages as $lang) {
$translation = $this->getTranslation($lang, $lookup, $array_support);
if ($translation) {
if (count($args) >= 1) {
return vsprintf($translation, $args);
} else {
return $translation;
}
}
}
}
if ($html_out) {
return '<span class="untranslated">' . $lookup . '</span>';
} else {
return $lookup;
}
}
/**
* Translate Array
*
* @param $key
* @param $index
* @param null $languages
* @param bool $html_out
*
* @return string
*/
public function translateArray($key, $index, $languages = null, $html_out = false)
{
if ($this->config->get('system.languages.translations', true)) {
if ($this->enabled() && $key) {
if (empty($languages)) {
if ($this->config->get('system.languages.translations_fallback', true)) {
$languages = $this->getFallbackLanguages();
} else {
$languages = (array)$this->getDefault();
}
}
} else {
$languages = ['en'];
}
foreach ((array)$languages as $lang) {
$translation_array = (array)$this->config->getLanguages()->get($lang . '.' . $key, null);
if ($translation_array && array_key_exists($index, $translation_array)) {
return $translation_array[$index];
}
}
}
if ($html_out) {
return '<span class="untranslated">' . $key . '[' . $index . ']</span>';
} else {
return $key . '[' . $index . ']';
}
}
/**
* Lookup the translation text for a given lang and key
*
* @param $lang lang code
* @param $key key to lookup with
* @param bool $array_support
*
* @return string
*/
public function getTranslation($lang, $key, $array_support = false)
{
$translation = $this->config->getLanguages()->get($lang . '.' . $key, null);
if (!$array_support && is_array($translation)) {
return (string)array_shift($translation);
}
return $translation;
}
public function getBrowserLanguages($accept_langs = [])
{
if (empty($this->http_accept_language)) {
if (empty($accept_langs) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$accept_langs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
} else {
return $accept_langs;
}
foreach (explode(',', $accept_langs) as $k => $pref) {
// split $pref again by ';q='
// and decorate the language entries by inverted position
if (false !== ($i = strpos($pref, ';q='))) {
$langs[substr($pref, 0, $i)] = array((float)substr($pref, $i + 3), -$k);
} else {
$langs[$pref] = array(1, -$k);
}
}
arsort($langs);
// no need to undecorate, because we're only interested in the keys
$this->http_accept_language = array_keys($langs);
}
return $this->http_accept_language;
}
}

View File

@@ -0,0 +1,770 @@
<?php
namespace Grav\Common\Language;
/**
* Language and translation functionality for Grav
*/
class LanguageCodes
{
protected static $codes = [
"ab" => [
"name" => "Abkhaz",
"nativeName" => "аҧсуа"
],
"aa" => [
"name" => "Afar",
"nativeName" => "Afaraf"
],
"af" => [
"name" => "Afrikaans",
"nativeName" => "Afrikaans"
],
"ak" => [
"name" => "Akan",
"nativeName" => "Akan"
],
"sq" => [
"name" => "Albanian",
"nativeName" => "Shqip"
],
"am" => [
"name" => "Amharic",
"nativeName" => "አማርኛ"
],
"ar" => [
"name" => "Arabic",
"nativeName" => "العربية"
],
"an" => [
"name" => "Aragonese",
"nativeName" => "Aragonés"
],
"hy" => [
"name" => "Armenian",
"nativeName" => "Հայերեն"
],
"as" => [
"name" => "Assamese",
"nativeName" => "অসমীয়া"
],
"av" => [
"name" => "Avaric",
"nativeName" => "авар мацӀ"
],
"ae" => [
"name" => "Avestan",
"nativeName" => "avesta"
],
"ay" => [
"name" => "Aymara",
"nativeName" => "aymar aru"
],
"az" => [
"name" => "Azerbaijani",
"nativeName" => "azərbaycan dili"
],
"bm" => [
"name" => "Bambara",
"nativeName" => "bamanankan"
],
"ba" => [
"name" => "Bashkir",
"nativeName" => "башҡорт теле"
],
"eu" => [
"name" => "Basque",
"nativeName" => "euskara"
],
"be" => [
"name" => "Belarusian",
"nativeName" => "Беларуская"
],
"bn" => [
"name" => "Bengali",
"nativeName" => "বাংলা"
],
"bh" => [
"name" => "Bihari",
"nativeName" => "भोजपुरी"
],
"bi" => [
"name" => "Bislama",
"nativeName" => "Bislama"
],
"bs" => [
"name" => "Bosnian",
"nativeName" => "bosanski jezik"
],
"br" => [
"name" => "Breton",
"nativeName" => "brezhoneg"
],
"bg" => [
"name" => "Bulgarian",
"nativeName" => "български език"
],
"my" => [
"name" => "Burmese",
"nativeName" => "ဗမာစာ"
],
"ca" => [
"name" => "Catalan",
"nativeName" => "Català"
],
"ch" => [
"name" => "Chamorro",
"nativeName" => "Chamoru"
],
"ce" => [
"name" => "Chechen",
"nativeName" => "нохчийн мотт"
],
"ny" => [
"name" => "Chichewa",
"nativeName" => "chiCheŵa"
],
"zh" => [
"name" => "Chinese",
"nativeName" => "中文"
],
"cv" => [
"name" => "Chuvash",
"nativeName" => "чӑваш чӗлхи"
],
"kw" => [
"name" => "Cornish",
"nativeName" => "Kernewek"
],
"co" => [
"name" => "Corsican",
"nativeName" => "corsu"
],
"cr" => [
"name" => "Cree",
"nativeName" => "ᓀᐦᐃᔭᐍᐏᐣ"
],
"hr" => [
"name" => "Croatian",
"nativeName" => "hrvatski"
],
"cs" => [
"name" => "Czech",
"nativeName" => "česky"
],
"da" => [
"name" => "Danish",
"nativeName" => "dansk"
],
"dv" => [
"name" => "Divehi",
"nativeName" => "ދިވެހި"
],
"nl" => [
"name" => "Dutch",
"nativeName" => "Nederlands"
],
"en" => [
"name" => "English",
"nativeName" => "English"
],
"eo" => [
"name" => "Esperanto",
"nativeName" => "Esperanto"
],
"et" => [
"name" => "Estonian",
"nativeName" => "eesti"
],
"ee" => [
"name" => "Ewe",
"nativeName" => "Eʋegbe"
],
"fo" => [
"name" => "Faroese",
"nativeName" => "føroyskt"
],
"fj" => [
"name" => "Fijian",
"nativeName" => "vosa Vakaviti"
],
"fi" => [
"name" => "Finnish",
"nativeName" => "suomi"
],
"fr" => [
"name" => "French",
"nativeName" => "français"
],
"ff" => [
"name" => "Fula",
"nativeName" => "Fulfulde"
],
"gl" => [
"name" => "Galician",
"nativeName" => "Galego"
],
"ka" => [
"name" => "Georgian",
"nativeName" => "ქართული"
],
"de" => [
"name" => "German",
"nativeName" => "Deutsch"
],
"el" => [
"name" => "Greek",
"nativeName" => "Ελληνικά"
],
"gn" => [
"name" => "Guaraní",
"nativeName" => "Avañeẽ"
],
"gu" => [
"name" => "Gujarati",
"nativeName" => "ગુજરાતી"
],
"ht" => [
"name" => "Haitian",
"nativeName" => "Kreyòl ayisyen"
],
"ha" => [
"name" => "Hausa",
"nativeName" => "هَوُسَ"
],
"he" => [
"name" => "Hebrew",
"nativeName" => "עברית"
],
"hz" => [
"name" => "Herero",
"nativeName" => "Otjiherero"
],
"hi" => [
"name" => "Hindi",
"nativeName" => "हिन्दी"
],
"ho" => [
"name" => "Hiri Motu",
"nativeName" => "Hiri Motu"
],
"hu" => [
"name" => "Hungarian",
"nativeName" => "Magyar"
],
"ia" => [
"name" => "Interlingua",
"nativeName" => "Interlingua"
],
"id" => [
"name" => "Indonesian",
"nativeName" => "Bahasa Indonesia"
],
"ie" => [
"name" => "Interlingue",
"nativeName" => "Interlingue"
],
"ga" => [
"name" => "Irish",
"nativeName" => "Gaeilge"
],
"ig" => [
"name" => "Igbo",
"nativeName" => "Asụsụ Igbo"
],
"ik" => [
"name" => "Inupiaq",
"nativeName" => "Iñupiaq"
],
"io" => [
"name" => "Ido",
"nativeName" => "Ido"
],
"is" => [
"name" => "Icelandic",
"nativeName" => "Íslenska"
],
"it" => [
"name" => "Italian",
"nativeName" => "Italiano"
],
"iu" => [
"name" => "Inuktitut",
"nativeName" => "ᐃᓄᒃᑎᑐᑦ"
],
"ja" => [
"name" => "Japanese",
"nativeName" => "日本語"
],
"jv" => [
"name" => "Javanese",
"nativeName" => "basa Jawa"
],
"kl" => [
"name" => "Kalaallisut",
"nativeName" => "kalaallisut"
],
"kn" => [
"name" => "Kannada",
"nativeName" => "ಕನ್ನಡ"
],
"kr" => [
"name" => "Kanuri",
"nativeName" => "Kanuri"
],
"ks" => [
"name" => "Kashmiri",
"nativeName" => "कश्मीरी"
],
"kk" => [
"name" => "Kazakh",
"nativeName" => "Қазақ тілі"
],
"km" => [
"name" => "Khmer",
"nativeName" => "ភាសាខ្មែរ"
],
"ki" => [
"name" => "Kikuyu",
"nativeName" => "Gĩkũyũ"
],
"rw" => [
"name" => "Kinyarwanda",
"nativeName" => "Ikinyarwanda"
],
"ky" => [
"name" => "Kirghiz",
"nativeName" => "кыргыз тили"
],
"kv" => [
"name" => "Komi",
"nativeName" => "коми кыв"
],
"kg" => [
"name" => "Kongo",
"nativeName" => "KiKongo"
],
"ko" => [
"name" => "Korean",
"nativeName" => "한국어"
],
"ku" => [
"name" => "Kurdish",
"nativeName" => "كوردی‎"
],
"kj" => [
"name" => "Kwanyama",
"nativeName" => "Kuanyama"
],
"la" => [
"name" => "Latin",
"nativeName" => "latine"
],
"lb" => [
"name" => "Luxembourgish",
"nativeName" => "Lëtzebuergesch"
],
"lg" => [
"name" => "Luganda",
"nativeName" => "Luganda"
],
"li" => [
"name" => "Limburgish",
"nativeName" => "Limburgs"
],
"ln" => [
"name" => "Lingala",
"nativeName" => "Lingála"
],
"lo" => [
"name" => "Lao",
"nativeName" => "ພາສາລາວ"
],
"lt" => [
"name" => "Lithuanian",
"nativeName" => "lietuvių kalba"
],
"lu" => [
"name" => "Luba-Katanga",
"nativeName" => "Luba-Katanga"
],
"lv" => [
"name" => "Latvian",
"nativeName" => "latviešu valoda"
],
"gv" => [
"name" => "Manx",
"nativeName" => "Gaelg"
],
"mk" => [
"name" => "Macedonian",
"nativeName" => "македонски јазик"
],
"mg" => [
"name" => "Malagasy",
"nativeName" => "Malagasy fiteny"
],
"ms" => [
"name" => "Malay",
"nativeName" => "بهاس ملايو‎"
],
"ml" => [
"name" => "Malayalam",
"nativeName" => "മലയാളം"
],
"mt" => [
"name" => "Maltese",
"nativeName" => "Malti"
],
"mi" => [
"name" => "Māori",
"nativeName" => "te reo Māori"
],
"mr" => [
"name" => "Marathi",
"nativeName" => "मराठी"
],
"mh" => [
"name" => "Marshallese",
"nativeName" => "Kajin M̧ajeļ"
],
"mn" => [
"name" => "Mongolian",
"nativeName" => "монгол"
],
"na" => [
"name" => "Nauru",
"nativeName" => "Ekakairũ Naoero"
],
"nv" => [
"name" => "Navajo",
"nativeName" => "Diné bizaad"
],
"nb" => [
"name" => "Norwegian Bokmål",
"nativeName" => "Norsk bokmål"
],
"nd" => [
"name" => "North Ndebele",
"nativeName" => "isiNdebele"
],
"ne" => [
"name" => "Nepali",
"nativeName" => "नेपाली"
],
"ng" => [
"name" => "Ndonga",
"nativeName" => "Owambo"
],
"nn" => [
"name" => "Norwegian Nynorsk",
"nativeName" => "Norsk nynorsk"
],
"no" => [
"name" => "Norwegian",
"nativeName" => "Norsk"
],
"ii" => [
"name" => "Nuosu",
"nativeName" => "ꆈꌠ꒿ Nuosuhxop"
],
"nr" => [
"name" => "South Ndebele",
"nativeName" => "isiNdebele"
],
"oc" => [
"name" => "Occitan",
"nativeName" => "Occitan"
],
"oj" => [
"name" => "Ojibwe, Ojibwa",
"nativeName" => "ᐊᓂᔑᓈᐯᒧᐎᓐ"
],
"cu" => [
"name" => "Church Slavic",
"nativeName" => "ѩзыкъ словѣньскъ"
],
"om" => [
"name" => "Oromo",
"nativeName" => "Afaan Oromoo"
],
"or" => [
"name" => "Oriya",
"nativeName" => "ଓଡ଼ିଆ"
],
"os" => [
"name" => "Ossetian",
"nativeName" => "ирон æвзаг"
],
"pa" => [
"name" => "Panjabi",
"nativeName" => "ਪੰਜਾਬੀ"
],
"pi" => [
"name" => "Pāli",
"nativeName" => "पाऴि"
],
"fa" => [
"name" => "Persian",
"nativeName" => "فارسی"
],
"pl" => [
"name" => "Polish",
"nativeName" => "polski"
],
"ps" => [
"name" => "Pashto",
"nativeName" => "پښتو"
],
"pt" => [
"name" => "Portuguese",
"nativeName" => "Português"
],
"qu" => [
"name" => "Quechua",
"nativeName" => "Runa Simi"
],
"rm" => [
"name" => "Romansh",
"nativeName" => "rumantsch grischun"
],
"rn" => [
"name" => "Kirundi",
"nativeName" => "kiRundi"
],
"ro" => [
"name" => "Romanian",
"nativeName" => "română"
],
"ru" => [
"name" => "Russian",
"nativeName" => "русский язык"
],
"sa" => [
"name" => "Sanskrit",
"nativeName" => "संस्कृतम्"
],
"sc" => [
"name" => "Sardinian",
"nativeName" => "sardu"
],
"sd" => [
"name" => "Sindhi",
"nativeName" => "सिन्धी"
],
"se" => [
"name" => "Northern Sami",
"nativeName" => "Davvisámegiella"
],
"sm" => [
"name" => "Samoan",
"nativeName" => "gagana faa Samoa"
],
"sg" => [
"name" => "Sango",
"nativeName" => "yângâ tî sängö"
],
"sr" => [
"name" => "Serbian",
"nativeName" => "српски језик"
],
"gd" => [
"name" => "Scottish Gaelic",
"nativeName" => "Gàidhlig"
],
"sn" => [
"name" => "Shona",
"nativeName" => "chiShona"
],
"si" => [
"name" => "Sinhala",
"nativeName" => "සිංහල"
],
"sk" => [
"name" => "Slovak",
"nativeName" => "slovenčina"
],
"sl" => [
"name" => "Slovene",
"nativeName" => "slovenščina"
],
"so" => [
"name" => "Somali",
"nativeName" => "Soomaaliga"
],
"st" => [
"name" => "Southern Sotho",
"nativeName" => "Sesotho"
],
"es" => [
"name" => "Spanish",
"nativeName" => "español"
],
"su" => [
"name" => "Sundanese",
"nativeName" => "Basa Sunda"
],
"sw" => [
"name" => "Swahili",
"nativeName" => "Kiswahili"
],
"ss" => [
"name" => "Swati",
"nativeName" => "SiSwati"
],
"sv" => [
"name" => "Swedish",
"nativeName" => "svenska"
],
"ta" => [
"name" => "Tamil",
"nativeName" => "தமிழ்"
],
"te" => [
"name" => "Telugu",
"nativeName" => "తెలుగు"
],
"tg" => [
"name" => "Tajik",
"nativeName" => "тоҷикӣ"
],
"th" => [
"name" => "Thai",
"nativeName" => "ไทย"
],
"ti" => [
"name" => "Tigrinya",
"nativeName" => "ትግርኛ"
],
"bo" => [
"name" => "Tibetan",
"nativeName" => "བོད་ཡིག"
],
"tk" => [
"name" => "Turkmen",
"nativeName" => "Türkmen"
],
"tl" => [
"name" => "Tagalog",
"nativeName" => "Wikang Tagalog"
],
"tn" => [
"name" => "Tswana",
"nativeName" => "Setswana"
],
"to" => [
"name" => "Tonga",
"nativeName" => "faka Tonga"
],
"tr" => [
"name" => "Turkish",
"nativeName" => "Türkçe"
],
"ts" => [
"name" => "Tsonga",
"nativeName" => "Xitsonga"
],
"tt" => [
"name" => "Tatar",
"nativeName" => "татарча"
],
"tw" => [
"name" => "Twi",
"nativeName" => "Twi"
],
"ty" => [
"name" => "Tahitian",
"nativeName" => "Reo Tahiti"
],
"ug" => [
"name" => "Uighur",
"nativeName" => "Uyƣurqə"
],
"uk" => [
"name" => "Ukrainian",
"nativeName" => "українська"
],
"ur" => [
"name" => "Urdu",
"nativeName" => "اردو"
],
"uz" => [
"name" => "Uzbek",
"nativeName" => "zbek"
],
"ve" => [
"name" => "Venda",
"nativeName" => "Tshivenḓa"
],
"vi" => [
"name" => "Vietnamese",
"nativeName" => "Tiếng Việt"
],
"vo" => [
"name" => "Volapük",
"nativeName" => "Volapük"
],
"wa" => [
"name" => "Walloon",
"nativeName" => "Walon"
],
"cy" => [
"name" => "Welsh",
"nativeName" => "Cymraeg"
],
"wo" => [
"name" => "Wolof",
"nativeName" => "Wollof"
],
"fy" => [
"name" => "Western Frisian",
"nativeName" => "Frysk"
],
"xh" => [
"name" => "Xhosa",
"nativeName" => "isiXhosa"
],
"yi" => [
"name" => "Yiddish",
"nativeName" => "ייִדיש"
],
"yo" => [
"name" => "Yoruba",
"nativeName" => "Yorùbá"
],
"za" => [
"name" => "Zhuang",
"nativeName" => "Saɯ cueŋƅ"
]
];
public static function getName($code)
{
return static::get($code, 'name');
}
public static function getNativeName($code)
{
return static::get($code, 'nativeName');
}
public static function getNames(array $keys)
{
$results = [];
foreach ($keys as $key) {
if (isset(static::$codes[$key])) {
$results[$key] = static::$codes[$key];
}
}
return $results;
}
protected static function get($code, $type)
{
if (isset(static::$codes[$code][$type])) {
return static::$codes[$code][$type];
} else {
return false;
}
}
}

View File

@@ -5,9 +5,9 @@ class Parsedown extends \Parsedown
{
use ParsedownGravTrait;
public function __construct($page)
public function __construct($page, $defaults)
{
$this->init($page);
$this->init($page, $defaults);
}
}

View File

@@ -5,9 +5,9 @@ class ParsedownExtra extends \ParsedownExtra
{
use ParsedownGravTrait;
public function __construct($page)
public function __construct($page, $defaults)
{
parent::__construct();
$this->init($page);
$this->init($page, $defaults);
}
}

View File

@@ -6,6 +6,7 @@ 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
@@ -22,11 +23,12 @@ trait ParsedownGravTrait
protected $twig_link_regex = '/\!*\[(?:.*)\]\((\{([\{%#])\s*(.*?)\s*(?:\2|\})\})\)/';
/**
* Initialiazation function to setup key variables needed by the MarkdownGravLinkTrait
* Initialization function to setup key variables needed by the MarkdownGravLinkTrait
*
* @param $page
* @param $defaults
*/
protected function init($page)
protected function init($page, $defaults)
{
$this->page = $page;
$this->pages = self::getGrav()['pages'];
@@ -35,7 +37,9 @@ trait ParsedownGravTrait
$this->pages_dir = self::getGrav()['locator']->findResource('page://');
$this->special_chars = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
$defaults = self::getGrav()['config']->get('system.pages.markdown');
if ($defaults === null) {
$defaults = self::getGrav()['config']->get('system.pages.markdown');
}
$this->setBreaksEnabled($defaults['auto_line_breaks']);
$this->setUrlsLinked($defaults['auto_url_links']);
@@ -116,7 +120,6 @@ trait ParsedownGravTrait
// if this is an image
if (isset($excerpt['element']['attributes']['src'])) {
$alt = $excerpt['element']['attributes']['alt'] ?: '';
$title = $excerpt['element']['attributes']['title'] ?: '';
$class = isset($excerpt['element']['attributes']['class']) ? $excerpt['element']['attributes']['class'] : '';
@@ -124,19 +127,16 @@ trait ParsedownGravTrait
//get the url and parse it
$url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['src']));
$path_parts = pathinfo($url['path']);
// if there is no host set but there is a path, the file is local
if (!isset($url['host']) && isset($url['path'])) {
$path_parts = pathinfo($url['path']);
// get the local path to page media if possible
if ($path_parts['dirname'] == $this->page->url()) {
$url['path'] = ltrim(str_replace($this->page->url(), '', $url['path']), '/');
$url['path'] = $path_parts['basename'];
// get the media objects for this page
$media = $this->page->media();
} else {
// see if this is an external page to this one
$page_route = str_replace($this->base_url, '', $path_parts['dirname']);
@@ -204,52 +204,10 @@ trait ParsedownGravTrait
// 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'] = $this->convertUrl(Uri::buildUrl($url));
$excerpt['element']['attributes']['href'] = Uri::convertUrl($this->page, Uri::buildUrl($url));
}
}
return $excerpt;
}
/**
* Converts links from absolute '/' or relative (../..) to a grav friendly format
* @param string $markdown_url the URL as it was written in the markdown
* @return string the more friendly formatted url
*/
protected function convertUrl($markdown_url)
{
// if absolute and starts with a base_url move on
if ($this->base_url != '' && strpos($markdown_url, $this->base_url) === 0) {
return $markdown_url;
// if its absolute and starts with /
} elseif (strpos($markdown_url, '/') === 0) {
return $this->base_url . $markdown_url;
} else {
$relative_path = $this->base_url . $this->page->route();
$real_path = $this->page->path() . '/' . parse_url($markdown_url, PHP_URL_PATH);
// strip numeric order from markdown path
if (($real_path)) {
$markdown_url = preg_replace('/^([\d]+\.)/', '', preg_replace('/\/([\d]+\.)/', '/', trim(preg_replace('/[^\/]+(\.md$)/', '', $markdown_url), '/')));
}
// else its a relative path already
$newpath = array();
$paths = explode('/', $markdown_url);
// remove the updirectory references (..)
foreach ($paths as $path) {
if ($path == '..') {
$relative_path = dirname($relative_path);
} else {
$newpath[] = $path;
}
}
// build the new url
$new_url = rtrim($relative_path, '/') . '/' . implode('/', $newpath);
}
return $new_url;
}
}

View File

@@ -361,12 +361,13 @@ class Collection extends Iterator
{
$routable = [];
foreach (array_keys($this->items) as $path => $slug) {
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page->routable()) {
$routable[$path] = $slug;
}
}
$this->items = $routable;
return $this;
}

View File

@@ -40,14 +40,14 @@ class Media extends Getters
$this->path = $path;
$iterator = new \DirectoryIterator($path);
$iterator = new \FilesystemIterator($path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$media = [];
/** @var \DirectoryIterator $info */
foreach ($iterator as $info) {
foreach ($iterator as $path => $info) {
// Ignore folders and Markdown files.
if ($info->isDot() || !$info->isFile() || $info->getExtension() == 'md') {
if (!$info->isFile() || $info->getExtension() == 'md' || $info->getBasename() === '.DS_Store') {
continue;
}
@@ -58,27 +58,36 @@ class Media extends Getters
if ($type === 'alternative') {
$media["{$basename}.{$ext}"][$type] = isset($media["{$basename}.{$ext}"][$type]) ? $media["{$basename}.{$ext}"][$type] : [];
$media["{$basename}.{$ext}"][$type][$extra] = $info->getPathname();
$media["{$basename}.{$ext}"][$type][$extra] = [ 'file' => $path, 'size' => $info->getSize() ];
} else {
$media["{$basename}.{$ext}"][$type] = $info->getPathname();
$media["{$basename}.{$ext}"][$type] = [ 'file' => $path, 'size' => $info->getSize() ];
}
}
foreach ($media as $name => $types) {
// First prepare the alternatives in case there is no base medium
if (!empty($types['alternative'])) {
foreach ($types['alternative'] as $ratio => &$file) {
$file = MediumFactory::fromFile($file);
foreach ($types['alternative'] as $ratio => &$alt) {
$alt['file'] = MediumFactory::fromFile($alt['file']);
if (!$alt['file']) {
unset($types['alternative'][$ratio]);
} else {
$alt['file']->set('size', $alt['size']);
}
}
}
// Create the base medium
if (!empty($types['base'])) {
$medium = MediumFactory::fromFile($types['base']);
$medium = MediumFactory::fromFile($types['base']['file']);
$medium && $medium->set('size', $types['base']['size']);
} else if (!empty($types['alternative'])) {
$altMedium = reset($types['alternative']);
$ratio = key($types['alternative']);
$altMedium = $altMedium['file'];
$medium = MediumFactory::scaledFromMedium($altMedium, $ratio, 1);
}
@@ -87,13 +96,13 @@ class Media extends Getters
}
if (!empty($types['meta'])) {
$medium->addMetaFile($types['meta']);
$medium->addMetaFile($types['meta']['file']);
}
if (!empty($types['thumb'])) {
// We will not turn it into medium yet because user might never request the thumbnail
// not wasting any resources on that, maybe we should do this for medium in general?
$medium->set('thumbnails.page', $types['thumb']);
$medium->set('thumbnails.page', $types['thumb']['file']);
}
// Build missing alternatives
@@ -111,7 +120,7 @@ class Media extends Getters
}
foreach ($types['alternative'] as $ratio => $altMedium) {
$medium->addAlternative($ratio, $altMedium);
$medium->addAlternative($ratio, $altMedium['file']);
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Grav\Common\Page\Medium;
use Grav\Common\GravTrait;
use Gregwar\Image\Exceptions\GenerationError;
use RocketTheme\Toolbox\Event\Event;
class ImageFile extends \Gregwar\Image\Image
{
use GravTrait;
/**
* This is the same as the Gregwar Image class except this one fires a Grav Event on creation of new cached file
*
* @param string $type the image type
* @param int $quality the quality (for JPEG)
* @param bool $actual
*
* @return mixed|string
*/
public function cacheFile($type = 'jpg', $quality = 80, $actual = false)
{
if ($type == 'guess') {
$type = $this->guessType();
}
if (!count($this->operations) && $type == $this->guessType() && !$this->forceCache) {
return $this->getFilename($this->getFilePath());
}
// Computes the hash
$this->hash = $this->getHash($type, $quality);
// Generates the cache file
$cacheFile = '';
if (!$this->prettyName || $this->prettyPrefix) {
$cacheFile .= $this->hash;
}
if ($this->prettyPrefix) {
$cacheFile .= '-';
}
if ($this->prettyName) {
$cacheFile .= $this->prettyName;
}
$cacheFile .= '.'.$type;
// If the files does not exists, save it
$image = $this;
// Target file should be younger than all the current image
// dependencies
$conditions = array(
'younger-than' => $this->getDependencies()
);
// The generating function
$generate = function ($target) use ($image, $type, $quality) {
$result = $image->save($target, $type, $quality);
if ($result != $target) {
throw new GenerationError($result);
}
self::getGrav()->fireEvent('onImageMediumSaved', new Event(['image' => $target]));
};
// Asking the cache for the cacheFile
try {
$file = $this->cache->getOrCreateFile($cacheFile, $conditions, $generate, $actual);
} catch (GenerationError $e) {
$file = $e->getNewFile();
}
if ($actual) {
return $file;
} else {
return $this->getFilename($file);
}
}
}

View File

@@ -2,7 +2,6 @@
namespace Grav\Common\Page\Medium;
use Grav\Common\Data\Blueprint;
use Gregwar\Image\Image as ImageFile;
class ImageMedium extends Medium
{
@@ -42,7 +41,7 @@ class ImageMedium extends Medium
public static $magic_actions = [
'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop',
'negate', 'brightness', 'contrast', 'grayscale', 'emboss',
'smooth', 'sharp', 'edge', 'colorize', 'sepia'
'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive'
];
/**
@@ -67,17 +66,23 @@ class ImageMedium extends Medium
{
parent::__construct($items, $blueprint);
$config = self::$grav['config'];
$image_info = getimagesize($this->get('filepath'));
$this->def('width', $image_info[0]);
$this->def('height', $image_info[1]);
$this->def('mime', $image_info['mime']);
$this->def('debug', self::$grav['config']->get('system.images.debug'));
$this->def('debug', $config->get('system.images.debug'));
$this->set('thumbnails.media', $this->get('filepath'));
$this->default_quality = self::$grav['config']->get('system.images.default_image_quality', 85);
$this->default_quality = $config->get('system.images.default_image_quality', 85);
$this->reset();
if ($config->get('system.images.cache_all', false)) {
$this->cache();
}
}
/**
@@ -127,7 +132,20 @@ class ImageMedium extends Medium
$this->reset();
}
return self::$grav['base_url'] . $output . $this->urlHash();
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
}
/**
* Simply processes with no extra methods. Useful for triggering events.
*
* @return $this
*/
public function cache()
{
if (!$this->image) {
$this->image();
}
return $this;
}
@@ -216,7 +234,7 @@ class ImageMedium extends Medium
}
/**
* Turn the current Medium inta a Link with lightbox enabled
* Turn the current Medium into a Link with lightbox enabled
*
* @param int $width
* @param int $height
@@ -299,7 +317,7 @@ class ImageMedium extends Medium
}
if (!in_array($method, self::$magic_actions)) {
return $this;
return parent::__call($method, $args);
}
// Always initialize image.
@@ -308,7 +326,7 @@ class ImageMedium extends Medium
}
try {
$result = call_user_func_array([$this->image, $method], $args);
call_user_func_array([$this->image, $method], $args);
foreach ($this->alternatives as $ratio => $medium) {
$args_copy = $args;
@@ -364,6 +382,10 @@ class ImageMedium extends Medium
return parent::path(false);
}
if (isset($this->result)) {
return $this->result;
}
if ($this->get('debug') && !$this->debug_watermarked) {
$ratio = $this->get('ratio');
if (!$ratio) {
@@ -375,9 +397,7 @@ class ImageMedium extends Medium
$this->image->merge(ImageFile::open($overlay));
}
$result = $this->image->cacheFile($this->format, $this->quality);
return $result;
return $this->image->cacheFile($this->format, $this->quality);
}
/**

View File

@@ -60,6 +60,6 @@ class Link implements RenderableInterface
// Don't start nesting links, if user has multiple link calls in his
// actions, we will drop the previous links.
return $this->source instanceof LinkMedium ? $this->source : $this;
return $this->source instanceof Link ? $this->source : $this;
}
}

View File

@@ -60,6 +60,10 @@ class Medium extends Data implements RenderableInterface
{
parent::__construct($items, $blueprint);
if (self::getGrav()['config']->get('system.media.enable_media_timestamp', true)) {
$this->querystring('&' . self::getGrav()['cache']->getKey());
}
$this->def('mime', 'application/octet-stream');
$this->reset();
}
@@ -129,7 +133,33 @@ class Medium extends Data implements RenderableInterface
$this->reset();
}
return self::$grav['base_url'] . $output . $this->urlHash();
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
}
/**
* Get/set querystring for the file's url
*
* @param string $hash
* @param boolean $withHash
* @return string
*/
public function querystring($querystring = null, $withQuestionmark = true)
{
if ($querystring) {
$this->set('querystring', ltrim($querystring, '?&'));
foreach ($this->alternatives as $alt) {
$alt->querystring($querystring, $withQuestionmark);
}
}
$querystring = $this->get('querystring', '');
if ($withQuestionmark && !empty($querystring)) {
return '?' . $querystring;
} else {
return $querystring;
}
}
/**
@@ -309,7 +339,7 @@ class Medium extends Data implements RenderableInterface
}
/**
* Turn the current Medium inta a Link with lightbox enabled
* Turn the current Medium into a Link with lightbox enabled
*
* @param int $width
* @param int $height
@@ -337,6 +367,17 @@ class Medium extends Data implements RenderableInterface
*/
public function __call($method, $args)
{
$qs = $method;
if (count($args) > 1 || (count($args) == 1 && !empty($args[0]))) {
$qs .= '=' . implode(',', array_map(function ($a) { return urlencode($a); }, $args));
}
if (!empty($qs)) {
$this->querystring($this->querystring(null, false) . '&' . $qs);
}
self::$grav['debugger']->addMessage($this->querystring());
return $this;
}
@@ -358,7 +399,7 @@ class Medium extends Data implements RenderableInterface
$thumb = $this->get('thumbnails.' . $type, false);
if ($thumb) {
$thumb = $thumb instanceof ThumbnailMedium ? $thumb : MediumFactory::fromFile($thumb, ['type' => 'thumbnail']);
$thumb = $thumb instanceof ThumbnailImageMedium ? $thumb : MediumFactory::fromFile($thumb, ['type' => 'thumbnail']);
$thumb->parent = $this;
}

View File

@@ -131,8 +131,7 @@ class MediumFactory
$debug = $medium->get('debug');
$medium->set('debug', false);
$file = $medium->resize($width, $height)->setPrettyName($basename)->url();
$file = preg_replace('|'. preg_quote(self::getGrav()['base_url_relative']) .'$|', '', GRAV_ROOT) . $file;
$file = $medium->resize($width, $height)->path();
$medium->set('debug', $debug);

View File

@@ -23,7 +23,7 @@ trait ParsedownHtmlTrait
$element = $this->parsedownElement($title, $alt, $class, $reset);
if (!$this->parsedown) {
$this->parsedown = new Parsedown(null);
$this->parsedown = new Parsedown(null, null);
}
return $this->parsedown->elementToHtml($element);

View File

@@ -90,7 +90,7 @@ class ThumbnailImageMedium extends ImageMedium
}
/**
* Turn the current Medium inta a Link with lightbox enabled
* Turn the current Medium into a Link with lightbox enabled
*
* @param int $width
* @param int $height

View File

@@ -7,7 +7,6 @@ use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Data;
use Gregwar\Image\Image as ImageFile;
class VideoMedium extends Medium
{

View File

@@ -34,32 +34,31 @@ class Page
* @var string Filename. Leave as null if page is folder.
*/
protected $name;
/**
* @var string Folder name.
*/
protected $folder;
/**
* @var string Path to the folder. Add $this->folder to get full path.
*/
protected $path;
protected $extension;
protected $parent;
protected $template;
protected $expires;
protected $visible;
protected $published;
protected $publish_date;
protected $unpublish_date;
protected $slug;
protected $route;
protected $raw_route;
protected $url;
protected $routes;
protected $routable;
protected $modified;
protected $id;
protected $items;
protected $header;
protected $frontmatter;
protected $language;
protected $content;
protected $summary;
protected $raw_content;
protected $pagination;
protected $media;
@@ -77,6 +76,8 @@ class Page
protected $process;
protected $summary_size;
protected $markdown_extra;
protected $etag;
protected $last_modified;
/**
* @var Page Unmodified (original) version of the page. Used for copying and moving the page.
@@ -106,9 +107,9 @@ class Page
* Initializes the page instance variables based on a file
*
* @param \SplFileInfo $file The file information for the .md file that the page represents
* @return void
* @param string $extension
*/
public function init(\SplFileInfo $file)
public function init(\SplFileInfo $file, $extension = null)
{
$this->filePath($file->getPathName());
$this->modified($file->getMTime());
@@ -116,28 +117,21 @@ class Page
$this->header();
$this->date();
$this->metadata();
$this->slug();
$this->url();
$this->visible();
$this->modularTwig($this->slug[0] == '_');
// Handle publishing dates if no explict published option set
if (self::getGrav()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) {
// unpublish if required, if not clear cache right before page should be unpublished
if ($this->unpublishDate()) {
if ($this->unpublishDate() < time()) {
$this->published(false);
} else {
$this->published();
self::getGrav()['cache']->setLifeTime($this->unpublishDate());
}
}
// publish if required, if not clear cache right before page is published
if ($this->publishDate() != $this->modified() && $this->publishDate() > time()) {
$this->published(false);
self::getGrav()['cache']->setLifeTime($this->publishDate());
}
}
$this->setPublishState();
$this->published();
if (empty($extension)) {
$this->extension('.'.$file->getExtension());
} else {
$this->extension($extension);
}
// Exract page language from page extension
$language = trim(basename($this->extension(), 'md'), '.') ?: null;
$this->language($language);
}
/**
@@ -161,6 +155,7 @@ class Page
$this->id($this->modified().md5($this->filePath()));
$this->header = null;
$this->content = null;
$this->summary = null;
}
return $file ? $file->raw() : '';
}
@@ -221,9 +216,15 @@ class Page
if (isset($this->header->slug)) {
$this->slug = trim($this->header->slug);
}
if (isset($this->header->routes)) {
$this->routes = (array)($this->header->routes);
}
if (isset($this->header->title)) {
$this->title = trim($this->header->title);
}
if (isset($this->header->language)) {
$this->language = trim($this->header->language);
}
if (isset($this->header->template)) {
$this->template = trim($this->header->template);
}
@@ -273,11 +274,37 @@ class Page
if (isset($this->header->unpublish_date)) {
$this->unpublish_date = strtotime($this->header->unpublish_date);
}
if (isset($this->header->expires)) {
$this->expires = intval($this->header->expires);
}
if (isset($this->header->etag)) {
$this->etag = (bool)$this->header->etag;
}
if (isset($this->header->last_modified)) {
$this->last_modified = (bool)$this->header->last_modified;
}
}
return $this->header;
}
/**
* Get page language
*
* @param $var
*
* @return mixed
*/
public function language($var = null)
{
if ($var !== null) {
$this->language = $var;
}
return $this->language;
}
/**
* Modify a header value directly
*
@@ -292,32 +319,43 @@ class Page
/**
* Get the summary.
*
* @param int $size Max summary size.
* @param int $size Max summary size.
* @return string
*/
public function summary($size = null)
{
/** @var Config $config */
$config = self::getGrav()['config'];
$content = $this->content();
$config = self::getGrav()['config']->get('site.summary');
if (isset($this->header->summary)) {
$config = array_merge($config, $this->header->summary);
}
// Return summary based on settings in site config file
if (!$config->get('site.summary.enabled', true)) {
if (!$config['enabled']) {
return $this->content();
}
// Set up variables to process summary from page or from custom summary
if ($this->summary === null) {
$content = $this->content();
$summary_size = $this->summary_size;
} else {
$content = $this->summary;
$summary_size = mb_strlen($this->summary);
}
// Return calculated summary based on summary divider's position
$format = $config['format'];
// Return entire page content on wrong/ unknown format
if (!in_array($format, array('short', 'long'))) {
return $content;
} elseif (($format === 'short') && isset($summary_size)) {
return mb_substr($content, 0, $summary_size);
}
// Get summary size from site config's file
if (is_null($size)) {
$size = $config->get('site.summary.size', null);
}
// Return calculated summary based on summary divider's position
$format = $config->get('site.summary.format', 'short');
// Return entire page content on wrong/ unknown format
if (!in_array($format, array('short', 'long'))) {
return $content;
} elseif (($format === 'short') && isset($this->summary_size)) {
return substr($content, 0, $this->summary_size);
$size = $config['size'];
}
// If the size is zero, return the entire page content
@@ -331,6 +369,16 @@ class Page
return Utils::truncateHTML($content, $size);
}
/**
* Sets the summary of the page
*
* @param string $var Summary
*/
public function setSummary($summary)
{
$this->summary = $summary;
}
/**
* Gets and Sets the content based on content portion of the .md file
*
@@ -352,7 +400,6 @@ class Page
$this->id(time().md5($this->filePath()));
$this->content = null;
}
// If no content, process it
if ($this->content === null) {
// Get media
@@ -411,7 +458,7 @@ class Page
// Handle summary divider
$delimiter = self::getGrav()['config']->get('site.summary.delimiter', '===');
$divider_pos = strpos($this->content, "<p>{$delimiter}</p>");
$divider_pos = mb_strpos($this->content, "<p>{$delimiter}</p>");
if ($divider_pos !== false) {
$this->summary_size = $divider_pos;
$this->content = str_replace("<p>{$delimiter}</p>", '', $this->content);
@@ -442,9 +489,9 @@ class Page
// Initialize the preferred variant of Parsedown
if ($defaults['extra']) {
$parsedown = new ParsedownExtra($this);
$parsedown = new ParsedownExtra($this, $defaults);
} else {
$parsedown = new Parsedown($this);
$parsedown = new Parsedown($this, $defaults);
}
$this->content = $parsedown->text($this->content);
@@ -516,7 +563,7 @@ class Page
return preg_replace($regex, '', $this->folder);
}
if ($name == 'type') {
return basename($this->name(), '.md');
return $this->template();
}
if ($name == 'media') {
return $this->media()->all();
@@ -559,6 +606,15 @@ class Page
return $default;
}
public function rawMarkdown($var = null)
{
if ($var !== null) {
$this->raw_content = $var;
}
return $this->raw_content;
}
/**
* Get file object to the page.
*
@@ -610,9 +666,12 @@ class Page
if ($parent->path()) {
$clone->path($parent->path() . '/' . $clone->folder());
}
// TODO: make sure we always have the route.
if ($parent->route()) {
$clone->route($parent->route() . '/'. $clone->slug());
} else {
$clone->route(self::getGrav()['pages']->root()->route() . '/'. $clone->slug());
}
return $clone;
@@ -645,7 +704,33 @@ class Page
/** @var Pages $pages */
$pages = self::getGrav()['pages'];
return $pages->blueprints($this->template());
$blueprint = $pages->blueprints($this->blueprintName());
$fields = $blueprint->fields();
// override if you only want 'normal' mode
if (empty($fields) && self::getGrav()['admin'] && self::getGrav()['config']->get('plugins.admin.edit_mode', 'auto') == 'normal') {
$blueprint = $pages->blueprints('default');
}
// override if you only want 'expert' mode
if (!empty($fields) && self::getGrav()['admin'] && self::getGrav()['config']->get('plugins.admin.edit_mode', 'auto') == 'expert') {
$blueprint = $pages->blueprints('');
}
return $blueprint;
}
/**
* Get the blueprint name for this page. Use the blueprint form field if set
*
* @return string
*/
public function blueprintName()
{
$blueprint_name = filter_input(INPUT_POST, 'blueprint', FILTER_SANITIZE_STRING) ?: $this->template();
return $blueprint_name;
}
/**
@@ -677,7 +762,7 @@ class Page
public function extra()
{
$blueprints = $this->blueprints();
return $blueprints->extra($this->toArray(), 'header.');
return $blueprints->extra($this->toArray()['header'], 'header.');
}
/**
@@ -776,11 +861,42 @@ class Page
$this->template = $var;
}
if (empty($this->template)) {
$this->template = ($this->modular() ? 'modular/' : '') . str_replace(CONTENT_EXT, '', $this->name());
$this->template = ($this->modular() ? 'modular/' : '') . str_replace($this->extension(), '', $this->name());
}
return $this->template;
}
/**
* Gets and sets the extension field.
*
* @param null $var
* @return null|string
*/
public function extension($var = null)
{
if ($var !== null) {
$this->extension = $var;
}
if (empty($this->extension)) {
$this->extension = '.' . pathinfo($this->name(), PATHINFO_EXTENSION);
}
return $this->extension;
}
/**
* Gets and sets the expires field. If not set will return the default
*
* @param string $var The name of this page.
* @return string The name of this page.
*/
public function expires($var = null)
{
if ($var !== null) {
$this->expires = $var;
}
return empty($this->expires) ? self::getGrav()['config']->get('system.pages.expires') : $this->expires;
}
/**
* Gets and sets the title for this Page. If no title is set, it will use the slug() to get a name
*
@@ -897,7 +1013,8 @@ class Page
/**
* Gets and Sets whether or not this Page is routable, ie you can reach it
* via a URL
* via a URL.
* The page must be *routable* and *published*
*
* @param bool $var true if the page is routable
* @return bool true if the page is routable
@@ -907,7 +1024,7 @@ class Page
if ($var !== null) {
$this->routable = (bool) $var;
}
return $this->routable;
return $this->routable && $this->published();
}
/**
@@ -941,38 +1058,34 @@ class Page
// if not metadata yet, process it.
if (null === $this->metadata) {
$header_tag_http_equivs = ['content-type', 'default-style', 'refresh'];
$this->metadata = array();
$page_header = $this->header;
$this->metadata = [];
// Set the Generator tag
$this->metadata['generator'] = array('name'=>'generator', 'content'=>'Grav ' . GRAV_VERSION);
$this->metadata['generator'] = array('name'=>'generator', 'content'=>'GravCMS ' . GRAV_VERSION);
// Safety check to ensure we have a header
if ($page_header) {
// Get initial metadata for the page
$metadata = self::getGrav()['config']->get('site.metadata');
if (isset($this->header->metadata)) {
// Merge any site.metadata settings in with page metadata
$defaults = (array) self::getGrav()['config']->get('site.metadata');
$metadata = array_merge($metadata, $this->header->metadata);
}
if (isset($page_header->metadata)) {
$page_header->metadata = array_merge($defaults, $page_header->metadata);
// Build an array of meta objects..
foreach ((array)$metadata as $key => $value) {
// If this is a property type metadata: "og", "twitter", "facebook" etc
if (is_array($value)) {
foreach ($value as $property => $prop_value) {
$prop_key = $key.":".$property;
$this->metadata[$prop_key] = array('property'=>$prop_key, 'content'=>htmlspecialchars($prop_value, ENT_QUOTES));
}
// If it this is a standard meta data type
} else {
$page_header->metadata = $defaults;
}
// Build an array of meta objects..
foreach ((array)$page_header->metadata as $key => $value) {
// If this is a property type metadata: "og", "twitter", "facebook" etc
if (is_array($value)) {
foreach ($value as $property => $prop_value) {
$prop_key = $key.":".$property;
$this->metadata[$prop_key] = array('property'=>$prop_key, 'content'=>htmlspecialchars($prop_value, ENT_QUOTES));
}
// If it this is a standard meta data type
if (in_array($key, $header_tag_http_equivs)) {
$this->metadata[$key] = array('http_equiv'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES));
} else {
if (in_array($key, $header_tag_http_equivs)) {
$this->metadata[$key] = array('http_equiv'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES));
} else {
$this->metadata[$key] = array('name'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES));
}
$this->metadata[$key] = array('name'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES));
}
}
}
@@ -992,16 +1105,13 @@ class Page
{
if ($var !== null) {
$this->slug = $var;
$baseRoute = $this->parent ? (string) $this->parent()->route() : null;
$this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug : null;
}
if (empty($this->slug)) {
$regex = '/^[0-9]+\./u';
$this->slug = preg_replace($regex, '', $this->folder);
$baseRoute = $this->parent ? (string) $this->parent()->route() : null;
$this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug : null;
}
return $this->slug;
}
@@ -1046,19 +1156,36 @@ class Page
/**
* Gets the url for the Page.
*
* @param bool $include_host Defaults false, but true would include http://yourhost.com
* @return string The url.
* @param bool $include_host Defaults false, but true would include http://yourhost.com
* @param bool $canonical true to return the canonical URL
*
* @return string The url.
*/
public function url($include_host = false)
public function url($include_host = false, $canonical = false)
{
/** @var Pages $pages */
$pages = self::getGrav()['pages'];
/** @var Language $language */
$language = self::getGrav()['language'];
// get pre-route
$pre_route = $language->enabled() && $language->getActive() ? '/'.$language->getActive() : '';
// get canonical route if requested
if ($canonical) {
$route = $pre_route . $this->routeCanonical();
} else {
$route = $pre_route . $this->route();
}
/** @var Uri $uri */
$uri = self::getGrav()['uri'];
$rootUrl = $uri->rootUrl($include_host) . $pages->base();
$url = $rootUrl.'/'.trim($this->route(), '/');
$url = $rootUrl.'/'.trim($route, '/');
// trim trailing / if not root
if ($url !== '/') {
@@ -1069,7 +1196,8 @@ class Page
}
/**
* Gets the route for the page based on the parents route and the current Page's slug.
* Gets the route for the page based on the route headers if available, else from
* the parents route and the current Page's slug.
*
* @param string $var Set new default route.
*
@@ -1080,9 +1208,90 @@ class Page
if ($var !== null) {
$this->route = $var;
}
if (empty($this->route)) {
// calculate route based on parent slugs
$baseRoute = $this->parent ? (string) $this->parent()->route() : null;
$this->route = isset($baseRoute) ? $baseRoute . '/'. $this->slug() : null;
if (!empty($this->routes) && isset($this->routes['default'])) {
$this->routes['aliases'][] = $this->route;
$this->route = $this->routes['default'];
return $this->route;
}
}
return $this->route;
}
/**
* Helper method to clear the route out so it regenerates next time you use it
*/
public function unsetRoute()
{
unset($this->route);
}
public function rawRoute($var = null)
{
if ($var !== null) {
$this->raw_route = $var;
}
if (empty($this->raw_route)) {
$baseRoute = $this->parent ? (string) $this->parent()->rawRoute() : null;
$regex = '/^[0-9]+\./u';
$slug = preg_replace($regex, '', $this->folder);
$this->raw_route = isset($baseRoute) ? $baseRoute . '/'. $slug : null;
}
return $this->raw_route;
}
/**
* Gets the route aliases for the page based on page headers.
*
* @param array $var list of route aliases
*
* @return array The route aliases for the Page.
*/
public function routeAliases($var = null)
{
if ($var !== null) {
$this->routes['aliases'] = (array) $var;
}
if (!empty($this->routes) && isset($this->routes['aliases'])) {
return $this->routes['aliases'];
} else {
return [];
}
}
/**
* Gets the canonical route for this page if its set. If provided it will use
* that value, else if it's `true` it will use the default route.
*
* @param null $var
*
* @return bool|string
*/
public function routeCanonical($var = null)
{
if ($var !== null) {
$this->routes['canonical'] = (array)$var;
}
if (!empty($this->routes) && isset($this->routes['canonical'])) {
return $this->routes['canonical'];
}
return $this->route();
}
/**
* Gets and sets the identifier for this Page object.
*
@@ -1111,6 +1320,40 @@ class Page
return $this->modified;
}
/**
* Gets and sets the option to show the etag header for the page.
*
* @param boolean $var show etag header
* @return boolean show etag header
*/
public function eTag($var = null)
{
if ($var !== null) {
$this->etag = $var;
}
if (!isset($this->etag)) {
$this->etag = (bool) self::getGrav()['config']->get('system.pages.etag');
}
return $this->etag;
}
/**
* Gets and sets the option to show the last_modified header for the page.
*
* @param boolean $var show last_modified header
* @return boolean show last_modified header
*/
public function lastModified($var = null)
{
if ($var !== null) {
$this->last_modified = $var;
}
if (!isset($this->last_modified)) {
$this->last_modified = (bool) self::getGrav()['config']->get('system.pages.last_modified');
}
return $this->last_modified;
}
/**
* Gets and sets the path to the .md file for this Page object.
*
@@ -1408,10 +1651,14 @@ class Page
*/
public function active()
{
/** @var Uri $uri */
$uri = self::getGrav()['uri'];
if ($this->url() == $uri->url()) {
return true;
$uri_path = self::getGrav()['uri']->path();
$routes = self::getGrav()['pages']->routes();
if (isset($routes[$uri_path])) {
if ($routes[$uri_path] == $this->path()) {
return true;
}
}
return false;
}
@@ -1424,20 +1671,18 @@ class Page
*/
public function activeChild()
{
/** @var Uri $uri */
$uri = self::getGrav()['uri'];
$config = self::getGrav()['config'];
$pages = self::getGrav()['pages'];
$uri_path = $uri->path();
$routes = self::getGrav()['pages']->routes();
// Special check when item is home
if ($this->home()) {
$paths = $uri->paths();
$home = ltrim($config->get('system.home.alias'), '/');
if (isset($paths[0]) && $paths[0] == $home) {
return true;
}
} else {
if (strpos($uri->url(), $this->url()) === 0) {
return true;
if (isset($routes[$uri_path])) {
$child_page = $pages->dispatch($uri->route())->parent();
while (!$child_page->root()) {
if ($this->path() == $child_page->path()) {
return true;
}
$child_page = $child_page->parent();
}
}
@@ -1642,7 +1887,6 @@ class Page
return $results;
}
/**
* Returns whether or not this Page object has a .md file associated with it or if its just a directory.
*
@@ -1699,7 +1943,7 @@ class Page
*/
protected function doRelocation($reorder)
{
if (empty($this->_original)) {
if (empty($this->_original) ) {
return;
}
@@ -1766,5 +2010,24 @@ class Page
$this->_original = null;
}
protected function setPublishState()
{
// Handle publishing dates if no explict published option set
if (self::getGrav()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) {
// unpublish if required, if not clear cache right before page should be unpublished
if ($this->unpublishDate()) {
if ($this->unpublishDate() < time()) {
$this->published(false);
} else {
$this->published();
self::getGrav()['cache']->setLifeTime($this->unpublishDate());
}
}
// publish if required, if not clear cache right before page is published
if ($this->publishDate() != $this->modified() && $this->publishDate() > time()) {
$this->published(false);
self::getGrav()['cache']->setLifeTime($this->publishDate());
}
}
}
}

View File

@@ -6,11 +6,13 @@ use Grav\Common\Config\Config;
use Grav\Common\Utils;
use Grav\Common\Cache;
use Grav\Common\Taxonomy;
use Grav\Common\Language;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Blueprints;
use Grav\Common\Filesystem\Folder;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Whoops\Exception\ErrorException;
/**
* GravPages is the class that is the entry point into the hierarchy of pages
@@ -65,6 +67,8 @@ class Pages
*/
static protected $types;
static protected $home_route;
/**
* Constructor
*
@@ -264,26 +268,34 @@ class Pages
/** @var Config $config */
$config = $this->grav['config'];
// Try redirects
$redirect = $config->get("site.redirects.{$url}");
if ($redirect) {
$this->grav->redirect($redirect);
}
// See if route matches one in the site configuration
$route = $config->get("site.routes.{$url}");
if ($route) {
$page = $this->dispatch($route, $all);
} else {
// Try looking for wildcards
foreach ($config->get("site.routes") as $alias => $route) {
$match = rtrim($alias, '*');
if (strpos($alias, '*') !== false && strpos($url, $match) !== false) {
$wildcard_url = str_replace('*', str_replace($match, '', $url), $route);
$page = isset($this->routes[$wildcard_url]) ? $this->get($this->routes[$wildcard_url]) : null;
if ($page) {
return $page;
// Try Regex style redirects
foreach ((array)$config->get("site.redirects") as $pattern => $replace) {
$pattern = '#' . $pattern . '#';
try {
$found = preg_replace($pattern, $replace, $url);
if ($found != $url) {
$this->grav->redirectLangSafe($found);
}
} catch (ErrorException $e) {
$this->grav['log']->error('site.redirects: '. $pattern . '-> ' . $e->getMessage());
}
}
// Try Regex style routes
foreach ((array)$config->get("site.routes") as $pattern => $replace) {
$pattern = '#' . $pattern . '#';
try {
$found = preg_replace($pattern, $replace, $url);
if ($found != $url) {
$page = $this->dispatch($found, $all);
}
} catch (ErrorException $e) {
$this->grav['log']->error('site.routes: '. $pattern . '-> ' . $e->getMessage());
}
}
}
@@ -331,6 +343,28 @@ class Pages
return $blueprint;
}
/**
* Get all pages
*
* @param \Grav\Common\Page\Page $current
* @return \Grav\Common\Page\Collection
*/
public function all(Page $current = null)
{
$all = new Collection();
$current = $current ?: $this->root();
if ($current->routable()) {
$all[$current->path()] = [ 'slug' => $current->slug() ];
}
foreach ($current->children() as $next) {
$all->append($this->all($next));
}
return $all;
}
/**
* Get list of route/title of all pages.
*
@@ -350,7 +384,8 @@ class Pages
}
$list = array();
if ($current->routable()) {
if (!$current->root()) {
$list[$current->route()] = str_repeat('&nbsp; ', ($level-1)*2) . $current->title();
}
@@ -370,8 +405,8 @@ class Pages
{
if (!self::$types) {
self::$types = new Types();
self::$types->scanBlueprints('theme://blueprints/');
self::$types->scanTemplates('theme://templates/');
file_exists('theme://blueprints/') && self::$types->scanBlueprints('theme://blueprints/');
file_exists('theme://templates/') && self::$types->scanTemplates('theme://templates/');
$event = new Event();
$event->types = self::$types;
@@ -420,6 +455,48 @@ class Pages
return $pages->getList();
}
/**
* Get's the home route
*
* @return string
*/
public static function getHomeRoute()
{
if (empty(self::$home)) {
$grav = Grav::instance();
/** @var Config $config */
$config = $grav['config'];
/** @var Language $language */
$language = $grav['language'];
$home = $config->get('system.home.alias');
if ($language->enabled()) {
$home_aliases = $config->get('system.home.aliases');
if ($home_aliases) {
$active = $language->getActive();
$default = $language->getDefault();
try {
if ($active) {
$home = $home_aliases[$active];
} else {
$home = $home_aliases[$default];
}
} catch (ErrorException $e) {
$home = $home_aliases[$default];
}
}
}
self::$home_route = trim($home, '/');
}
return self::$home_route;
}
/**
* Builds pages.
*
@@ -432,6 +509,9 @@ class Pages
/** @var Config $config */
$config = $this->grav['config'];
/** @var Language $language */
$language = $this->grav['language'];
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$pagesDir = $locator->findResource('page://');
@@ -455,11 +535,12 @@ class Pages
$last_modified = Folder::lastModifiedFile($pagesDir);
}
$page_cache_id = md5(USER_DIR.$last_modified.$config->checksum());
$page_cache_id = md5(USER_DIR.$last_modified.$language->getActive().$config->checksum());
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
if (!$this->instances) {
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
$this->recurse($pagesDir);
$this->buildRoutes();
@@ -491,12 +572,23 @@ class Pages
protected function recurse($directory, Page &$parent = null)
{
$directory = rtrim($directory, DS);
$iterator = new \DirectoryIterator($directory);
$page = new Page;
/** @var Config $config */
$config = $this->grav['config'];
/** @var Language $language */
$language = $this->grav['language'];
// stuff to do at root page
if ($parent === null) {
// Fire event for memory and time consuming plugins...
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onBuildPagesInitialized');
}
}
$page->path($directory);
if ($parent) {
$page->parent($parent);
@@ -515,18 +607,38 @@ class Pages
throw new \RuntimeException('Fatal error when creating page instances.');
}
$content_exists = false;
$pages_found = glob($directory.'/*'.CONTENT_EXT);
$page_extensions = $language->getFallbackPageExtensions();
if ($pages_found) {
foreach ($page_extensions as $extension) {
foreach ($pages_found as $found) {
if (preg_match('/^.*\/[0-9A-Za-z\-\_]+('.$extension.')$/', $found)) {
$page_found = $found;
$page_extension = $extension;
break 2;
}
}
}
}
if ($parent && !empty($page_found)) {
$file = new \SplFileInfo($page_found);
$page->init($file, $page_extension);
$content_exists = true;
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
}
}
// set current modified of page
$last_modified = $page->modified();
// flat for content availability
$content_exists = false;
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
if ($file->isDot()) {
continue;
}
foreach (new \FilesystemIterator($directory) as $file) {
$name = $file->getFilename();
if ($file->isFile()) {
@@ -534,15 +646,6 @@ class Pages
if ($file->getBasename() !== '.DS_Store' && ($modified = $file->getMTime()) > $last_modified) {
$last_modified = $modified;
}
if (Utils::endsWith($name, CONTENT_EXT)) {
$page->init($file);
$content_exists = true;
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
}
}
} elseif ($file->isDir()) {
if (!$page->path()) {
$page->path($file->getPath());
@@ -586,29 +689,45 @@ class Pages
/** @var $taxonomy Taxonomy */
$taxonomy = $this->grav['taxonomy'];
// Get the home route
$home = self::getHomeRoute();
// Build routes and taxonomy map.
/** @var $page Page */
foreach ($this->instances as $page) {
$parent = $page->parent();
if ($parent) {
$route = rtrim($parent->route(), '/') . '/' . $page->slug();
$this->routes[$route] = $page->path();
$page->route($route);
}
if (!empty($route)) {
if (!$page->root()) {
// process taxonomy
$taxonomy->addTaxonomy($page);
} else {
$page->routable(false);
$route = $page->route();
$raw_route = $page->rawRoute();
$page_path = $page->path();
// add regular route
$this->routes[$route] = $page_path;
// add raw route
if ($raw_route != $route) {
$this->routes[$raw_route] = $page_path;
}
// add canonical route
$route_canonical = $page->routeCanonical();
if ($route_canonical && ($route !== $route_canonical)) {
$this->routes[$route_canonical] = $page_path;
}
// add aliases to routes list if they are provided
$route_aliases = $page->routeAliases();
if ($route_aliases) {
foreach ($route_aliases as $alias) {
$this->routes[$alias] = $page_path;
}
}
}
}
/** @var Config $config */
$config = $this->grav['config'];
// Alias and set default route to home page.
$home = trim($config->get('system.home.alias'), '/');
if ($home && isset($this->routes['/' . $home])) {
$this->routes['/'] = $this->routes['/' . $home];
$this->get($this->routes['/' . $home])->route('/');

View File

@@ -13,27 +13,24 @@ class Types implements \ArrayAccess, \Iterator, \Countable
use ArrayAccess, Constructor, Iterator, Countable, Export;
protected $items;
protected $systemBlueprints;
public function register($type, $blueprint = null)
{
if (!$blueprint && $this->systemBlueprints && isset($this->systemBlueprints[$type])) {
$useBlueprint = $this->systemBlueprints[$type];
} else {
$useBlueprint = $blueprint;
}
if ($blueprint || empty($this->items[$type])) {
$this->items[$type] = $blueprint;
$this->items[$type] = $useBlueprint;
}
}
public function scanBlueprints($path)
{
$options = [
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => [
'key' => '|\.yaml$|'
],
'key' => 'SubPathName',
'value' => 'PathName',
];
$this->items = Folder::all($path, $options) + $this->items;
$this->items = $this->findBlueprints($path) + $this->items;
}
public function scanTemplates($path)
@@ -48,6 +45,10 @@ class Types implements \ArrayAccess, \Iterator, \Countable
'recursive' => false
];
if (!$this->systemBlueprints) {
$this->systemBlueprints = $this->findBlueprints('blueprints://pages');
}
foreach (Folder::all($path, $options) as $type) {
$this->register($type);
}
@@ -83,4 +84,19 @@ class Types implements \ArrayAccess, \Iterator, \Countable
ksort($list);
return $list;
}
private function findBlueprints($path)
{
$options = [
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => [
'key' => '|\.yaml$|'
],
'key' => 'SubPathName',
'value' => 'PathName',
];
return Folder::all($path, $options);
}
}

View File

@@ -114,48 +114,72 @@ class Plugin implements EventSubscriberInterface
}
}
/**
* This function will search a string for markdown links in a specific format. The link value can be
* optionally compared against via the $internal_regex and operated on by the callback $function
* provided.
*
* format: [plugin:myplugin_name](function_data)
*
* @param $content The string to perform operations upon
* @param $function The anonymous callback function
* @param string $internal_regex Optional internal regex to extra data from
*
* @return string
*/
protected function parseLinks($content, $function, $internal_regex = '(.*)')
{
$regex = '/\[plugin:(?:'.$this->name.')\]\('.$internal_regex.'\)/i';
return preg_replace_callback($regex, $function, $content);
}
/**
* Merge global and page configurations.
*
* @param Page $page The page to merge the configurations with the
* plugin settings.
*
* @param bool $deep Should you use deep or shallow merging
* @param Page $page The page to merge the configurations with the
* plugin settings.
* @param bool $deep Should you use deep or shallow merging
* @param array $params Array of additional configuration options to
* merge with the plugin settings.
*
* @return \Grav\Common\Data\Data
*/
protected function mergeConfig(Page $page, $deep = false)
protected function mergeConfig(Page $page, $deep = false, $params = [])
{
$class_name = $this->name;
$class_name_merged = $class_name . '.merged';
$defaults = $this->config->get('plugins.' . $class_name, array());
$header = array();
if (isset($page->header()->$class_name_merged)) {
$merged = $page->header()->$class_name_merged;
if (count($merged) > 0) {
return $merged;
} else {
return new Data($defaults);
$defaults = $this->config->get('plugins.'. $class_name, []);
$page_header = $page->header();
$header = [];
if (!isset($page_header->$class_name_merged) && isset($page_header->$class_name)) {
// Get default plugin configurations and retrieve page header configuration
$config = $page_header->$class_name;
if (is_bool($config)) {
// Overwrite enabled option with boolean value in page header
$config = ['enabled' => $config];
}
}
// Get default plugin configurations and retrieve page header configuration
if (isset($page->header()->$class_name)) {
// Merge page header settings using deep or shallow merging technique
if ($deep) {
$header = array_replace_recursive($defaults, $page->header()->$class_name);
$header = array_replace_recursive($defaults, $config);
} else {
$header = array_merge($defaults, $page->header()->$class_name);
$header = array_merge($defaults, $config);
}
} else {
// Create new config object and set it on the page object so it's cached for next time
$page->modifyHeader($class_name_merged, new Data($header));
} else if (isset($page_header->$class_name_merged)) {
$merged = $page_header->$class_name_merged;
$header = $merged->toArray();
}
if (empty($header)) {
$header = $defaults;
}
// Create new config object and set it on the page object so it's cached for next time
$config = new Data($header);
$page->modifyHeader($class_name_merged, $config);
// Merge additional parameter with configuration options
if ($deep) {
$header = array_replace_recursive($header, $params);
} else {
$header = array_merge($header, $params);
}
// Return configurations as a new data config class
return $config;
return new Data($header);
}
}

View File

@@ -4,6 +4,7 @@ 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;
@@ -17,12 +18,7 @@ use RocketTheme\Toolbox\Event\EventSubscriberInterface;
*/
class Plugins extends Iterator
{
protected $grav;
public function __construct(Grav $grav)
{
$this->grav = $grav;
}
use GravTrait;
/**
* Recurses through the plugins directory creating Plugin objects for each plugin it finds.
@@ -33,11 +29,13 @@ class Plugins extends Iterator
public function init()
{
/** @var Config $config */
$config = $this->grav['config'];
$config = self::getGrav()['config'];
$plugins = (array) $config->get('plugins');
$inflector = self::getGrav()['inflector'];
/** @var EventDispatcher $events */
$events = $this->grav['events'];
$events = self::getGrav()['events'];
foreach ($plugins as $plugin => $data) {
if (empty($data['enabled'])) {
@@ -45,9 +43,10 @@ class Plugins extends Iterator
continue;
}
$filePath = $this->grav['locator']('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT);
$locator = self::getGrav()['locator'];
$filePath = $locator->findResource('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT);
if (!is_file($filePath)) {
$this->grav['log']->addWarning(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin));
self::getGrav()['log']->addWarning(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin));
continue;
}
@@ -55,7 +54,7 @@ class Plugins extends Iterator
$pluginClassFormat = [
'Grav\\Plugin\\'.ucfirst($plugin).'Plugin',
'Grav\\Plugin\\'.Inflector::camelize($plugin).'Plugin'
'Grav\\Plugin\\'.$inflector->camelize($plugin).'Plugin'
];
$pluginClassName = false;
@@ -70,7 +69,7 @@ class Plugins extends Iterator
throw new \RuntimeException(sprintf("Plugin '%s' class not found! Try reinstalling this plugin.", $plugin));
}
$instance = new $pluginClassName($plugin, $this->grav, $config);
$instance = new $pluginClassName($plugin, self::getGrav(), $config);
if ($instance instanceof EventSubscriberInterface) {
$events->addSubscriber($instance);
}
@@ -123,10 +122,10 @@ class Plugins extends Iterator
$obj = new Data($file->content(), $blueprint);
// Override with user configuration.
$file = CompiledYamlFile::instance("user://config/plugins/{$name}.yaml");
$obj->merge($file->content());
$obj->merge(self::getGrav()['config']->get('plugins.' . $name) ?: []);
// Save configuration always to user/config.
$file = CompiledYamlFile::instance("config://plugins/{$name}.yaml");
$obj->file($file);
return $obj;

View File

@@ -38,18 +38,8 @@ class ConfigServiceProvider implements ServiceProviderInterface
public function loadMasterConfig(Container $container)
{
$environment = $this->getEnvironment($container);
$file = CACHE_DIR . 'compiled/config/master-'.$environment.'.php';
$data = is_file($file) ? (array) include $file : [];
if ($data) {
try {
$config = new Config($data, $container, $environment);
} catch (\Exception $e) {
}
}
if (!isset($config)) {
$config = new Config($this->setup, $container, $environment);
}
$config = new Config($this->setup, $container, $environment);
return $config;
}

View File

@@ -30,7 +30,11 @@ class ErrorServiceProvider implements ServiceProviderInterface
$logger = $container['log'];
$errors->pushHandler(function (\Exception $exception, $inspector, $run) use ($logger) {
$logger->addCritical($exception->getMessage(). ' - Trace: '. $exception->getTraceAsString());
try {
$logger->addCritical($exception->getMessage() . ' - Trace: ' . $exception->getTraceAsString());
} catch (\Exception $e) {
echo $e;
}
}, 'log');
$errors->register();

View File

@@ -0,0 +1,47 @@
<?php
namespace Grav\Common;
/**
* Wrapper for Session
*/
class Session extends \RocketTheme\Toolbox\Session\Session
{
protected $grav;
protected $session;
public function __construct(Grav $grav)
{
$this->grav = $grav;
}
public function init()
{
/** @var Uri $uri */
$uri = $this->grav['uri'];
$config = $this->grav['config'];
if ($config->get('system.session.enabled')) {
// Only activate admin if we're inside the admin path.
$is_admin = false;
$route = $config->get('plugins.admin.route');
$base = '/' . trim($route, '/');
if (substr($uri->route(), 0, strlen($base)) == $base) {
$is_admin = true;
}
$session_timeout = $config->get('system.session.timeout', 1800);
$session_path = $config->get('system.session.path', '/' . ltrim($uri->rootUrl(false), '/'));
// Define session service.
parent::__construct(
$session_timeout,
$session_path
);
$site_identifier = $config->get('site.title', 'unknown');
$this->setName($config->get('system.session.name', 'grav_site') . '_' . substr(md5($site_identifier), 0, 7) . ($is_admin ? '_admin' : ''));
$this->start();
setcookie(session_name(), session_id(), time() + $session_timeout, $session_path);
}
}
}

View File

@@ -52,13 +52,13 @@ class Taxonomy
$page_taxonomy = $page->taxonomy();
}
if (!$page->published()) {
if (!$page->published() || empty($page_taxonomy)) {
return;
}
/** @var Config $config */
$config = $this->grav['config'];
if ($config->get('site.taxonomies') && count($page_taxonomy) > 0) {
if ($config->get('site.taxonomies')) {
foreach ((array) $config->get('site.taxonomies') as $taxonomy) {
if (isset($page_taxonomy[$taxonomy])) {
foreach ((array) $page_taxonomy[$taxonomy] as $item) {
@@ -76,7 +76,7 @@ class Taxonomy
*
* @param array $taxonomies taxonomies to search, eg ['tag'=>['animal','cat']]
* @param string $operator can be 'or' or 'and' (defaults to 'or')
* @return Colleciton Collection object set to contain matches found in the taxonomy map
* @return Collection Collection object set to contain matches found in the taxonomy map
*/
public function findTaxonomy($taxonomies, $operator = 'and')
{

View File

@@ -106,10 +106,10 @@ class Themes extends Iterator
$obj = new Data($file->content(), $blueprint);
// Override with user configuration.
$file = CompiledYamlFile::instance("user://config/themes/{$name}" . YAML_EXT);
$obj->merge($file->content());
$obj->merge($this->grav['config']->get('themes.' . $name) ?: []);
// Save configuration always to user/config.
$file = CompiledYamlFile::instance("config://themes/{$name}" . YAML_EXT);
$obj->file($file);
return $obj;
@@ -141,15 +141,16 @@ class Themes extends Iterator
$locator = $grav['locator'];
$file = $locator('theme://theme.php') ?: $locator("theme://{$name}.php");
$inflector = $grav['inflector'];
if ($file) {
// Local variables available in the file: $grav, $config, $name, $file
$class = include $file;
if (!is_object($class)) {
$themeClassFormat = [
'Grav\\Theme\\'.ucfirst($name),
'Grav\\Theme\\'.Inflector::camelize($name)
'Grav\\Theme\\'.$inflector->camelize($name)
];
$themeClassName = false;
@@ -221,7 +222,13 @@ class Themes extends Iterator
protected function loadConfiguration($name, Config $config)
{
$themeConfig = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT)->content();
$config->joinDefaults("themes.{$name}", $themeConfig);
if ($this->config->get('system.languages.translations', true)) {
$languages = CompiledYamlFile::instance("themes://{$name}/languages". YAML_EXT)->content();
if ($languages) {
$config->getLanguages()->mergeRecursive($languages);
}
}
}
}

View File

@@ -0,0 +1,16 @@
<?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
*
* @author RocketTheme
* @license MIT
*/
class TraceableTwigEnvironment extends \DebugBar\Bridge\Twig\TraceableTwigEnvironment
{
use WriteCacheFileTrait;
}

View File

@@ -1,6 +1,7 @@
<?php
namespace Grav\Common;
namespace Grav\Common\Twig;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
@@ -57,6 +58,7 @@ class Twig
public function __construct(Grav $grav)
{
$this->grav = $grav;
$this->twig_paths = [];
}
/**
@@ -72,7 +74,23 @@ class Twig
$locator = $this->grav['locator'];
$debugger = $this->grav['debugger'];
$this->twig_paths = $locator->findResources('theme://templates');
/** @var Language $language */
$language = $this->grav['language'];
$active_language = $language->getActive();
$language_append = $active_language ? '/'.$active_language : '';
// handle language templates if available
if ($language->enabled()) {
$lang_templates = $locator->findResource('theme://templates/'.$active_language);
if ($lang_templates) {
$this->twig_paths[] = $lang_templates;
}
}
$this->twig_paths = array_merge($this->twig_paths, $locator->findResources('theme://templates'));
$this->grav->fireEvent('onTwigTemplatePaths');
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
@@ -84,9 +102,9 @@ class Twig
$params['cache'] = $locator->findResource('cache://twig', true, true);
}
$this->twig = new \Twig_Environment($loader_chain, $params);
$this->twig = new TwigEnvironment($loader_chain, $params);
if ($debugger->enabled() && $config->get('system.debugger.twig')) {
$this->twig = new \DebugBar\Bridge\Twig\TraceableTwigEnvironment($this->twig);
$this->twig = new TraceableTwigEnvironment($this->twig);
$collector = new \DebugBar\Bridge\Twig\TwigCollector($this->twig);
$debugger->addCollector($collector);
}
@@ -131,9 +149,9 @@ class Twig
'config' => $config,
'uri' => $this->grav['uri'],
'base_dir' => rtrim(ROOT_DIR, '/'),
'base_url' => $this->grav['base_url'],
'base_url_absolute' => $this->grav['base_url_absolute'],
'base_url_relative' => $this->grav['base_url_relative'],
'base_url' => $this->grav['base_url'] . $language_append,
'base_url_absolute' => $this->grav['base_url_absolute'] . $language_append,
'base_url_relative' => $this->grav['base_url_relative'] . $language_append,
'theme_dir' => $locator->findResource('theme://'),
'theme_url' => $this->grav['base_url'] .'/'. $locator->findResource('theme://', false),
'site' => $config->get('site'),
@@ -283,15 +301,22 @@ class Twig
$this->grav->fireEvent('onTwigSiteVariables');
$pages = $this->grav['pages'];
$page = $this->grav['page'];
$content = $page->content();
$twig_vars = $this->twig_vars;
$twig_vars['pages'] = $pages->root();
$twig_vars['page'] = $page;
$twig_vars['header'] = $page->header();
$twig_vars['content'] = $page->content();
$twig_vars['content'] = $content;
$ext = '.' . ($format ? $format : 'html') . TWIG_EXT;
// determine if params are set, if so disable twig cache
$params = $this->grav['uri']->params(null, true);
if (!empty($params)) {
$this->twig->setCache(false);
}
// Get Twig template layout
$template = $this->template($page->template() . $ext);

View File

@@ -0,0 +1,16 @@
<?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
*
* @author RocketTheme
* @license MIT
*/
class TwigEnvironment extends \Twig_Environment
{
use WriteCacheFileTrait;
}

View File

@@ -1,11 +1,13 @@
<?php
namespace Grav\Common;
namespace Grav\Common\Twig;
use Grav\Common\Grav;
use Grav\Common\Inflector;
use Grav\Common\Utils;
use Grav\Common\Markdown\Parsedown;
use Grav\Common\Markdown\ParsedownExtra;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* The Twig extension adds some filters and functions that are useful for Grav
*
@@ -16,11 +18,13 @@ class TwigExtension extends \Twig_Extension
{
protected $grav;
protected $debugger;
protected $config;
public function __construct()
{
$this->grav = Grav::instance();
$this->debugger = isset($this->grav['debugger']) ? $this->grav['debugger'] : null;
$this->config = $this->grav['config'];
}
/**
@@ -52,7 +56,11 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFilter('contains', [$this, 'containsFilter']),
new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFilter']),
new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFilter'])
new \Twig_SimpleFilter('markdown', [$this, 'markdownFilter']),
new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']),
new \Twig_SimpleFilter('t', [$this, 'translate']),
new \Twig_SimpleFilter('ta', [$this, 'translateArray'])
];
}
@@ -70,6 +78,9 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
new \Twig_SimpleFunction('array', [$this, 'arrayFunc']),
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('ta', [$this, 'translateArray'])
];
}
@@ -106,7 +117,7 @@ class TwigExtension extends \Twig_Extension
* Truncate content by a limit.
*
* @param string $string
* @param int $limit Nax number of characters.
* @param int $limit Max number of characters.
* @param string $break Break point.
* @param string $pad Appended padding to the end of the string.
* @return string
@@ -184,16 +195,18 @@ class TwigExtension extends \Twig_Extension
// TODO: check this and fix the docblock if needed.
$action = $action.'ize';
$inflector = $this->grav['inflector'];
if (in_array(
$action,
['titleize','camelize','underscorize','hyphenize', 'humanize','ordinalize','monthize']
)) {
return Inflector::$action($data);
return $inflector->$action($data);
} elseif (in_array($action, ['pluralize','singularize'])) {
if ($count) {
return Inflector::$action($data, $count);
return $inflector->$action($data, $count);
} else {
return Inflector::$action($data);
return $inflector->$action($data);
}
} else {
return $data;
@@ -333,13 +346,13 @@ class TwigExtension extends \Twig_Extension
public function markdownFilter($string)
{
$page = $this->grav['page'];
$defaults = $this->grav['config']->get('system.pages.markdown');
$defaults = $this->config->get('system.pages.markdown');
// Initialize the preferred variant of Parsedown
if ($defaults['extra']) {
$parsedown = new ParsedownExtra($page);
$parsedown = new ParsedownExtra($page, $defaults);
} else {
$parsedown = new Parsedown($page);
$parsedown = new Parsedown($page, $defaults);
}
$string = $parsedown->text($string);
@@ -347,6 +360,27 @@ class TwigExtension extends \Twig_Extension
return $string;
}
public function startsWithFilter($haystack, $needle)
{
return Utils::startsWith($haystack, $needle);
}
public function endsWithFilter($haystack, $needle)
{
return Utils::endsWith($haystack, $needle);
}
public function translate()
{
return $this->grav['language']->translate(func_get_args());
}
public function translateArray($key, $index, $lang = null)
{
return $this->grav['language']->translateArray($key, $index, $lang);
}
/**
* Repeat given string x times.
*
@@ -447,4 +481,14 @@ class TwigExtension extends \Twig_Extension
{
return Utils::generateRandomString($count);
}
public function arrayFunc($value)
{
return (array) $value;
}
public function translateFunc()
{
return $this->grav['language']->translate(func_get_args());
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Grav\Common\Twig;
use Grav\Common\GravTrait;
/**
* A trait to add some custom processing to the identifyLink() method in Parsedown and ParsedownExtra
*/
trait WriteCacheFileTrait
{
use GravTrait;
protected static $umask;
/**
* This exists so template cache files use the same
* group between apache and cli
*
* @param $file
* @param $content
*/
protected function writeCacheFile($file, $content)
{
if (!isset(self::$umask)) {
self::$umask = self::getGrav()['config']->get('system.twig.umask_fix', false);
}
if (self::$umask) {
if (!is_dir(dirname($file))) {
$old = umask(0002);
mkdir(dirname($file), 0777, true);
umask($old);
}
parent::writeCacheFile($file, $content);
chmod($file, 0775);
} else {
parent::writeCacheFile($file, $content);
}
}
}

View File

@@ -1,6 +1,9 @@
<?php
namespace Grav\Common;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
/**
* The URI object provides information about the current URL
*
@@ -11,6 +14,7 @@ class Uri
{
public $url;
protected $basename;
protected $base;
protected $root;
protected $bits;
@@ -33,8 +37,7 @@ class Uri
$port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
$root_path = rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/');
$root_path = str_replace(' ', '%20', rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/'));
if (isset($_SERVER['HTTPS'])) {
$base = (@$_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
@@ -64,6 +67,7 @@ class Uri
$this->base = $base;
$this->root = $base . $root_path;
$this->url = $base . $uri;
}
/**
@@ -71,37 +75,61 @@ class Uri
*/
public function init()
{
$config = Grav::instance()['config'];
$grav = Grav::instance();
$config = $grav['config'];
$language = $grav['language'];
// resets
$this->paths = [];
$this->params = [];
$this->query = [];
// get any params and remove them
$uri = str_replace($this->root, '', $this->url);
// reset params
$this->params = [];
// process params
$uri = $this->processParams($uri, $config->get('system.param_sep'));
// set active language
$uri = $language->setActiveFromUri($uri);
// redirect to language specific homepage if configured to do so
if ($uri == '/' && $language->enabled()) {
if ($config->get('system.languages.home_redirect.include_route', true)) {
$prefix = $config->get('system.languages.home_redirect.include_lang', true) ? $language->getLanguage() . '/' : '';
$grav->redirect($prefix . Pages::getHomeRoute());
} elseif ($config->get('system.languages.home_redirect.include_lang', true)) {
$grav->redirect($language->getLanguage() . '/');
}
}
// split the URL and params
$bits = parse_url($uri);
// process query string
if (isset($bits['query'])) {
parse_str($bits['query'], $this->query);
$uri = $bits['path'];
}
// remove the extension if there is one set
$parts = pathinfo($uri);
if (preg_match("/\.(txt|xml|html|json|rss|atom)$/", $parts['basename'])) {
$uri = rtrim($parts['dirname'], '/').'/'.$parts['filename'];
// set the original basename
$this->basename = $parts['basename'];
$valid_page_types = implode('|', $config->get('system.pages.types'));
if (preg_match("/\.(".$valid_page_types.")$/", $parts['basename'])) {
$uri = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS). '/' .$parts['filename'];
$this->extension = $parts['extension'];
}
// set the new url
$this->url = $this->root . $uri;
// split into bits
$this->bits = parse_url($uri);
$this->query = array();
if (isset($this->bits['query'])) {
parse_str($this->bits['query'], $this->query);
}
$this->paths = array();
$this->path = $this->bits['path'];
$this->path = $uri;
$this->content_path = trim(str_replace($this->base, '', $this->path), '/');
if ($this->content_path != '') {
$this->paths = explode('/', $this->content_path);
@@ -131,7 +159,7 @@ class Uri
$path[] = $bit;
}
}
$uri = implode('/', $path);
$uri = '/' . ltrim(implode('/', $path), '/');
}
return $uri;
}
@@ -186,20 +214,27 @@ class Uri
* Return all or a single query parameter as a URI compatible string.
*
* @param string $id Optional parameter name.
* @param boolean $array return the array format or not
* @return null|string
*/
public function params($id = null)
public function params($id = null, $array = false)
{
$config = Grav::instance()['config'];
$params = null;
if ($id === null) {
if ($array) {
return $this->params;
}
$output = array();
foreach ($this->params as $key => $value) {
$output[] = $key . $config->get('system.param_sep') . $value;
$params = '/'.implode('/', $output);
}
} elseif (isset($this->params[$id])) {
if ($array) {
return $this->params[$id];
}
$params = "/{$id}". $config->get('system.param_sep') . $this->params[$id];
}
@@ -244,7 +279,11 @@ class Uri
*/
public function path()
{
return $this->path;
$path = $this->path;
if ($path === '') {
$path = '/';
}
return $path;
}
/**
@@ -282,6 +321,17 @@ class Uri
return $this->host();
}
/**
* Return the basename of the URI
*
* @return String The basename of the URI
*/
public function basename()
{
return $this->basename;
}
/**
* Return the base of the URI
*
@@ -355,7 +405,7 @@ class Uri
}
/**
* Retrun the IP address of the current user
* Return the IP address of the current user
*
* @return string ip address
*/
@@ -378,6 +428,7 @@ class Uri
return $ipaddress;
}
/**
* Is this an external URL? if it starts with `http` then yes, else false
*
@@ -412,4 +463,86 @@ class Uri
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
return "$scheme$user$pass$host$port$path$query$fragment";
}
/**
* Converts links from absolute '/' or relative (../..) to a grav friendly format
*
* @param $page the current page to use as reference
* @param string $markdown_url the URL as it was written in the markdown
*
* @return string the more friendly formatted url
*/
public static function convertUrl(Page $page, $markdown_url)
{
$grav = Grav::instance();
$pages_dir = $grav['locator']->findResource('page://');
$base_url = rtrim($grav['base_url'] . $grav['pages']->base(), '/');
// if absolute and starts with a base_url move on
if (pathinfo($markdown_url, PATHINFO_DIRNAME) == '.' && $page->url() == '/') {
return '/' . $markdown_url;
// no path to convert
} elseif ($base_url != '' && Utils::startsWith($markdown_url, $base_url)) {
return $markdown_url;
// if contains only a fragment
} elseif (Utils::startsWith($markdown_url, '#')) {
return $markdown_url;
} else {
$target = null;
// see if page is relative to this or absolute
if (Utils::startsWith($markdown_url, '/')) {
$normalized_url = Utils::normalizePath($base_url . $markdown_url);
$normalized_path = Utils::normalizePath($pages_dir . $markdown_url);
} else {
$normalized_url = $base_url . Utils::normalizePath($page->route() . '/' . $markdown_url);
$normalized_path = Utils::normalizePath($page->path() . '/' . $markdown_url);
}
// special check to see if path checking is required.
$just_path = str_replace($normalized_url, '', $normalized_path);
if ($just_path == $page->path()) {
return $normalized_url;
}
$url_bits = parse_url($normalized_path);
$full_path = ($url_bits['path']);
if (file_exists($full_path)) {
// do nothing
} elseif (file_exists(urldecode($full_path))) {
$full_path = urldecode($full_path);
} else {
return $normalized_url;
}
$path_info = pathinfo($full_path);
$page_path = $path_info['dirname'];
$filename = '';
if ($markdown_url == '..') {
$page_path = $full_path;
} else {
// save the filename if a file is part of the path
if (is_file($full_path)) {
if ($path_info['extension'] != 'md') {
$filename = '/' . $path_info['basename'];
}
} else {
$page_path = $full_path;
}
}
// get page instances and try to find one that fits
$instances = $grav['pages']->instances();
if (isset($instances[$page_path])) {
$target = $instances[$page_path];
$url_bits['path'] = $base_url . $target->route() . $filename;
return Uri::buildUrl($url_bits);
}
return $normalized_url;
}
}
}

View File

@@ -13,11 +13,22 @@ abstract class Authentication
* Create password hash from plaintext password.
*
* @param string $password Plaintext password.
* @throws \RuntimeException
* @return string|bool
*/
public static function create($password)
{
return password_hash($password, PASSWORD_DEFAULT);
if (!$password) {
throw new \RuntimeException('Password hashing failed: no password provided.');
}
$hash = password_hash($password, PASSWORD_DEFAULT);
if (!$hash) {
throw new \RuntimeException('Password hashing failed: internal error.');
}
return $hash;
}
/**
@@ -29,13 +40,8 @@ abstract class Authentication
*/
public static function verify($password, $hash)
{
// Always accept plaintext passwords (needs an update).
if ($password && $password == $hash) {
return 2;
}
// Fail if hash doesn't match.
if (!$password || !password_verify($password, $hash)) {
// Fail if hash doesn't match
if (!$password || !$hash || !password_verify($password, $hash)) {
return 0;
}

View File

@@ -9,6 +9,9 @@ use Grav\Common\GravTrait;
/**
* User object
*
* @property mixed authenticated
* @property mixed password
* @property bool|string hashed_password
* @author RocketTheme
* @license MIT
*/
@@ -53,11 +56,35 @@ class User extends Data
*/
public function authenticate($password)
{
$result = Authentication::verify($password, $this->password);
$save = false;
// Plain-text is still stored
if ($this->password) {
if ($password !== $this->password) {
// Plain-text passwords do not match, we know we should fail but execute
// verify to protect us from timing attacks and return false regardless of
// the result
Authentication::verify($password, self::getGrav()['config']->get('system.security.default_hash'));
return false;
} else {
// Plain-text does match, we can update the hash and proceed
$save = true;
$this->hashed_password = Authentication::create($this->password);
unset($this->password);
}
}
$result = Authentication::verify($password, $this->hashed_password);
// Password needs to be updated, save the file.
if ($result == 2) {
$this->password = Authentication::create($password);
$save = true;
$this->hashed_password = Authentication::create($password);
}
if ($save) {
$this->save();
}
@@ -65,13 +92,31 @@ class User extends Data
}
/**
* Checks user authorisation to the action.
* Save user without the username
*/
public function save()
{
$file = $this->file();
if ($file) {
$username = $this->get('username');
unset($this->username);
$file->save($this->items);
$this->set('username', $username);
}
}
/**
* Checks user authorization to the action.
*
* @param string $action
* @return bool
*/
public function authorise($action)
{
if (empty($this->items)) {
return false;
}
return $this->get("access.{$action}") === true;
}
}

View File

@@ -1,6 +1,8 @@
<?php
namespace Grav\Common;
use RocketTheme\Toolbox\Event\Event;
/**
* Misc utilities.
*
@@ -8,29 +10,58 @@ namespace Grav\Common;
*/
abstract class Utils
{
use GravTrait;
/**
* @param string $haystack
* @param string $needle
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function startsWith($haystack, $needle)
{
if (is_array($needle)) {
$status = false;
foreach ($needle as $each_needle) {
$status = $status || ($each_needle === '' || strpos($haystack, $each_needle) === 0);
if ($status) {
return $status;
}
}
return $status;
}
return $needle === '' || strpos($haystack, $needle) === 0;
}
/**
* @param string $haystack
* @param string $needle
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function endsWith($haystack, $needle)
{
if (is_array($needle)) {
$status = false;
foreach ($needle as $each_needle) {
$status = $status || ($each_needle === '' || substr($haystack, -strlen($each_needle)) === $each_needle);
if ($status) {
return $status;
}
}
return $status;
}
return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
}
/**
* @param string $haystack
* @param string $needle
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function contains($haystack, $needle)
@@ -43,80 +74,12 @@ abstract class Utils
*
* @param object $obj1
* @param object $obj2
*
* @return object
*/
public static function mergeObjects($obj1, $obj2)
{
return (object) array_merge((array) $obj1, (array) $obj2);
}
/**
* Recursive remove a directory - DANGEROUS! USE WITH CARE!!!!
*
* @param $dir
* @return bool
*/
public static function rrmdir($dir)
{
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
/** @var \DirectoryIterator $fileinfo */
foreach ($files as $fileinfo) {
if ($fileinfo->isDir()) {
if (false === rmdir($fileinfo->getRealPath())) {
return false;
}
} else {
if (false === unlink($fileinfo->getRealPath())) {
return false;
}
}
}
return rmdir($dir);
}
/**
* Recursive copy of one directory to another
*
* @param $src
* @param $dest
*
* @return bool
*/
public static function rcopy($src, $dest)
{
// If the src is not a directory do a simple file copy
if (!is_dir($src)) {
copy($src, $dest);
return true;
}
// If the destination directory does not exist create it
if (!is_dir($dest)) {
if (!mkdir($dest)) {
// If the destination directory could not be created stop processing
return false;
}
}
// Open the source directory to read in files
$i = new \DirectoryIterator($src);
/** @var \DirectoryIterator $f */
foreach ($i as $f) {
if ($f->isFile()) {
copy($f->getRealPath(), "$dest/" . $f->getFilename());
} else {
if (!$f->isDot() && $f->isDir()) {
static::rcopy($f->getRealPath(), "$dest/$f");
}
}
}
return true;
return (object)array_merge((array)$obj1, (array)$obj2);
}
/**
@@ -127,6 +90,7 @@ abstract class Utils
* @param string $ending
* @param bool $exact
* @param bool $considerHtml
*
* @return string
*/
public static function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true)
@@ -137,7 +101,7 @@ abstract class Utils
if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
return $text;
}
// splits all html-tags to scanable lines
// splits all html-tags to scannable lines
preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
$total_length = strlen($ending);
$truncate = '';
@@ -145,34 +109,41 @@ abstract class Utils
// if there is any html-tag in this line, handle it and add it (uncounted) to the output
if (!empty($line_matchings[1])) {
// if it's an "empty element" with or without xhtml-conform closing slash
if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is',
$line_matchings[1])) {
// do nothing
// if tag is a closing tag
} else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
// delete tag from $open_tags list
$pos = array_search($tag_matchings[1], $open_tags);
if ($pos !== false) {
unset($open_tags[$pos]);
// if tag is a closing tag
} else {
if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
// delete tag from $open_tags list
$pos = array_search($tag_matchings[1], $open_tags);
if ($pos !== false) {
unset($open_tags[$pos]);
}
// if tag is an opening tag
} else {
if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
// add tag to the beginning of $open_tags list
array_unshift($open_tags, strtolower($tag_matchings[1]));
}
}
// if tag is an opening tag
} else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
// add tag to the beginning of $open_tags list
array_unshift($open_tags, strtolower($tag_matchings[1]));
}
// add html-tag to $truncate'd text
$truncate .= $line_matchings[1];
}
// calculate the length of the plain text part of the line; handle entities as one character
$content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
if ($total_length+$content_length> $length) {
$content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ',
$line_matchings[2]));
if ($total_length + $content_length > $length) {
// the number of characters which are left
$left = $length - $total_length;
$entities_length = 0;
// search for html entities
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities,
PREG_OFFSET_CAPTURE)) {
// calculate the real length of all entities in the legal range
foreach ($entities[0] as $entity) {
if ($entity[1]+1-$entities_length <= $left) {
if ($entity[1] + 1 - $entities_length <= $left) {
$left--;
$entities_length += strlen($entity[0]);
} else {
@@ -181,7 +152,7 @@ abstract class Utils
}
}
}
$truncate .= substr($line_matchings[2], 0, $left+$entities_length);
$truncate .= substr($line_matchings[2], 0, $left + $entities_length);
// maximum length is reached, so get off the loop
break;
} else {
@@ -202,7 +173,7 @@ abstract class Utils
}
// if the words shouldn't be cut in the middle...
if (!$exact) {
// ...search the last occurance of a space...
// ...search the last occurrence of a space...
$spacepos = strrpos($truncate, ' ');
if (isset($spacepos)) {
// ...and cut the text in this position
@@ -217,6 +188,7 @@ abstract class Utils
$truncate .= '</' . $tag . '>';
}
}
return $truncate;
}
@@ -231,4 +203,234 @@ abstract class Utils
{
return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, $length);
}
/**
* Provides the ability to download a file to the browser
*
* @param $file the full path to the file to be downloaded
* @param bool $force_download as opposed to letting browser choose if to download or render
*/
public static function download($file, $force_download = true)
{
if (file_exists($file)) {
// fire download event
self::getGrav()->fireEvent('onBeforeDownload', new Event(['file' => $file]));
$file_parts = pathinfo($file);
$filesize = filesize($file);
set_time_limit(0);
ignore_user_abort(false);
if ($force_download) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . $file_parts['basename']);
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
} else {
header("Content-Type: " . Utils::getMimeType($file_parts['extension']));
}
header('Content-Length: ' . $filesize);
// 8kb chunks for now
$chunk = 8 * 1024;
$fh = fopen($file, "rb");
if ($fh === false) {
return;
}
// Repeat reading until EOF
while (!feof($fh)) {
echo fread($fh, $chunk);
ob_flush(); // flush output
flush();
}
exit;
}
}
/**
* Return the mimetype based on filename
*
* @param $extension Extension of file (eg .txt)
*
* @return string
*/
public static function getMimeType($extension)
{
$extension = strtolower($extension);
switch ($extension) {
case "js":
return "application/x-javascript";
case "json":
return "application/json";
case "jpg":
case "jpeg":
case "jpe":
return "image/jpg";
case "png":
case "gif":
case "bmp":
case "tiff":
return "image/" . $extension;
case "css":
return "text/css";
case "xml":
return "application/xml";
case "doc":
case "docx":
return "application/msword";
case "xls":
case "xlt":
case "xlm":
case "xld":
case "xla":
case "xlc":
case "xlw":
case "xll":
return "application/vnd.ms-excel";
case "ppt":
case "pps":
return "application/vnd.ms-powerpoint";
case "rtf":
return "application/rtf";
case "pdf":
return "application/pdf";
case "html":
case "htm":
case "php":
return "text/html";
case "txt":
return "text/plain";
case "mpeg":
case "mpg":
case "mpe":
return "video/mpeg";
case "mp3":
return "audio/mpeg3";
case "wav":
return "audio/wav";
case "aiff":
case "aif":
return "audio/aiff";
case "avi":
return "video/msvideo";
case "wmv":
return "video/x-ms-wmv";
case "mov":
return "video/quicktime";
case "zip":
return "application/zip";
case "tar":
return "application/x-tar";
case "swf":
return "application/x-shockwave-flash";
default:
return "application/octet-stream";
}
}
/**
* Normalize path by processing relative `.` and `..` syntax and merging path
*
* @param $path
*
* @return string
*/
public static function normalizePath($path)
{
$root = ($path[0] === '/') ? '/' : '';
$segments = explode('/', trim($path, '/'));
$ret = array();
foreach ($segments as $segment) {
if (($segment == '.') || empty($segment)) {
continue;
}
if ($segment == '..') {
array_pop($ret);
} else {
array_push($ret, $segment);
}
}
return $root . implode('/', $ret);
}
public static function timezones()
{
$timezones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL);
$offsets = [];
$testDate = new \DateTime;
foreach ($timezones as $zone) {
$tz = new \DateTimeZone($zone);
$offsets[$zone] = $tz->getOffset($testDate);
}
asort($offsets);
$timezone_list = array();
foreach ($offsets as $timezone => $offset) {
$offset_prefix = $offset < 0 ? '-' : '+';
$offset_formatted = gmdate('H:i', abs($offset));
$pretty_offset = "UTC${offset_prefix}${offset_formatted}";
$timezone_list[$timezone] = "(${pretty_offset}) $timezone";
}
return $timezone_list;
}
public static function arrayFilterRecursive(Array $source, $fn)
{
$result = array();
foreach ($source as $key => $value)
{
if (is_array($value))
{
$result[$key] = static::arrayFilterRecursive($value, $fn);
continue;
}
if ($fn($key, $value))
{
$result[$key] = $value; // KEEP
continue;
}
}
return $result;
}
}

View File

@@ -1,6 +1,9 @@
<?php
namespace Grav\Console\Cli;
use Grav\Common\Backup\ZipBackup;
use Grav\Console\ConsoleTrait;
use RocketTheme\Toolbox\File\JsonFile;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\ProgressBar;
@@ -14,14 +17,9 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class BackupCommand extends Command
{
use ConsoleTrait;
/**
* @var
*/
protected $source;
/**
* @var
*/
protected $progress;
/**
@@ -34,13 +32,12 @@ class BackupCommand extends Command
->addArgument(
'destination',
InputArgument::OPTIONAL,
'Where to store the backup'
'Where to store the backup (/backup is default)'
)
->setDescription("Creates a backup of the Grav instance")
->setHelp('The <info>backup</info> creates a zipped backup. Optionally can be saved in a different destination.');
$this->source = getcwd();
}
@@ -52,33 +49,23 @@ class BackupCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
$this->setupConsole($input, $output);
$this->progress = new ProgressBar($output);
$this->progress->setFormat('Archiving <cyan>%current%</cyan> files [<green>%bar%</green>] %elapsed:6s% %memory:6s%');
$name = basename($this->source);
$dir = dirname($this->source);
$date = date('YmdHis', time());
$filename = $name . '-' . $date . '.zip';
self::getGrav()['config']->init();
$destination = ($input->getArgument('destination')) ? $input->getArgument('destination') : ROOT_DIR;
$destination = rtrim($destination, DS) . DS . $filename;
$destination = ($input->getArgument('destination')) ? $input->getArgument('destination') : null;
$log = JsonFile::instance(self::getGrav()['locator']->findResource("log://backup.log", true, true));
$backup = ZipBackup::backup($destination, [$this, 'output']);
$output->writeln('');
$output->writeln('Creating new Backup "' . $destination . '"');
$this->progress->start();
$log->content([
'time' => time(),
'location' => $backup
]);
$log->save();
$zip = new \ZipArchive();
$zip->open($destination, \ZipArchive::CREATE);
$zip->addEmptyDir($name);
$this->folderToZip($this->source, $zip, strlen($dir . DS), $this->progress);
$zip->close();
$this->progress->finish();
$output->writeln('');
$output->writeln('');
@@ -90,25 +77,21 @@ class BackupCommand extends Command
* @param $exclusiveLength
* @param $progress
*/
private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, ProgressBar $progress)
public function output($args)
{
$handle = opendir($folder);
while (false !== $f = readdir($handle)) {
if ($f != '.' && $f != '..') {
$filePath = "$folder/$f";
// Remove prefix from file path before add to zip.
$localPath = substr($filePath, $exclusiveLength);
if (is_file($filePath)) {
$zipFile->addFile($filePath, $localPath);
$progress->advance();
} elseif (is_dir($filePath)) {
// Add sub-directory.
$zipFile->addEmptyDir($localPath);
self::folderToZip($filePath, $zipFile, $exclusiveLength, $progress);
switch ($args['type']) {
case 'message':
$this->output->writeln($args['message']);
break;
case 'progress':
if ($args['complete']) {
$this->progress->finish();
} else {
$this->progress->advance();
}
}
break;
}
closedir($handle);
}
}

View File

@@ -2,6 +2,7 @@
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;
@@ -13,6 +14,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class CleanCommand extends Command
{
use ConsoleTrait;
/**
* @var array
@@ -66,7 +68,6 @@ class CleanCommand extends Command
'vendor/filp/whoops/.scrutinizer.yml',
'vendor/filp/whoops/.travis.yml',
'vendor/filp/whoops/phpunit.xml.dist',
'vendor/filp/whoops/src/deprecated',
'vendor/gregwar/image/Gregwar/Image/composer.json',
'vendor/gregwar/image/Gregwar/Image/phpunit.xml',
'vendor/gregwar/image/Gregwar/Image/.gitignore',
@@ -178,27 +179,14 @@ class CleanCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// Create a red output option
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
$this->cleanPaths($output);
$this->setupConsole($input, $output);
$this->cleanPaths();
}
// loops over the array of paths and deletes the files/folders
/**
* @param OutputInterface $output
*/
private function cleanPaths(OutputInterface $output)
private function cleanPaths()
{
$output->writeln('');
$output->writeln('<red>DELETING</red>');
$this->output->writeln('');
$this->output->writeln('<red>DELETING</red>');
$anything = false;
@@ -207,16 +195,16 @@ class CleanCommand extends Command
if (is_dir($path) && @Folder::delete($path)) {
$anything = true;
$output->writeln('<red>dir: </red>' . $path);
$this->output->writeln('<red>dir: </red>' . $path);
} elseif (is_file($path) && @unlink($path)) {
$anything = true;
$output->writeln('<red>file: </red>' . $path);
$this->output->writeln('<red>file: </red>' . $path);
}
}
if (!$anything) {
$output->writeln('');
$output->writeln('<green>Nothing to clean...</green>');
$this->output->writeln('');
$this->output->writeln('<green>Nothing to clean...</green>');
}
}

View File

@@ -2,6 +2,7 @@
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;
@@ -14,6 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class ClearCacheCommand extends Command
{
use ConsoleTrait;
/**
*
@@ -38,40 +40,33 @@ class ClearCacheCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// Create a red output option
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
$this->cleanPaths($input, $output);
$this->setupConsole($input, $output);
$this->cleanPaths();
}
// loops over the array of paths and deletes the files/folders
/**
* @param InputInterface $input
* @param OutputInterface $output
* loops over the array of paths and deletes the files/folders
*/
private function cleanPaths(InputInterface $input, OutputInterface $output)
private function cleanPaths()
{
$output->writeln('');
$output->writeln('<magenta>Clearing cache</magenta>');
$output->writeln('');
$this->output->writeln('');
$this->output->writeln('<magenta>Clearing cache</magenta>');
$this->output->writeln('');
if ($input->getOption('all')) {
if ($this->input->getOption('all')) {
$remove = 'all';
} elseif ($input->getOption('assets-only')) {
} elseif ($this->input->getOption('assets-only')) {
$remove = 'assets-only';
} elseif ($input->getOption('images-only')) {
} elseif ($this->input->getOption('images-only')) {
$remove = 'images-only';
} elseif ($input->getOption('cache-only')) {
} elseif ($this->input->getOption('cache-only')) {
$remove = 'cache-only';
} else {
$remove = 'standard';
}
foreach (Cache::clearCache($remove) as $result) {
$output->writeln($result);
$this->output->writeln($result);
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Grav\Console\Cli;
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;
use Symfony\Component\Yaml\Yaml;
/**
* Class ComposerCommand
* @package Grav\Console\Cli
*/
class ComposerCommand extends Command
{
use ConsoleTrait;
/**
* @var
*/
protected $config;
/**
* @var
*/
protected $local_config;
/**
* @var
*/
protected $destination;
/**
* @var
*/
protected $user_path;
/**
*
*/
protected function configure()
{
$this
->setName("composer")
->addOption(
'install',
'i',
InputOption::VALUE_NONE,
'install the dependencies'
)
->addOption(
'update',
'u',
InputOption::VALUE_NONE,
'update the dependencies'
)
->setDescription("Updates the composer vendordependencies needed by Grav.")
->setHelp('The <info>composer</info> command updates the composer vendordependencies needed by Grav');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$action = 'update';
if ($input->getOption('install')) {
$action = 'install';
}
// Updates composer first
$output->writeln("\nInstalling vendor dependencies");
$output->writeln($this->composerUpdate(GRAV_ROOT, $action));
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace Grav\Console\Cli;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
@@ -15,7 +16,7 @@ use Symfony\Component\Yaml\Yaml;
*/
class InstallCommand extends Command
{
use ConsoleTrait;
/**
* @var
*/
@@ -64,6 +65,7 @@ class InstallCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$dependencies_file = '.dependencies';
$local_config_file = exec('eval echo ~/.grav/config');
@@ -73,12 +75,6 @@ class InstallCommand extends Command
$this->destination = rtrim($this->destination, DS) . DS;
$this->user_path = $this->destination . USER_PATH;
// Create a red output option
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
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>');
@@ -98,11 +94,11 @@ class InstallCommand extends Command
if (!$input->getOption('symlink')) {
// Updates composer first
$output->writeln("\nInstalling vendor dependencies");
$output->writeln(system('php bin/composer.phar --working-dir="'.$this->destination.'" --no-interaction update'));
$output->writeln($this->composerUpdate(GRAV_ROOT, 'install'));
$this->gitclone($output);
$this->gitclone();
} else {
$this->symlink($output);
$this->symlink();
}
} else {
$output->writeln('<red>ERROR</red> invalid YAML in ' . $dependencies_file);
@@ -111,45 +107,43 @@ class InstallCommand extends Command
}
// loops over the array of paths and deletes the files/folders
/**
* @param OutputInterface $output
* Clones from Git
*/
private function gitclone(OutputInterface $output)
private function gitclone()
{
$output->writeln('');
$output->writeln('<green>Cloning Bits</green>');
$output->writeln('============');
$output->writeln('');
$this->output->writeln('');
$this->output->writeln('<green>Cloning Bits</green>');
$this->output->writeln('============');
$this->output->writeln('');
foreach ($this->config['git'] as $repo => $data) {
$path = $this->destination . DS . $data['path'];
if (!file_exists($path)) {
exec('cd ' . $this->destination . ' && git clone -b ' . $data['branch'] . ' ' . $data['url'] . ' ' . $data['path']);
$output->writeln('<green>SUCCESS</green> cloned <magenta>' . $data['url'] . '</magenta> -> <cyan>' . $path . '</cyan>');
$output->writeln('');
exec('cd "' . $this->destination . '" && git clone -b ' . $data['branch'] . ' ' . $data['url'] . ' ' . $data['path']);
$this->output->writeln('<green>SUCCESS</green> cloned <magenta>' . $data['url'] . '</magenta> -> <cyan>' . $path . '</cyan>');
$this->output->writeln('');
} else {
$output->writeln('<red>' . $path . ' already exists, skipping...</red>');
$output->writeln('');
$this->output->writeln('<red>' . $path . ' already exists, skipping...</red>');
$this->output->writeln('');
}
}
}
// loops over the array of paths and deletes the files/folders
/**
* @param OutputInterface $output
* Symlinks
*/
private function symlink(OutputInterface $output)
private function symlink()
{
$output->writeln('');
$output->writeln('<green>Symlinking Bits</green>');
$output->writeln('===============');
$output->writeln('');
$this->output->writeln('');
$this->output->writeln('<green>Symlinking Bits</green>');
$this->output->writeln('===============');
$this->output->writeln('');
if (!$this->local_config) {
$output->writeln('<red>No local configuration available, aborting...</red>');
$output->writeln('');
$this->output->writeln('<red>No local configuration available, aborting...</red>');
$this->output->writeln('');
return;
}
@@ -161,15 +155,15 @@ class InstallCommand extends Command
if (file_exists($from)) {
if (!file_exists($to)) {
symlink($from, $to);
$output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
$output->writeln('');
$this->output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
$this->output->writeln('');
} else {
$output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
$output->writeln('');
$this->output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
$this->output->writeln('');
}
} else {
$output->writeln('<red>source: ' . $from . ' does not exists, skipping...</red>');
$output->writeln('');
$this->output->writeln('<red>source: ' . $from . ' does not exists, skipping...</red>');
$this->output->writeln('');
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace Grav\Console\Cli;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
@@ -14,6 +15,7 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class NewProjectCommand extends Command
{
use ConsoleTrait;
/**
*
@@ -45,6 +47,7 @@ class NewProjectCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$sandboxCommand = $this->getApplication()->find('sandbox');
$installCommand = $this->getApplication()->find('install');

View File

@@ -2,7 +2,7 @@
namespace Grav\Console\Cli;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Utils;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
@@ -16,10 +16,13 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class SandboxCommand extends Command
{
use ConsoleTrait;
/**
* @var array
*/
protected $directories = array(
'/backup',
'/cache',
'/logs',
'/images',
@@ -66,22 +69,8 @@ class SandboxCommand extends Command
protected $default_file = "---\ntitle: HomePage\n---\n# HomePage\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque porttitor eu felis sed ornare. Sed a mauris venenatis, pulvinar velit vel, dictum enim. Phasellus ac rutrum velit. Nunc lorem purus, hendrerit sit amet augue aliquet, iaculis ultricies nisl. Suspendisse tincidunt euismod risus, quis feugiat arcu tincidunt eget. Nulla eros mi, commodo vel ipsum vel, aliquet congue odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque velit orci, laoreet at adipiscing eu, interdum quis nibh. Nunc a accumsan purus.";
/**
* @var
*/
protected $source;
/**
* @var
*/
protected $destination;
/**
* @var InputInterface $input
*/
protected $input;
/**
* @var OutputInterface $output
*/
protected $output;
/**
*
@@ -114,14 +103,8 @@ class SandboxCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$this->destination = $input->getArgument('destination');
$this->input = $input;
$this->output = $output;
// Create a red output option
$this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
$this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
$this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
// Symlink the Core Stuff
if ($input->getOption('symlink')) {
@@ -189,7 +172,7 @@ class SandboxCommand extends Command
$to = $this->destination . $target;
$this->output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
Utils::rcopy($from, $to);
Folder::rcopy($from, $to);
}
}
@@ -269,7 +252,7 @@ class SandboxCommand extends Command
if (count($pages_files) == 0) {
$destination = $this->source . '/user/pages';
Utils::rcopy($destination, $pages_dir);
Folder::rcopy($destination, $pages_dir);
$this->output->writeln(' <cyan>' . $destination . '</cyan> <comment>-></comment> Created');
}
@@ -281,7 +264,7 @@ class SandboxCommand extends Command
private function perms()
{
$this->output->writeln('');
$this->output->writeln('<comment>Permisions Initializing</comment>');
$this->output->writeln('<comment>Permissions Initializing</comment>');
$dir_perms = 0755;

View File

@@ -2,6 +2,7 @@
namespace Grav\Console;
use Grav\Common\GravTrait;
use Grav\Common\Composer;
use Grav\Console\Cli\ClearCacheCommand;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\ArrayInput;
@@ -83,6 +84,13 @@ trait ConsoleTrait
}
}
public function composerUpdate($path, $action = 'install')
{
$composer = Composer::getComposerExecutor();
return system($composer . ' --working-dir="'.$path.'" --no-interaction --no-dev --prefer-dist -o '. $action);
}
/**
* @param array $all
*

View File

@@ -255,7 +255,7 @@ class InstallCommand extends Command
// Confirmation received, copy over the data
$this->output->writeln(" |- Installing demo content... <green>ok</green> ");
Utils::rcopy($demo_dir, $dest_dir);
Folder::rcopy($demo_dir, $dest_dir);
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
@@ -359,8 +359,6 @@ class InstallCommand extends Command
{
$matches = $this->getGitRegexMatches($package);
$to = $this->destination . DS . $package->install_path;
$this->output->writeln("Preparing to Git clone <cyan>" . $package->name . "</cyan> from " . $matches[0]);
$this->output->write(" |- Checking destination... ");

View File

@@ -6,6 +6,7 @@ use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Common\GPM\Upgrader;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -84,9 +85,10 @@ class SelfupgradeCommand extends Command
$this->setupConsole($input, $output);
$this->upgrader = new Upgrader($this->input->getOption('force'));
$update = $this->upgrader->getAssets()['grav-update'];
$local = $this->upgrader->getLocalVersion();
$remote = $this->upgrader->getRemoteVersion();
$update = $this->upgrader->getAssets()->{'grav-update'};
$release = strftime('%c', strtotime($this->upgrader->getReleaseDate()));
if (!$this->upgrader->isUpgradable()) {
@@ -94,6 +96,9 @@ class SelfupgradeCommand extends Command
exit;
}
// not used but preloaded just in case!
new ArrayInput([]);
$questionHelper = $this->getHelper('question');
$skipPrompt = $this->input->getOption('all-yes');
@@ -110,10 +115,10 @@ class SelfupgradeCommand extends Command
$this->output->writeln("");
foreach ($changelog as $version => $log) {
$title = $version . ' [' . $log->date . ']';
$title = $version . ' [' . $log['date'] . ']';
$content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) {
return "\n" . ucfirst($match[1]) . ":";
}, $log->content);
}, $log['content']);
$this->output->writeln($title);
$this->output->writeln(str_repeat('-', strlen($title)));
@@ -138,7 +143,7 @@ class SelfupgradeCommand extends Command
$this->output->writeln("");
$this->output->writeln("Preparing to upgrade to v<cyan>$remote</cyan>..");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update->size) . "]... 0%");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update['size']) . "]... 0%");
$this->file = $this->download($update);
$this->output->write(" |- Installing upgrade... ");
@@ -164,17 +169,17 @@ class SelfupgradeCommand extends Command
private function download($package)
{
$this->tmp = CACHE_DIR . DS . 'tmp/Grav-' . uniqid();
$output = Response::get($package->download, [], [$this, 'progress']);
$output = Response::get($package['download'], [], [$this, 'progress']);
Folder::mkdir($this->tmp);
$this->output->write("\x0D");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package->size) . "]... 100%");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package['size']) . "]... 100%");
$this->output->writeln('');
file_put_contents($this->tmp . DS . $package->name, $output);
file_put_contents($this->tmp . DS . $package['name'], $output);
return $this->tmp . DS . $package->name;
return $this->tmp . DS . $package['name'];
}
/**

View File

@@ -110,14 +110,14 @@ class UninstallCommand extends Command
$this->output->write(" |- Checking destination... ");
$checks = $this->checkDestination($package);
$checks = $this->checkDestination($slug, $package);
if (!$checks) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
$this->output->write(" |- Uninstalling package... ");
$uninstall = $this->uninstallPackage($package);
$uninstall = $this->uninstallPackage($slug, $package);
if (!$uninstall) {
$this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
@@ -135,12 +135,14 @@ class UninstallCommand extends Command
/**
* @param $slug
* @param $package
*
* @return bool
*/
private function uninstallPackage($package)
private function uninstallPackage($slug, $package)
{
$path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug);
$path = self::getGrav()['locator']->findResource($package->package_type . '://' .$slug);
Installer::uninstall($path);
$errorCode = Installer::lastErrorCode();
@@ -159,15 +161,17 @@ class UninstallCommand extends Command
return true;
}
/**
* @param $slug
* @param $package
*
* @return bool
*/
private function checkDestination($package)
private function checkDestination($slug, $package)
{
$path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug);
$path = self::getGrav()['locator']->findResource($package->package_type . '://' . $slug);
$questionHelper = $this->getHelper('question');
$skipPrompt = $this->input->getOption('all-yes');

View File

@@ -160,7 +160,7 @@ class UpdateCommand extends Command
$commandExec = $installCommand->run($args, $this->output);
if ($commandExec != 0) {
$this->output->writeln("<red>Error:</red> An error occured while trying to install the extensions");
$this->output->writeln("<red>Error:</red> An error occurred while trying to install the extensions");
exit;
}

View File

@@ -1,51 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<defaultDocument>
<files>
<remove value="index.php" />
<add value="index.php" />
</files>
</defaultDocument>
<rewrite>
<rules>
<rule name="request_filename" stopProcessing="true">
<match url="." ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
<rule name="user_accounts" stopProcessing="true">
<match url="^user/accounts/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="user_config" stopProcessing="true">
<match url="^user/config/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="user_error_redirect" stopProcessing="true">
<match url="^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="cache" stopProcessing="true">
<match url="^cache/(.*)" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="bin" stopProcessing="true">
<match url="^bin/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="system" stopProcessing="true">
<match url="^system/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="vendor" stopProcessing="true">
<match url="^vendor/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<defaultDocument>
<files>
<remove value="index.php" />
<add value="index.php" />
</files>
</defaultDocument>
<rewrite>
<rules>
<rule name="request_filename" stopProcessing="true">
<match url="." ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
<rule name="user_accounts" stopProcessing="true">
<match url="^user/accounts/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="user_config" stopProcessing="true">
<match url="^user/config/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="user_error_redirect" stopProcessing="true">
<match url="^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="cache" stopProcessing="true">
<match url="^cache/(.*)" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="bin" stopProcessing="true">
<match url="^bin/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="backup" stopProcessing="true">
<match url="^backup/(.*)" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="system" stopProcessing="true">
<match url="^system/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="vendor" stopProcessing="true">
<match url="^vendor/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>