Compare commits

...

744 Commits

Author SHA1 Message Date
Andy Miller
846a0baed8 Merge branch 'release/0.9.16' 2015-01-30 18:13:37 -07:00
Andy Miller
cb8ea7780f version update 2015-01-30 18:13:25 -07:00
Andy Miller
5400bd3951 Added a page override support to media debug 2015-01-30 17:38:43 -07:00
Andy Miller
03521cd3f6 Some performance optimizations 2015-01-29 17:22:55 -07:00
Andy Miller
1eb0e37214 Merge pull request #133 from Sommerregen/feature/merge-config-and-disable-function-in-plugin.php
Added merge config and disable function in "Plugin.php"
2015-01-29 13:02:23 -07:00
Andy Miller
3e769618ec some cleanup refactoring 2015-01-29 10:40:30 -07:00
Andy Miller
9319d5c0aa PSR fixes 2015-01-29 10:27:10 -07:00
Andy Miller
eb94940df6 Merge pull request #132 from Sommerregen/feature/summary-options-and-query-support-in-assets
Added 'enabled' option for summary and fixed query support in assets
2015-01-29 10:23:59 -07:00
Sommerregen
a4c8c53939 Added 'enabled' option for summary and fixed query support in assets 2015-01-29 16:57:46 +01:00
Sommerregen
567dd0d2c6 Added merge config and disable function in "Plugin.php" 2015-01-29 16:51:17 +01:00
Andy Miller
25c44816c0 moved cached images from image:// to cache://images 2015-01-28 10:01:20 -07:00
Andy Miller
1a692b348e some minor cleanup 2015-01-27 21:27:42 -07:00
Andy Miller
17ed48e677 PSR Fixes 2015-01-27 17:37:58 -07:00
Andy Miller
685033bb02 Went back to using regular image:// stream 2015-01-27 17:31:41 -07:00
Andy Miller
b86c982ba1 Updated to use new image cache stream and add width/height back to link 2015-01-27 17:24:21 -07:00
Andy Miller
a0148f36fd added image cache stream 2015-01-27 17:22:06 -07:00
Andy Miller
b88de0cd3b Merge pull request #131 from namaless/bugfix/medium_fix_locator_unused_vars_phpdoc
Bug fixes phpdoc/missed vars/locator.
2015-01-27 17:00:11 -07:00
Pereira Ricardo
3207efd383 Add again the width and height params for lightbox method. 2015-01-28 00:55:12 +01:00
Pereira Ricardo
31dd235b50 Add missed streams for images and system folders. Fixed problem with images link when you use user/images folder instead /images folder. 2015-01-28 00:39:24 +01:00
Pereira Ricardo
bb1cdb17f5 Typo fix. 2015-01-27 23:40:41 +01:00
Pereira Ricardo
f35287cb7a - Removed unused variables.
- Add missed phpdoc params.
- Update constants with locator.
2015-01-27 23:36:43 +01:00
Andy Miller
391f7d3be4 Merge pull request #130 from Gertt/feature-responsive-images
Feature responsive images
2015-01-27 13:35:32 -07:00
Gert
76d0583b00 prevent double overlays on scaled media generation 2015-01-27 21:29:14 +01:00
Gert
7857568c92 update images 2015-01-27 21:28:59 +01:00
Gert
bfe94bc5d0 add option to show debug overlay on images 2015-01-27 21:18:16 +01:00
Gert
cf3bcf6d7f fix double base url 2015-01-26 23:21:47 +01:00
Gert
d2aa775ee8 make cache names of generated images more informative by adding correct @#x suffix 2015-01-26 21:45:10 +01:00
Gert
ce800ccd92 fix lightbox issue 2015-01-26 21:31:47 +01:00
Gert
2c57453608 don't output srcset when only one image 2015-01-26 21:30:17 +01:00
Gert
03f729602b refactor the way parsedown gets image data, this also fixes lightbox operations for markdown 2015-01-26 18:57:54 +01:00
Andy Miller
21f064b1d0 updated parsedown extra lib 2015-01-25 22:12:46 -07:00
Gert
b7f6b827e4 fix lightbox for responsive images 2015-01-26 03:18:17 +01:00
Gert
a33e2ed226 fix empty alternatives bug 2015-01-26 02:45:14 +01:00
Gert
ef7806b509 forgot to save after fixing merge conflict 2015-01-26 02:43:52 +01:00
Gert
e268cda1b5 Merge branch 'develop' of github.com:getgrav/grav into feature-responsive-images
* 'develop' of github.com:getgrav/grav:
  fix for #129 markdown images not found when base_url == '/' on homepage

Conflicts:
	system/src/Grav/Common/Markdown/ParsedownGravTrait.php
2015-01-26 02:42:48 +01:00
Gert
3ae5e3c569 refactor and create missing versions of medium 2015-01-26 02:40:34 +01:00
Andy Miller
304c7519d1 fix for #129 markdown images not found when base_url == '/' on homepage 2015-01-25 18:33:24 -07:00
Gert
a4a8a422a5 fix for base url error 2015-01-25 23:48:58 +01:00
Gert
6700003dd2 generate @1x image when user provides only @2x 2015-01-25 23:11:23 +01:00
Gert
93f99fcff1 refactor image parsedown and correct use of reset 2015-01-25 23:07:24 +01:00
Gert
a03f54902a make sure param is int 2015-01-25 23:06:47 +01:00
Gert
4112d363dd get filters working on srcset 2015-01-25 20:40:29 +01:00
Gert
3f1c6dd662 implement srcset on all output functions and add data-attribute for lightbox plugins to use 2015-01-25 19:25:47 +01:00
Gert
89b2da636f Merge branch 'develop' of github.com:getgrav/grav into feature-responsive-images
* 'develop' of github.com:getgrav/grav:
  Added logic so markdown link will work with other non-self pages
  fixed bug with image dispatch for traditionally _non-routable_ pages, such as `image/foo.jpg`
2015-01-25 15:47:29 +01:00
Gert
c010ae3a97 switch to new srcset spec 2015-01-25 15:47:23 +01:00
Andy Miller
f619c8f49f Added logic so markdown link will work with other non-self pages 2015-01-24 17:28:20 -07:00
Andy Miller
a27a1a3fd1 fixed bug with image dispatch for traditionally _non-routable_ pages, such as image/foo.jpg 2015-01-24 16:59:20 -07:00
Gert
f7470ace97 basic srcset implementation 2015-01-24 17:36:49 +01:00
Andy Miller
cb6b362e20 Merge branch 'release/0.9.15' 2015-01-23 17:22:57 -07:00
Andy Miller
c6200f386a Merge branch 'release/0.9.15' into develop 2015-01-23 17:22:57 -07:00
Andy Miller
60423e4a28 version update 2015-01-23 17:22:41 -07:00
Andy Miller
f40410816e updated changelog 2015-01-23 17:21:58 -07:00
Andy Miller
63a2ffc0b1 fix for markdown images/links in pages with slug override 2015-01-23 17:09:37 -07:00
Andy Miller
fad428b94b Fix for broken image last-ditch-effort mechanism 2015-01-23 16:44:39 -07:00
Andy Miller
8b251ca350 Part 2 of greedy fix 2015-01-23 16:37:20 -07:00
Andy Miller
05bd5cb964 Fix for aggressive and greedy regex stripping numbers after / when should only strip order 2015-01-23 16:17:02 -07:00
Andy Miller
76a6b77065 fixed typo in mime types 2015-01-23 15:57:54 -07:00
Andy Miller
5049086062 Fix to pickup deprecated markdown_extra system setting. 2015-01-23 14:46:34 -07:00
Andy Miller
f04dece44c Merge branch 'release/0.9.14' 2015-01-23 13:08:47 -07:00
Andy Miller
621f38d856 Merge branch 'release/0.9.14' into develop 2015-01-23 13:08:47 -07:00
Andy Miller
ccc65aa420 version update 2015-01-23 13:08:35 -07:00
Andy Miller
7ddf35d3fa updated changelog 2015-01-23 12:34:18 -07:00
Andy Miller
689c65c5e4 bumped parsedown extra lib version 2015-01-22 17:47:05 -07:00
Andy Miller
e571f3bcfd Added some missing clean statements 2015-01-22 17:44:32 -07:00
Andy Miller
adf80c3f87 fix for change in slash trimming in Resource Locator 2015-01-22 16:50:35 -07:00
Andy Miller
fed9862e44 set auto_link_breaks to false by default 2015-01-21 22:07:37 -07:00
Andy Miller
7bc924b6d6 Added ability to configure markdown options in configuration and in page 2015-01-21 21:58:51 -07:00
Andy Miller
c881624801 Added ability to configure the special characters that are automatically converted in Parsedown 2015-01-21 21:57:55 -07:00
Andy Miller
3c63993db8 Some pretty serious page refactoring to clean up a rather unreadable chunk of code in content() method 2015-01-21 18:21:39 -07:00
Andy Miller
ac26c3ab4e added ability to set metadata 2015-01-21 15:36:05 -07:00
Andy Miller
2dedd9d1a0 Refactored Parsedown override class names 2015-01-21 13:23:53 -07:00
Andy Miller
c1ef8b6399 ENT_QUOTES works better than ENT_HTML5, so changing 2015-01-21 13:23:15 -07:00
Andy Miller
c2f2f7b009 fix for quotes in metadata 2015-01-21 12:22:29 -07:00
Andy Miller
c2c5afbb05 Added new onPageContentRaw() event that processes before markdown 2015-01-21 11:14:52 -07:00
Andy Miller
19eae2300f Merge branch 'feature/process_ordering' into develop 2015-01-20 18:08:41 -07:00
Andy Miller
0ddd2941e9 Add event when caching markdown content too 2015-01-20 18:08:14 -07:00
Andy Miller
6863d70f0b Merge pull request #126 from namaless/develop
Update User.php
2015-01-20 16:27:23 -07:00
Pereira Ricardo
9f782a4aed Update User.php 2015-01-20 21:11:13 +01:00
Matias Griese
bc844ba0d3 Merge pull request #125 from getgrav/revert-124-hotfix/user_class_streams
Revert "Replace constant with streams. (need for multisite)."
2015-01-20 19:43:09 +02:00
Andy Miller
af97876794 some initial refactoring 2015-01-20 10:42:13 -07:00
Matias Griese
92f319c188 Revert "Replace constant with streams. (need for multisite)." 2015-01-20 19:36:38 +02:00
Matias Griese
010872fc1e Merge pull request #124 from namaless/hotfix/user_class_streams
Replace constant with streams. (need for multisite).
2015-01-20 19:35:40 +02:00
Matias Griese
259d8356b0 Update url() method in twig 2015-01-20 19:23:05 +02:00
Andy Miller
4cd4bb4202 fix for non-url valid twig code in Markdown Link/Image 2015-01-20 09:35:30 -07:00
Pereira Ricardo
fdb19b1a90 Replace constant with streams. (need for multisite). 2015-01-20 11:07:37 +01:00
Andy Miller
c9385632a9 cleanup 2015-01-19 15:32:57 -07:00
Andy Miller
0c90b5c7b2 assets typo 2015-01-19 15:32:45 -07:00
Andy Miller
ac4650632e Merge branch 'rindeal-feature/introduce_phpunit' into develop 2015-01-19 15:21:55 -07:00
Andy Miller
aae644ca33 Merge branch 'feature/introduce_phpunit' of https://github.com/rindeal/grav into rindeal-feature/introduce_phpunit
Conflicts:
	composer.json
2015-01-19 15:21:10 -07:00
Andy Miller
6aabb83204 minor perf optimizations 2015-01-19 15:16:36 -07:00
Andy Miller
593f7bfe27 Merge pull request #115 from namaless/feature/inline_assets_priority
Add inline assets priority
2015-01-19 15:12:05 -07:00
Andy Miller
77d1bf5ca8 Merge pull request #118 from namaless/fix/typo
Fix small spellchecker
2015-01-19 15:06:39 -07:00
Andy Miller
bc8f742565 broke out resource into a variable 2015-01-19 15:02:33 -07:00
Andy Miller
632c48e1e6 Updated parsedown extra lib 2015-01-19 14:36:05 -07:00
Andy Miller
42b2f99c6d RE PR: #119 - Refactored a bit to be better optimized and also take into account $pages->base() 2015-01-19 14:35:52 -07:00
Pereira Ricardo
e518e3ca92 Fix small spellchecker 2015-01-18 03:22:51 +01:00
Pereira Ricardo
e013cf70db Add inline assets priority
Use md5 from content for discard multiple iterance.
2015-01-18 03:14:09 +01:00
Djamil Legato
bdf80fd920 Cherry-pick of #112 2015-01-16 14:44:25 -08:00
Andy Miller
ce282c47cf updated changelog 2015-01-16 13:39:06 -07:00
Andy Miller
23a25b8df2 Added a gzip param - default to off 2015-01-16 13:38:56 -07:00
Andy Miller
8363da35c6 updated parsedown-extra 2015-01-16 13:38:43 -07:00
Andy Miller
ff973a0634 Added Gzip support and fix for php-fpm connections 2015-01-16 13:38:30 -07:00
Andy Miller
2b504149f8 updates for changelog 2015-01-16 11:14:34 -07:00
Andy Miller
031df8de2c Added optional all param to pages.find() 2015-01-16 11:14:18 -07:00
Andy Miller
229d3acc99 moved rewrite base before exploits in .htaccess 2015-01-16 11:13:47 -07:00
Andy Miller
195bdd71a2 Updated Parsedown + ParsedownExtra to latest stable version 2015-01-12 17:29:44 -07:00
Andy Miller
f6da7c9344 Fixes for most recent Parsedown + ParsedownExtra 2015-01-12 17:29:15 -07:00
Andy Miller
50ebdeddad Revert "forgot to change this one back"
This reverts commit 4f4974aae4.
2015-01-11 16:00:14 -07:00
Andy Miller
4f4974aae4 forgot to change this one back 2015-01-11 15:57:55 -07:00
Andy Miller
977b7d2936 Force modular pages to be non-visible in menus 2015-01-11 15:23:22 -07:00
Andy Miller
c4797a2d6f Rolled back previous MarkdownTrait until next Parsedown release 2015-01-11 12:14:43 -07:00
Andy Miller
e293050ea1 Rolled back Parsedown to 1.1.4 and ParsedownExtra to 0.2.6 until next Parsedown Release 2015-01-11 12:14:22 -07:00
Andy Miller
4c2ed0ee3d Updated included composer package 2015-01-11 11:54:55 -07:00
Andy Miller
c550faa843 fix for published setting having precedents over publish_date and unpublish_date 2015-01-11 11:54:40 -07:00
Andy Miller
e433150a5a Use page:// stream rather than PAGES_DIR constant 2015-01-10 16:07:33 -07:00
Andy Miller
ceb3626c94 More fixes for markdown images 2015-01-10 15:58:44 -07:00
Andy Miller
2d33f745ba fix for bug cause by commit: 6e39107d45 2015-01-10 15:58:24 -07:00
Andy Miller
4cf0a71a45 fixes for Parsedown update 2015-01-10 14:31:03 -07:00
Matias Griese
7f526cea76 Fix fixed path in theme_url Twig variable 2015-01-10 17:55:28 +02:00
Matias Griese
6e39107d45 Add support for path prefixes like /en for multisite/multilanguage support 2015-01-10 17:54:09 +02:00
Matias Griese
9ddfced969 Fix a bug with missing streams when configuration was not loaded from cache 2015-01-10 14:23:16 +02:00
Matias Griese
dde6b2cd8b Add support for multiple configurations by setup.php 2015-01-10 11:55:57 +02:00
Matias Griese
a9894902be Fix RewriteBase getting run only after exploit checks in .htaccess 2015-01-10 11:55:01 +02:00
Andy Miller
62c668b32c Merge branch 'release/0.9.13' 2015-01-09 15:44:07 -07:00
Andy Miller
f48c8fb50a Merge branch 'release/0.9.13' into develop 2015-01-09 15:44:07 -07:00
Andy Miller
0ac7c4b6bf version update 2015-01-09 15:43:55 -07:00
Andy Miller
5bdf22764f Added a copy() method on collections 2015-01-09 13:35:05 -07:00
Andy Miller
f48a05e247 Added custom header. query type with default support 2015-01-09 13:26:18 -07:00
Andy Miller
dc0b7cd3d2 removed some unused commands 2015-01-09 13:25:46 -07:00
Andy Miller
d40a71757b fix and optimize the cache lifetime settings 2015-01-09 08:06:34 -07:00
Andy Miller
bd702b4c21 support publish_date and unpublish_date page headers 2015-01-09 08:06:11 -07:00
Andy Miller
b843167d6b set default cache lifetime to 1 week 2015-01-09 08:04:49 -07:00
Andy Miller
be7517def5 renamed publish system param 2015-01-09 08:04:31 -07:00
Andy Miller
b3f085c27d Support markdown extra classes on lightbox 2015-01-09 07:15:16 -07:00
Andy Miller
c7647ef20b Merge branch 'feature/published_state' into develop 2015-01-08 19:13:33 -07:00
Andy Miller
c56471fe73 added functionality to set cache lifetime to force refresh when a page is due to be published 2015-01-08 18:15:51 -07:00
Andy Miller
c87279c350 removed a nefarious chunk of code that was causing maleficent issues 2015-01-08 16:59:28 -07:00
Andy Miller
ed4040c6d0 cleanup of published support 2015-01-08 16:58:17 -07:00
Andy Miller
7345c75ac0 Added dateRange support to @ Collection definitions 2015-01-08 16:57:41 -07:00
Andy Miller
3b7bd3bb71 Collection methods modify 'self' and return self for chaining 2015-01-08 16:54:53 -07:00
Andy Miller
9f2ecfadf7 added new dateRange() method for Collection 2015-01-08 16:54:07 -07:00
Andy Miller
f7fa6f114d setting and code to unpublish pages with future dates 2015-01-07 18:51:33 -07:00
Andy Miller
8c99758a7a some error handling in collection when item is not in the collection (i.e., unpublished) 2015-01-07 18:35:17 -07:00
Andy Miller
9614932e07 new helper methods on collection 2015-01-07 18:34:51 -07:00
Andy Miller
b8715d9730 don't store taxonomy for unpublished pages 2015-01-07 18:34:30 -07:00
Andy Miller
c9563ac6a3 removed filter logic, relying on collection methods now 2015-01-07 18:34:06 -07:00
Andy Miller
0b20bf1909 pretty much working again 2015-01-07 17:44:53 -07:00
Djamil Legato
45d39df057 Fixed uninstall 2015-01-07 16:39:40 -08:00
Andy Miller
b7ffd3c721 more WIP for published state 2015-01-07 10:21:22 -07:00
Andy Miller
70f15de701 Initial commit with published state in pages 2015-01-06 19:39:03 -07:00
Andy Miller
4446770dda Merge branch 'release/0.9.12' 2015-01-06 11:54:42 -07:00
Andy Miller
e2389db483 Merge branch 'release/0.9.12' into develop 2015-01-06 11:54:42 -07:00
Andy Miller
7be42080da version update 2015-01-06 11:54:31 -07:00
Andy Miller
22e550ab40 Proper fix: Put in non-exception throwing handler for undefined methods on Medium objects 2015-01-06 11:33:53 -07:00
Andy Miller
c515996adc Revert "Put in non-exception throwing handler for undefined methods on Medium objects"
This reverts commit 00971dee24.
2015-01-05 18:22:50 -07:00
Andy Miller
fb3ee1187d Merge pull request #105 from jeffdm/develop
Update composer.json to not use GitHub API for rockettheme
2015-01-05 13:06:12 -07:00
Jeffrey Morgan
8cbba11955 Update composer.json 2015-01-04 16:19:49 -08:00
Andy Miller
00971dee24 Put in non-exception throwing handler for undefined methods on Medium objects 2015-01-04 15:08:01 -07:00
Andy Miller
e8b7b40535 Fix for recalcitrant CodeKit 2015-01-04 14:25:39 -07:00
Andy Miller
15113d0acb Added configurable support for undefined Twig functions and filters (allowed by default) 2015-01-03 16:31:06 -07:00
Andy Miller
5bd79dc072 Added support for in-page twig processing in modular pages 2015-01-03 16:05:18 -07:00
Djamil Legato
0f75f6b1e1 Added GPM UninstallCommand - fixes #96 2015-01-01 15:01:50 -08:00
Djamil Legato
33229f0f26 Fixed argument description for InstallCommand 2015-01-01 13:01:40 -08:00
Djamil Legato
5bbedb40fc Displaying latest version rather than current when updating package via GPM 2015-01-01 12:58:50 -08:00
Andy Miller
d1164a14c1 added support for - as well as _ in plugin filenames 2014-12-30 17:13:08 -07:00
Andy Miller
ce4b227328 Merge pull request #97 from namaless/develop
Simple way to check the class name
2014-12-30 17:09:49 -07:00
Andy Miller
5b2de3795f fixed description for self-upgrade in GPM 2014-12-30 17:03:10 -07:00
Namaless
3b25cc1a79 Simple way to check the class name
Follow the comments here: a6e951a4dc
2014-12-29 12:50:53 +01:00
Andy Miller
a6e951a4dc Added support for plugin names without _ to make them PSR-1 friendly 2014-12-28 13:12:04 -07:00
Andy Miller
ea734567ea Added some fallback template handling if not using default .html extension 2014-12-22 10:53:31 -07:00
Andy Miller
d32ec013dd Added a default all-access robots.txt 2014-12-22 10:51:10 -07:00
Andy Miller
b568ad8f56 Merge branch 'release/0.9.11' 2014-12-21 20:56:38 -07:00
Andy Miller
04541dac38 Merge branch 'release/0.9.11' into develop 2014-12-21 20:56:38 -07:00
Andy Miller
c8287d12fa version update 2014-12-21 20:56:24 -07:00
Andy Miller
8fdf517613 Fix attempt for invalid/missing user agent string 2014-12-21 17:48:45 -07:00
Andy Miller
0725a4729a fix for directory relative links and url fragments (#pagelink) 2014-12-21 12:02:49 -07:00
Andy Miller
a67b1f7350 fix for relative links with no subfolder in the base_url 2014-12-20 15:32:09 -07:00
Andy Miller
a7abd91868 Support for redirects not just routes 2014-12-16 22:21:01 -07:00
Andy Miller
7d9ea51ea5 Handle Twig errors more cleanly 2014-12-16 22:20:46 -07:00
Andy Miller
f284147b31 Merge branch 'release/0.9.10' 2014-12-12 10:25:24 -07:00
Andy Miller
0f71e1e795 Merge branch 'release/0.9.10' into develop 2014-12-12 10:25:24 -07:00
Andy Miller
14780c3bb1 version update 2014-12-12 10:25:14 -07:00
Andy Miller
1409b7284b moved clear-cache functionality into Cache object 2014-12-12 10:21:55 -07:00
Djamil Legato
c7e3b4d026 Typo 2014-12-10 13:19:49 -08:00
Andy Miller
139d9f8531 Added a useful 'nicetime' twig filter for Facebook style human readable dates "4 minutes ago" 2014-12-10 11:57:46 -07:00
Andy Miller
b9a569b71f Fix for prevSibling/nextSibling/isFirst/isLast when limit is imposed on parent collection 2014-12-08 15:30:29 -07:00
Andy Miller
8991af149e Merge branch 'release/0.9.9' 2014-12-05 11:37:58 -07:00
Andy Miller
fccebb83c2 Merge branch 'release/0.9.9' into develop 2014-12-05 11:37:58 -07:00
Andy Miller
dbfd2373fe version update 2014-12-05 11:33:30 -07:00
Andy Miller
2fda197eff Revert "added useful toArray() method on Iterator"
This reverts commit 92159d1df8.
2014-12-05 10:30:02 -07:00
Andy Miller
fbf09a7741 switching to Symfony 2.6 for yaml/console/event-dispatcher 2014-12-05 08:59:13 -07:00
Andy Miller
45e26c0936 refactored page prev/next/adjacent so they work! 2014-12-05 08:51:48 -07:00
Andy Miller
92159d1df8 added useful toArray() method on Iterator 2014-12-05 08:17:42 -07:00
Andy Miller
3ba491d02c PSR Fixes 2014-12-05 08:17:20 -07:00
Andy Miller
b3047f7156 updated composer 2014-12-04 18:36:28 -07:00
Andy Miller
ed818b55de Added a new @page collection type to take a parent page 2014-12-03 18:14:26 -07:00
Andy Miller
2a308d2a08 Fixed a broken getSibling() function on the page 2014-12-03 18:13:52 -07:00
Andy Miller
8fa8b55c7e Added 'contains' filter and 'gist' function 2014-12-03 18:13:04 -07:00
Andy Miller
08fc3918a7 added ksort filter 2014-12-02 15:21:40 -07:00
Andy Miller
32162532d5 Merge branch 'release/0.9.8' into develop 2014-12-01 17:06:32 -07:00
Andy Miller
d012c8ed2d Merge branch 'release/0.9.8' 2014-12-01 17:06:31 -07:00
Andy Miller
e66b89f08c trying again! 2014-12-01 17:06:16 -07:00
Andy Miller
94d019982b better fix for multiple cache that helps resolve broken images 2014-12-01 16:56:39 -07:00
Andy Miller
29d2eed5fb example wildcard route 2014-12-01 16:56:04 -07:00
Andy Miller
50940da168 Merge branch 'release/0.9.8' 2014-12-01 15:31:13 -07:00
Andy Miller
d1060940f0 Merge branch 'release/0.9.8' into develop 2014-12-01 15:31:13 -07:00
Andy Miller
a05df55b15 version update 2014-12-01 15:31:00 -07:00
Andy Miller
d5e71072c0 Should fix the double-caching issues - hopefully no repercussions! 2014-12-01 15:29:45 -07:00
Andy Miller
099589da90 set default cache back to unlimited 2014-12-01 15:29:06 -07:00
Andy Miller
8784372d41 implemented a simple wildcard routing solution 2014-12-01 14:03:39 -07:00
Andy Miller
f2f00bb09b fixed http_response_code 2014-11-30 22:51:24 -07:00
Andy Miller
7262fbac55 PSR fix 2014-11-30 21:02:30 -07:00
Andy Miller
bb0635c36f set status code base on page header 2014-11-30 17:50:41 -07:00
Andy Miller
a66ce64171 Added a configuration option to set a default lifetime on cache saves 2014-11-27 23:00:11 -07:00
Andy Miller
ea4690db3f minor cleanup 2014-11-26 07:11:35 -07:00
Andy Miller
47859d496e Ensure onPageContentProcessed only fires when not cached 2014-11-26 06:24:25 -07:00
Andy Miller
9d6cc2cbcc Removed old deprecated methods 2014-11-26 06:12:25 -07:00
Andy Miller
0b7d2e6d7e PSR fixes 2014-11-26 05:07:20 -07:00
Andy Miller
99783a6ab9 Ensure Twig tags are treated as block items in markdown 2014-11-26 04:53:35 -07:00
Andy Miller
1a5102e47b fix for summary miscalculation - take 2 2014-11-25 17:54:57 -07:00
Andy Miller
dd6986e5ff Fix for summary miscalculation 2014-11-25 17:08:31 -07:00
Andy Miller
05bea0bf50 PSR fixes 2014-11-25 17:08:12 -07:00
Andy Miller
96e8ab4610 Merge branch 'release/0.9.7' 2014-11-24 16:02:41 -07:00
Andy Miller
5626f56595 Merge branch 'release/0.9.7' into develop 2014-11-24 16:02:41 -07:00
Andy Miller
614b126939 version update 2014-11-24 16:02:23 -07:00
Andy Miller
d38a0973c6 Fix for non-valid assets shown up as empty tags 2014-11-24 11:51:25 -07:00
Andy Miller
a910568144 PSR fixes 2014-11-24 11:22:02 -07:00
Andy Miller
4a558ca89d removed problematic set_time_limit() call and put a check on ignore_user_abort() 2014-11-23 19:45:19 -07:00
Andy Miller
a45c31b952 Merge pull request #85 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2014-11-23 17:44:06 -07:00
The Gitter Badger
2abc5d82da Added Gitter badge 2014-11-23 05:18:37 +00:00
Andy Miller
1aec50ca65 comment fix 2014-11-21 11:35:47 -07:00
Andy Miller
3c24dcc5aa #84 fix for all URLs with 'schemes' not being converted to Grav internal URLs 2014-11-19 13:08:53 -07:00
Andy Miller
d3265baa4e Fixed JS assets that don't have ; at the end for pipeline. Also PSR fixes 2014-11-18 17:22:03 -07:00
Andy Miller
50cdf01fae nginx config update - thanks paulr 2014-11-18 16:28:47 -07:00
Andy Miller
36688c9451 Merge branch 'release/0.9.6' 2014-11-17 15:58:40 -07:00
Andy Miller
cbfd6936e6 Merge branch 'release/0.9.6' into develop 2014-11-17 15:58:40 -07:00
Andy Miller
2d3e452baf updated version and changelog 2014-11-17 15:58:26 -07:00
Andy Miller
6879a2d54c added filename/thumb/extension to medium properties 2014-11-17 13:55:09 -07:00
Andy Miller
a2aaa2ce79 force media array sorts to use natural sort ordering 2014-11-17 13:54:35 -07:00
Andy Miller
088075888e support uppercase image extensions 2014-11-17 13:54:13 -07:00
Andy Miller
16ce10a5c7 PSR formatting fixes 2014-11-17 13:53:51 -07:00
Andy Miller
87d16e36cb Upped the default compression from 80 to 85 2014-11-17 13:53:08 -07:00
Andy Miller
19fc443518 Fix for relative URLs in markdown on installs with no base_url 2014-11-17 11:14:05 -07:00
Matias Griese
376c436318 Move base_url variables from configuration into grav container 2014-11-14 20:31:46 +02:00
Andy Miller
edb102b1dd fixed hostname for config overrides 2014-11-13 21:49:46 -07:00
Matias Griese
dc28ba5d1a Fix cached configuration (part 2) 2014-11-13 21:16:44 +02:00
Matias Griese
91f43c1613 Do not save environmental variables system.base_url_absolute & system.base_url_relative 2014-11-13 21:08:10 +02:00
Andy Miller
318319731d Fix for infinite loop in content() 2014-11-12 19:19:33 -07:00
Andy Miller
4b46ea241c Merge branch 'release/0.9.5' 2014-11-09 19:20:39 -07:00
Andy Miller
2a66f6b441 Merge branch 'release/0.9.5' into develop 2014-11-09 19:20:39 -07:00
Andy Miller
60bbb32833 version update 2014-11-09 19:20:25 -07:00
Andy Miller
31063c5749 added helpful messages about pages cache state 2014-11-09 15:13:17 -07:00
Andy Miller
210c66de21 url public 2014-11-09 15:12:44 -07:00
Andy Miller
45525af846 Force route rebuild when config changes (needed when changing home alias) 2014-11-09 12:53:13 -07:00
Andy Miller
2c247a4529 Handle sub pages of 'home' menu item better 2014-11-09 12:52:23 -07:00
Andy Miller
2fac370741 Change paths() method to be more useful 2014-11-09 12:51:46 -07:00
Andy Miller
454b3a61e7 PSR code fixes 2014-11-09 12:51:18 -07:00
Andy Miller
376cc0c095 Added helpful message when theme can not be found 2014-11-08 23:31:18 -07:00
Andy Miller
21b1323d29 Fix for incorrect template name when throwing missing error 2014-11-08 23:23:08 -07:00
Andy Miller
fb549701ff Updated Vendor libs to latest 2014-11-07 18:34:51 -07:00
Andy Miller
d4cb85174c added new onPageContentProcessed() event that is post-content processing but pre-caching 2014-11-07 18:17:36 -07:00
Andy Miller
d596dd98dd PSR code tidy 2014-11-07 18:17:02 -07:00
Andy Miller
16ba82b795 Set output to public so it can be manipulated in the onOutputGenerated() event 2014-11-07 18:16:42 -07:00
Andy Miller
78279e4d7a Moved environment to Uri object 2014-11-06 18:27:48 -07:00
Andy Miller
699fade3d1 added some specific clear options to 'clear-cache' CLI command 2014-11-06 13:17:37 -07:00
Andy Miller
fdd94633a8 fix for markdown links with fragments and query elements 2014-11-06 12:43:19 -07:00
Andy Miller
2f1e0c6be2 Fix for data urls in CSS 2014-11-05 22:02:54 -07:00
Andy Miller
d0cded11fb Fix for inline CSS and JS when pipelining enabled 2014-11-05 21:30:44 -07:00
Andy Miller
8cd962e0f3 make comparison case insensitive 2014-11-05 16:22:00 -07:00
Andy Miller
9bb7d0c37b Added support for AND and OR taxonomy filtering. Default to AND (was OR) 2014-11-05 16:17:33 -07:00
Andy Miller
f41ba3172b removed extraneous manual cache disable 2014-11-05 16:17:07 -07:00
Andy Miller
aecdbaaa27 Added quality method to Medium to set the compression 2014-11-03 15:21:59 -07:00
Andy Miller
e572015913 Don't remove json/text errors handlers! 2014-11-01 22:18:51 -06:00
Andy Miller
e11c426c38 fix for 'installed undefined' error 2014-11-01 22:18:35 -06:00
Andy Miller
e2aff43963 Merge branch 'release/0.9.4' 2014-10-31 18:12:28 -06:00
Andy Miller
7551d0c69a Merge branch 'release/0.9.4' into develop 2014-10-31 18:12:28 -06:00
Andy Miller
7ac6e436fa New version 2014-10-31 18:11:55 -06:00
Andy Miller
10ca2ef3fd Merge branch 'feature/0.9.4_changelog' of https://github.com/getgrav/grav into release/0.9.4 2014-10-31 18:10:53 -06:00
Andy Miller
04e197f9cc Better safari support on error page 2014-10-30 16:36:34 -06:00
Andy Miller
f2638f17c3 More useful template missing error 2014-10-30 16:36:14 -06:00
Andy Miller
051a7e66f6 Added ability to enable/disable error display and logging 2014-10-30 16:19:05 -06:00
Matias Griese
ff1db8ece8 Improve cached configuration check 2014-10-30 15:35:14 +02:00
Matias Griese
3bfe32eda1 Fix a typo in configuration loading making configuration updates to do more work than needed 2014-10-30 15:27:53 +02:00
Andy Miller
588bd0168c Unique configuration/blueprints based on environment name 2014-10-29 15:09:43 -06:00
Andy Miller
e81980b6c0 removed commented out var_dump 2014-10-29 13:10:33 -06:00
Djamil Legato
e33693f224 Displaying packages not found when running Version command 2014-10-29 09:43:10 -07:00
Djamil Legato
74747844e4 Initializing streams for GPM CLI 2014-10-28 22:29:43 -07:00
Andy Miller
9b2221f702 initial 0.9.4 changelog 2014-10-28 18:39:33 -06:00
Andy Miller
cac8503c72 Turn off debugger by default 2014-10-28 17:12:58 -06:00
Andy Miller
7c37216d6a Merge branch 'feature/alternative_debugger' into develop 2014-10-28 15:58:32 -06:00
Andy Miller
359c33dae5 Merge branch 'develop' into feature/alternative_debugger
Conflicts:
	system/src/Grav/Common/TwigExtension.php
	system/src/Grav/Console/Cli/InstallCommand.php
	system/src/Grav/Console/Cli/SandboxCommand.php
2014-10-28 15:56:01 -06:00
Andy Miller
a9b517386c Added a default/sample htaccess 2014-10-28 14:18:09 -06:00
Andy Miller
7202766cb5 Several hatches improvements and unifications 2014-10-27 21:36:29 -06:00
Andy Miller
9bd62558c1 permissions 2014-10-27 18:46:51 -06:00
Andy Miller
ac8887129c added some missing files 2014-10-27 17:33:45 -06:00
Andy Miller
2d4eb6d364 removed redundant debugger fork reference 2014-10-27 17:17:22 -06:00
Andy Miller
046a50ffcd Added vendor to the mappings for copy/symlink 2014-10-26 19:24:02 -06:00
Andy Miller
fc863c560f moved composer update into non-symlink block 2014-10-26 19:23:34 -06:00
Andy Miller
9928b75d0d Fix for debugbar with in-page Twig rendering 2014-10-26 19:20:07 -06:00
Andy Miller
a54d116be3 removed unused vendor libs 2014-10-22 22:12:50 -06:00
Andy Miller
7046c104d6 additional vendor cleanups 2014-10-22 22:03:36 -06:00
Andy Miller
bb0ee34082 Tentatively added twig panel to debugbar - currently has issues with "processpage" twig calls used by modular 2014-10-22 21:14:11 -06:00
Andy Miller
8950e6661f check for html explicitly 2014-10-20 10:42:57 -07:00
Andy Miller
112dd6f4ae more stuff 2014-10-20 10:37:41 -07:00
Andy Miller
9426b42a33 other css tweaks 2014-10-19 18:47:14 -07:00
Andy Miller
e6bf5d9ea5 css text outline fix 2014-10-19 18:21:21 -07:00
Andy Miller
ce9e955f21 Added support for auto detecting environment configurations 2014-10-18 23:03:06 -07:00
Andy Miller
5025a430b6 Tested log rotation 2014-10-18 09:34:11 -07:00
Andy Miller
458f6cb55d Monolog added and logging exceptions 2014-10-17 23:16:24 -07:00
Andy Miller
b379b38fff Set Twig debug() method to use 'debug' notice level 2014-10-17 22:16:02 -07:00
Matias Griese
0711875200 Add {{ debug() }} and {{ dump() }} calls to twig 2014-10-16 14:47:26 +03:00
Matias Griese
767ac573af Add initial loading time to debugger timeline 2014-10-16 12:42:34 +03:00
Matias Griese
c96bacc320 Merge branch 'feature/alternative_debugger' of https://github.com/getgrav/grav into feature/alternative_debugger 2014-10-16 11:36:14 +03:00
Matias Griese
8aec9f7c15 Improve and optimize configuration loading 2014-10-16 11:34:17 +03:00
Andy Miller
a66fede72d wrapping fix 2014-10-15 21:34:32 -06:00
Matias Griese
5586c1923c Move Whoops code into its proper place and implement ajax support for json files 2014-10-15 11:56:09 +03:00
Andy Miller
01467e1b32 side panel now purple 2014-10-14 21:54:39 -06:00
Andy Miller
6be04d6406 Fix for non-transparent clipboard icon 2014-10-14 21:46:51 -06:00
Andy Miller
a7176c4a6e color styling for whoops 2014-10-14 19:05:42 -06:00
Andy Miller
67484a1a90 typo in .htaccess 2014-10-14 17:27:14 -06:00
Andy Miller
783d148551 Debugger style overrides 2014-10-14 17:26:14 -06:00
Andy Miller
5134b28bad Potential improvement of .htaccess 2014-10-14 15:45:17 -06:00
Andy Miller
c10882f290 Added Whoops 2014-10-14 15:42:25 -06:00
Djamil Legato
19bbfd0a50 Cleanup 2014-10-14 13:13:55 -07:00
Djamil Legato
3f6b5e35de Added new GPM version command that allows to ask for the version of the Grav instance as well as any installed package. It also supports multiple arguments and if an update is available it will be shown 2014-10-14 12:45:10 -07:00
Djamil Legato
002902c9d9 Cleanup 2014-10-14 12:17:29 -07:00
Andy Miller
d93e17cff4 Merge branch 'develop' into feature/alternative_debugger 2014-10-14 12:49:27 -06:00
Andy Miller
9fa92b504f Admin check 2014-10-14 12:48:33 -06:00
Andy Miller
74d25d240a Admin check 2014-10-14 12:47:47 -06:00
Matias Griese
a136241952 Add support for system timers, allowing to time initialization 2014-10-14 10:10:17 +03:00
Matias Griese
c89f683b8c Load debugger as early as possible 2014-10-14 09:51:55 +03:00
Matias Griese
45e79f88a3 Fix potential double // in assets (breaks assets loading in FF) 2014-10-14 09:32:16 +03:00
Andy Miller
bdd0c1f4c7 tidy up.. 2014-10-13 23:27:54 -06:00
Andy Miller
2bfcd88bfd Initial commit with PHP Debug Bar instead of Tracy 2014-10-13 22:23:13 -06:00
Djamil Legato
d18f0bf751 Removed is_file check on the else statement as files with no extensions aren't recognized as files by PHP, preventing .htaccess or LICENSE to get copied over 2014-10-13 14:24:55 -07:00
Djamil Legato
3d983d8377 Forcing a hard clear-cache for self upgrade only and soft clear-cache for install/update of themes/plugins 2014-10-13 10:38:11 -07:00
Andy Miller
6c912d126a Fixed error messages relating to plugins 2014-10-13 11:07:33 -06:00
Djamil Legato
48b02b0f05 Selfupgrade command now clears cache after a successful upgrade 2014-10-12 18:54:27 -07:00
Andy Miller
523b6376bb fix for username field 2014-10-12 13:49:20 -06:00
Djamil Legato
c276457390 Merge branch 'release/0.9.3' 2014-10-10 15:13:29 -07:00
Djamil Legato
d983a908e4 Merge branch 'release/0.9.3' into develop 2014-10-10 15:13:29 -07:00
Djamil Legato
538fa23b9d Rereleasing 0.9.3 2014-10-10 15:13:19 -07:00
Djamil Legato
d2bfacad4e Resetting permissions of binaries after an upgrade 2014-10-10 15:07:47 -07:00
Andy Miller
cc68065f94 Fix for extended image url handling in markdown 2014-10-10 15:57:29 -06:00
Djamil Legato
00f6738b7e Merge branch 'release/0.9.3' 2014-10-10 14:02:11 -07:00
Djamil Legato
9310c3f12f Merge branch 'release/0.9.3' into develop 2014-10-10 14:02:11 -07:00
Djamil Legato
0fc1f5492f Prepearing for release 2014-10-10 14:02:01 -07:00
Andy Miller
4562de083d Merge branch 'feature/0.9.3_changelog' into develop 2014-10-10 14:05:21 -06:00
Matias Griese
7250a3ecc1 Fix theme overrides 2014-10-10 21:12:11 +03:00
Matias Griese
ac1874795b Modify some blueprints 2014-10-10 15:25:37 +03:00
Matias Griese
2fb8fb62c6 Add function to load user account 2014-10-10 15:02:56 +03:00
Matias Griese
fd9816c177 Bluprints: Move validation parameter inside the form 2014-10-10 12:27:41 +03:00
Matias Griese
5a6e32fce7 Add option to create modular content 2014-10-10 11:57:57 +03:00
Djamil Legato
774850f30d Inverted order of questions on self upgrade. Now first is change log view and then if you want to upgrade. Makes more sense :) 2014-10-09 20:42:57 -07:00
Andy Miller
41f4d269b0 Various tweaks and hopefully improvements to file change detection 2014-10-09 21:04:30 -06:00
Djamil Legato
18e79ce4fe Typo 2014-10-09 18:45:59 -07:00
Djamil Legato
92ce0aa816 Added getChangelog method with option to set a diff starting point version
Selfupgrade command is now going to prompt you before continuing upgrading and optionally can show the change log
Added -y|--all-yes option in grav self upgrade command to skip any prompt and just upgrade
2014-10-09 18:44:30 -07:00
Andy Miller
5ca9180033 return state for compatibility with functions that look for true/false 2014-10-09 18:43:23 -06:00
Andy Miller
1856d1f8c7 Stupidly forgot we already had a recursive delete method in Folder class! 2014-10-09 18:42:53 -06:00
Djamil Legato
af9810d1b3 Now resetting permissions for all binaries inside bin/ when running sandbox 2014-10-09 17:21:46 -07:00
Andy Miller
a454312bd2 Updated change log to reference updates today 2014-10-09 18:07:31 -06:00
Andy Miller
88063c2a60 Merge branch 'develop' of github.com:getgrav/grav into develop 2014-10-09 18:03:54 -06:00
Djamil Legato
8c010fbd3c Updated distributed composer.phar to 73e9db5d9952d52a46ecbc20a269a8c5f9c5b011 2014-10-09 17:03:48 -07:00
Andy Miller
7f615f19f7 Unified multiple versions of "remove directory" method 2014-10-09 18:03:32 -06:00
Djamil Legato
5f7b70748b GPM installer now extracts into the grav instance cache folder rather than system tmp folder 2014-10-09 16:49:08 -07:00
Andy Miller
79268130f4 clear-cache CLI command deletes specific folders. new --all flag added 2014-10-09 16:50:43 -06:00
Andy Miller
8b78b4997e Switched GPM repo to use custom Doctrine cache 2014-10-09 16:50:08 -06:00
Andy Miller
e224841650 switched Doctrine cache to use new stream locator for directory and moved to sub folder 2014-10-09 14:53:37 -06:00
Djamil Legato
a69e5fa115 Switched deprecated Parsedown parse method to text 2014-10-09 12:34:35 -07:00
Andy Miller
539b7931e6 Revert "Moved File Page cache into /cache/pages subfolder"
This reverts commit f303c1243f.
2014-10-09 13:18:32 -06:00
Andy Miller
f303c1243f Moved File Page cache into /cache/pages subfolder 2014-10-09 13:13:24 -06:00
Andy Miller
347d8ecebe Moved Twig cache into /cache/twig subfolder 2014-10-09 13:05:55 -06:00
Andy Miller
f977092115 New changelog work in progress 2014-10-09 12:38:57 -06:00
Matias Griese
5430792687 Disable markdown file compilation for now 2014-10-09 20:40:34 +03:00
Andy Miller
e591212166 typo in descriptions 2014-10-08 14:29:22 -06:00
Andy Miller
77f19974f5 Merge branch 'develop' of github.com:getgrav/grav into develop 2014-10-08 13:24:52 -06:00
Andy Miller
8b34e7dc19 Add inline CSS and JS support to Assets 2014-10-08 13:24:37 -06:00
Djamil Legato
3d4fb6a7d4 Detecting symbolically linked plugins and themes, flagging them and ignoring updates for them 2014-10-08 11:59:25 -07:00
Andy Miller
af7d3ccf83 New change log format 2014-10-08 12:13:15 -06:00
Matias Griese
a8b6841923 Simplify modular page logic 2014-10-08 19:54:59 +03:00
Matias Griese
e09ab139c8 Simplify modular page loading 2014-10-08 19:43:21 +03:00
Matias Griese
fe6a0a27b3 Remove unneeded debugging 2014-10-08 19:00:05 +03:00
Matias Griese
2f4c32e682 Add session close before redirect 2014-10-08 18:59:32 +03:00
Matias Griese
fdf2884f97 Fix Page::filePath() for modular content 2014-10-08 15:12:22 +03:00
Matias Griese
8d8129b49e Add support for modular raw template 2014-10-08 14:33:27 +03:00
Matias Griese
a811ee67cc Add blueprints support for modular content 2014-10-08 14:21:24 +03:00
Matias Griese
f9bfed2b22 Use blueprints from plugins 2014-10-08 13:24:26 +03:00
Matias Griese
3991f51cdf Remove unused file 2014-10-08 12:26:59 +03:00
Matias Griese
0902840aa4 Add support for listing custom page templates/types 2014-10-08 12:26:45 +03:00
Matias Griese
df475af2fd Add back Twig::processString() as it is used in plugins 2014-10-07 20:47:44 +03:00
Matias Griese
9fb56df9a0 Add back configuration key to caching (was accidentally removed) 2014-10-07 14:03:53 +03:00
Matias Griese
b11b39752f Compile markdown files into PHP and cache them 2014-10-07 12:32:31 +03:00
Matias Griese
8cd361ca41 Fix undefined variable in Data\Blueprint class 2014-10-07 12:31:42 +03:00
Matias Griese
4c04dfc747 Do not check blueprint updates when caching configuration 2014-10-07 12:31:01 +03:00
Matias Griese
6cc3d15ee7 Fix warnings when sorting empty collection of pages 2014-10-07 12:22:41 +03:00
Matias Griese
c0bf787dc7 Fix undefined variable in Uri::query() part 2 2014-10-07 12:06:30 +03:00
Matias Griese
4452cf3d1b Fix undefined variable in Uri::query() 2014-10-07 12:02:18 +03:00
Matias Griese
9fa97ad46b Use compiled Yaml files in medium and plugins 2014-10-07 11:04:52 +03:00
Matias Griese
6c3e1d83a7 Merge branch 'develop' of https://github.com/getgrav/grav into develop 2014-10-07 11:02:18 +03:00
Matias Griese
9f2d1b48e6 Use session class in shutdown 2014-10-07 11:02:09 +03:00
Djamil Legato
fd7fd2fb0c Including description_html and description_plain in the Local Packages 2014-10-06 14:58:33 -07:00
Djamil Legato
a58ea17867 Merge branch 'feature/gpm' of github.com:getgrav/grav into feature/gpm 2014-10-06 11:51:31 -07:00
Andy Miller
093e101f1b fix for close connection 2014-10-06 12:50:09 -06:00
Andy Miller
93dd673639 added type bool for assets 2014-10-06 12:46:34 -06:00
Djamil Legato
71b25190b8 Merge branch 'feature/gpm' of github.com:getgrav/grav into feature/gpm 2014-10-06 11:44:42 -07:00
Djamil Legato
1d90cecc11 Now initializing Remote\Grav with GPM 2014-10-06 10:59:58 -07:00
Djamil Legato
e771393489 Cleanup 2014-10-06 10:59:35 -07:00
Matias Griese
122107d1f8 Merge remote-tracking branch 'origin/feature/gpm' into feature/gpm 2014-10-06 20:58:54 +03:00
Matias Griese
ebd81a0dc8 Fix a bug in caching where configuration update had no effect 2014-10-06 20:58:42 +03:00
Djamil Legato
69282e4423 Added isUpdatable method for GPM\Remote\Grav 2014-10-06 10:58:34 -07:00
Djamil Legato
ce71ac352e Typo I suppose 2014-10-06 10:55:11 -07:00
Matias Griese
7738e052e1 Rename CompiledYaml class to CompiledYamlFile 2014-10-06 20:42:34 +03:00
Matias Griese
f43e047497 Fix a bug causing admin not to fill up the data fields 2014-10-06 14:37:04 +03:00
Andy Miller
9eb06da9a9 Added support for spaces in page and media urls 2014-10-05 18:43:33 -06:00
Andy Miller
ce4a9a02d4 Fix for file checking not updating the last modified time 2014-10-04 17:25:39 -06:00
Matias Griese
a8a82e1a3c Fix regression in blueprints 2014-10-02 00:58:47 +03:00
Matias Griese
5abc9b320b Fix missing user stream in gpm 2014-10-02 00:30:26 +03:00
Djamil Legato
0aba432688 Fixed Iterator default $items value 2014-10-01 14:12:45 -07:00
Djamil Legato
d80429a0ea Initializing themes and plugins only in the gpm cli, we don't need that in the classes 2014-10-01 14:12:09 -07:00
Matias Griese
d9145b0ebc Fix infinite loop 2014-10-01 23:28:39 +03:00
Djamil Legato
c05252a570 Removed locator initialization as it's no needed anymore 2014-10-01 12:43:12 -07:00
Djamil Legato
acd81eb7c3 Merge branch 'develop' into feature/gpm 2014-10-01 12:42:30 -07:00
Matias Griese
a3c58fcc5a Fix base directory for streams 2014-10-01 22:23:29 +03:00
Matias Griese
16b541a8ee Merge remote-tracking branch 'origin/develop' into develop 2014-10-01 22:12:26 +03:00
Matias Griese
d705530e64 Add stream "plugin" (alias of "plugins") 2014-10-01 22:12:13 +03:00
Andy Miller
84873484d5 Typo fix for constant 2014-10-01 12:54:10 -06:00
Djamil Legato
eb1883854e Merge branch 'develop' into feature/gpm
Conflicts:
	system/src/Grav/Common/Grav.php
	system/src/Grav/Common/Plugins.php
2014-10-01 11:37:56 -07:00
Matias Griese
9cb83ba368 Merge branch 'develop' of https://github.com/getgrav/grav into feature/multi-config 2014-10-01 20:35:41 +03:00
Andy Miller
82bc6fb308 Renaming 2014-09-30 15:49:09 -06:00
Andy Miller
5aa95c0b7e more flexible children() options 2014-09-30 15:48:36 -06:00
Matias Griese
419b46afb0 Minor fixes 2014-09-30 20:47:18 +03:00
Matias Griese
0b607c5197 Rename Grav\Component back to Grav\Common (unnessessary change) 2014-09-30 19:27:42 +03:00
Matias Griese
9da1ec836e Merge branch 'develop' of https://github.com/getgrav/grav into feature/multi-config
Conflicts:
	system/src/Grav/Common/Filesystem/File/Markdown.php
	system/src/Grav/Common/Grav.php
	system/src/Grav/Common/TwigExtension.php
2014-09-30 19:16:19 +03:00
Djamil Legato
5639941ff3 Fixed reset of the index when listing updates available 2014-09-26 17:57:02 -07:00
Djamil Legato
9d59db5adc Moved $container back to where it belongs 2014-09-26 16:25:19 -07:00
Djamil Legato
033f43b82f Minor space fix 2014-09-26 16:24:29 -07:00
Djamil Legato
91f4dc1a79 Fixed limit not working in the update command 2014-09-26 16:24:15 -07:00
Djamil Legato
9fe24272e3 Merge branch 'develop' into feature/gpm 2014-09-25 18:37:36 -07:00
Djamil Legato
b156c8752a Returning empty array if input wasn't provided. 2014-09-25 18:37:08 -07:00
Djamil Legato
93c51584db Deleted VERSION, no longer needed. 2014-09-25 18:35:45 -07:00
Djamil Legato
f36f31dfcf Added Selfupgrade command for upgrading Grav to the latest available version 2014-09-25 18:35:09 -07:00
Djamil Legato
5cef486981 Added Upgrader GPM class which allows to get details about the latest version of Grav available including version, release date and available assets to download 2014-09-25 18:33:47 -07:00
Djamil Legato
5db100ae49 Updated installer to support sophisticated and non sophisticated methods. Non sophisticated is delete, unzip, move. Sophisticated unzip and loops through each file in the zip (1 level deep only) and replace the node in the destination 2014-09-25 18:30:39 -07:00
Djamil Legato
46bcd1b095 Added new Grav Remote for fetching details about latest version 2014-09-25 18:28:26 -07:00
Djamil Legato
1fc668f0fc Merge branch 'develop' into feature/gpm 2014-09-25 18:27:09 -07:00
Andy Miller
5589b48e01 added columns in raw and ordering 2014-09-25 17:24:49 -06:00
Djamil Legato
cc28f85fde Merge branch 'develop' into feature/gpm 2014-09-25 15:34:58 -07:00
Djamil Legato
a6d46ebcd7 Forcing 2 space indentation on all yaml files 2014-09-25 15:34:29 -07:00
Matias Griese
ac3b0ba3ec Fix saving page as a new type 2014-09-25 23:31:59 +03:00
Andy Miller
e4ff2ea39d new visibile() and routable() filters to Collections 2014-09-25 12:16:54 -06:00
Djamil Legato
4653df1350 Merge branch 'develop' into feature/gpm 2014-09-24 16:00:13 -07:00
Djamil Legato
e3c5234038 Oops, the reference was not meant to be there 2014-09-24 15:59:56 -07:00
Djamil Legato
4f0e1ea8b0 Merge branch 'develop' into feature/gpm 2014-09-24 15:50:40 -07:00
Djamil Legato
eaa05ee252 Added new 'sort_by_key' twig filter which sorts an array by the desired key. It also takes in a direction as second argument which can be any of the predefined sorting flags (http://php.net/manual/en/array.constants.php) 2014-09-24 15:50:21 -07:00
Djamil Legato
0c4d0b5646 Merge branch 'develop' into feature/gpm 2014-09-24 13:54:34 -07:00
Andy Miller
00ac1da29d Add modular/regular page filtering to the children() method. Issue #60 2014-09-24 14:04:49 -06:00
Djamil Legato
6ac126867e Merge branch 'develop' into feature/gpm 2014-09-23 11:40:04 -07:00
Andy Miller
ea7ddeaf55 Added dynamic media support via the URL 2014-09-23 12:34:49 -06:00
Djamil Legato
f536dbfc20 Merge branch 'develop' into feature/gpm 2014-09-22 15:41:58 -07:00
Andy Miller
7261cea92f add filesize for medium 2014-09-22 15:58:12 -06:00
Andy Miller
41d9b52005 added ability to set default file extension 2014-09-22 15:57:46 -06:00
Djamil Legato
50529ae711 Merge branch 'develop' into feature/gpm 2014-09-22 10:57:59 -07:00
Andy Miller
1bfa2f31e7 Added uploads media field 2014-09-22 11:31:23 -06:00
Andy Miller
2b8e3d16a3 updated raw format 2014-09-21 17:17:53 -06:00
Andy Miller
c1e9cc2f02 Merge branch 'feature/frontmatter_support' into develop 2014-09-20 15:34:59 -06:00
Andy Miller
a39656d536 refactored to make more reliable 2014-09-20 15:33:41 -06:00
Andy Miller
103369cc64 sample implementation of frontmatter in pages 2014-09-19 17:51:29 -06:00
Matias Griese
0ab39a9b85 Fix page saving using html instead of raw input 2014-09-19 19:36:13 +03:00
Djamil Legato
af582f88fc Merge branch 'develop' into feature/gpm 2014-09-18 17:32:35 -07:00
Andy Miller
cccd084a55 working on blueprints 2014-09-18 18:05:00 -06:00
Matias Griese
bde5e65d10 Fix a bug on calling raw() from non-existing page 2014-09-18 11:42:27 +03:00
Matias Griese
874bfe47b8 Move page related blueprints into its own folder 2014-09-18 11:33:13 +03:00
Djamil Legato
31c12c9c0d Fixed infoCommand author url since it's now optional 2014-09-17 23:32:36 -07:00
Djamil Legato
ed8903ece3 Updated GPM to use the new repository logic 2014-09-17 17:15:56 -07:00
Djamil Legato
5c92069e52 Cleanup 2014-09-17 17:13:40 -07:00
Djamil Legato
79229f6b35 Fixed count of installed packages 2014-09-17 17:11:32 -07:00
Djamil Legato
f9272887f5 Added new method isUpdatable($slug) to know if a generic slug is updatable or not 2014-09-17 17:11:20 -07:00
Djamil Legato
aa9af76f02 Internal cache for the class to speed up retrieving plugins and themes 2014-09-17 17:08:36 -07:00
Djamil Legato
967202cbc3 Merge branch 'develop' into feature/gpm 2014-09-17 17:05:19 -07:00
Djamil Legato
a7007eabfa Merge branch 'develop' into feature/gpm 2014-09-17 17:03:19 -07:00
Matias Griese
0e75f999cc Minor fix on general page blueprints 2014-09-17 11:24:02 +03:00
Andy Miller
4e3b5039da Added title to new page blueprint 2014-09-16 18:12:34 -06:00
Matias Griese
7a2af8e63e Merge branches 'develop' and 'feature/multi-config' of https://github.com/getgrav/grav into feature/multi-config
Conflicts:
	system/src/Grav/Component/Filesystem/ResourceLocator.php
2014-09-16 09:45:17 +03:00
Andy Miller
3fe18a9213 set taxonomy field to large 2014-09-15 21:28:02 -06:00
Djamil Legato
bfa9cc294e Merge branch 'release/0.9.2' into develop 2014-09-15 17:08:40 -07:00
Djamil Legato
81c4ecaabd Merge branch 'release/0.9.2' 2014-09-15 17:08:39 -07:00
Djamil Legato
2f3f4a1137 Preparing for release v0.9.2 2014-09-15 17:08:27 -07:00
Andy Miller
c50cba64bb Went to simplified array rather than metadata object 2014-09-15 17:46:55 -06:00
Andy Miller
5b2aa6ead9 reverted naming 2014-09-15 17:25:10 -06:00
Andy Miller
6a3f6b9be2 missed one 2014-09-15 16:50:39 -06:00
Andy Miller
61169d868c changed metadata var name 2014-09-15 16:47:09 -06:00
Andy Miller
72023fcd4a added a comment to class 2014-09-15 16:36:16 -06:00
Andy Miller
7c160b9b08 updated metadata to be new format 2014-09-15 14:15:29 -06:00
Andy Miller
e8e90892aa minor change of default site description 2014-09-15 14:08:12 -06:00
Andy Miller
16ad95c205 Updated with more flexible metadata support in pages and site.yaml 2014-09-15 13:25:35 -06:00
Andy Miller
d509ed0a34 Code tidy #55 2014-09-14 16:20:54 -06:00
Andy Miller
6d0539c793 #55 Skip Minify by default on Windows systems. Can activate with assets.css_minify_windows = true if you have updated your ThreadStackSize in httpd.conf 2014-09-14 16:18:19 -06:00
Jan Chren
260f200d6f fixed problems with multiloading of includes
https://github.com/sebastianbergmann/phpunit/issues/314
2014-09-14 23:08:56 +02:00
Jan Chren
2d95b3bb09 PHPUnit testing introduced 2014-09-14 22:44:59 +02:00
Andy Miller
0276ce94df wrapper for existing modular() method 2014-09-12 17:06:35 -06:00
Andy Miller
978dc571bb Fix for menu not highlighting properly 2014-09-11 15:32:44 -06:00
Andy Miller
059189ec77 Fancy selectize support 2014-09-11 15:32:18 -06:00
Djamil Legato
1af0e7068e PSR 2014-09-11 14:09:42 -07:00
Djamil Legato
c8d6bfa455 Cleanup 2014-09-11 14:09:21 -07:00
Djamil Legato
1ad80acd72 Before exiting if a theme is not found, ensure we are not running it from the CLI 2014-09-11 13:08:22 -07:00
Djamil Legato
e19b15cacd Fixed InstallCommand to use the new Installer class 2014-09-11 12:45:17 -07:00
Djamil Legato
2178971e5e Added new Installer class 2014-09-11 12:44:53 -07:00
Djamil Legato
ffecd8bb87 PSR cleanup 2014-09-11 12:44:37 -07:00
Djamil Legato
4326e059a5 Themes now can get initialized by themselves! 2014-09-11 12:44:13 -07:00
Djamil Legato
adaadf3c60 Fixed static method call for all in GPM Plugins 2014-09-11 12:43:51 -07:00
Djamil Legato
c4d590674d Response cleanup and more docs 2014-09-11 12:41:24 -07:00
Djamil Legato
a86159a02c Fixed GPM cli to run the locator 2014-09-11 12:40:53 -07:00
Djamil Legato
88c9436df9 Added GPM docs 2014-09-11 12:40:29 -07:00
Djamil Legato
b26d938147 Folder::mkdir is now public 2014-09-11 12:09:38 -07:00
Djamil Legato
97808a8b6a Merge branch 'develop' into feature/gpm 2014-09-11 12:07:33 -07:00
Andy Miller
bf39adbda7 Fixes and reformatting of blueprints 2014-09-10 14:49:11 -06:00
Matias Griese
ec82760cbf Merge remote-tracking branch 'origin/develop' into develop 2014-09-10 20:41:09 +03:00
Matias Griese
3f6901c036 Fail gracefully when theme does not exist 2014-09-10 20:40:49 +03:00
Matias Griese
6d27da4f9b Add possibility to use /setup.php to customize site setup 2014-09-10 19:53:51 +03:00
Andy Miller
ee623596a8 system.yaml updates and synchronization 2014-09-10 10:12:08 -06:00
Andy Miller
9df91e482e system.yaml updates and synchronization 2014-09-10 10:05:36 -06:00
Matias Griese
5739e9729f Add stream support into ResourceLocator::addPath() 2014-09-10 14:45:16 +03:00
Matias Griese
eba77a8028 After merge tweaks 2014-09-10 11:22:26 +03:00
Matias Griese
82533c17e6 Merge branch 'develop' of https://github.com/getgrav/grav into feature/multi-config
Conflicts:
	system/config/streams.yaml
	system/src/Grav/Common/Config.php
	system/src/Grav/Common/Grav.php
	system/src/Grav/Common/Themes.php
	vendor/autoload.php
	vendor/composer/autoload_classmap.php
	vendor/composer/autoload_files.php
	vendor/composer/autoload_psr4.php
	vendor/composer/autoload_real.php
	vendor/composer/installed.json
2014-09-10 11:21:03 +03:00
Djamil Legato
fd64fd0822 Merge branch 'develop' into feature/gpm 2014-09-08 22:41:04 -07:00
Djamil Legato
04fb7326e5 Added index count 2014-09-08 22:40:17 -07:00
Djamil Legato
1d8f41df35 Fixed index of packages 2014-09-08 22:40:00 -07:00
Djamil Legato
1b01dff466 Removed FetchCommand since it's not needed anymore 2014-09-08 22:35:56 -07:00
Djamil Legato
e2021d1075 Merge pull request #52 from rindeal/feature/speed-optimizations
Just some speed optimizations
2014-09-08 22:29:39 -07:00
Djamil Legato
75d0b687c1 Converted InstallCommand and UpdateCommand to use the new GPM class 2014-09-08 22:28:44 -07:00
Djamil Legato
d0e6b23637 Throwing exception if fopen fails 2014-09-08 22:27:53 -07:00
Djamil Legato
e9f4c7d9a5 Added countInstalled method
Added findPackages method
2014-09-08 22:27:13 -07:00
Djamil Legato
a3b76252d1 Updated InfoCommand to use the new GPM class 2014-09-08 18:04:36 -07:00
Djamil Legato
231e278c76 Removed fetch logic from the ConsoleTrait as it's not needed anymore 2014-09-08 18:04:09 -07:00
Djamil Legato
5f41beccde Added method to find a package in the repository 2014-09-08 18:02:28 -07:00
Djamil Legato
a44193db16 Updated IndexCommand to use the new GPM class 2014-09-08 17:22:11 -07:00
Djamil Legato
c6dcb22d56 Added methods to retrieve single plugin/theme (remote or local) and all updatables.
Updatable items have now `version` defaulting to the local installed version and `available` as the remote version
2014-09-08 17:21:37 -07:00
Jan Chren
ff3ebb1f17 calculate only once in loops 2014-09-09 02:05:19 +02:00
Djamil Legato
8e489f6f09 Inverted order of $refresh and $callback arguments for fetching data ($refresh comes first now)
Can now pass $refresh and $callback when instantiating a new GPM
2014-09-08 16:07:09 -07:00
Djamil Legato
618a461835 Merge branch 'develop' into feature/gpm 2014-09-08 15:00:10 -07:00
Djamil Legato
281eb14178 Setting slug as key to the items array 2014-09-08 14:59:12 -07:00
Djamil Legato
1cbb39a38b Initial Grav Package Manager for handling local installed packages and get a status of available packages in the repository 2014-09-08 14:54:45 -07:00
Matias Griese
b50ec3fedd Separate themes from plugins, add themes:// stream and onTask events 2014-09-08 22:18:31 +03:00
Matias Griese
5f03dfa8ed Fix url() twig function when Grav isn't installed to root 2014-09-08 09:51:37 +03:00
Andy Miller
fa92ec0141 Added method to get user IP address 2014-09-07 19:57:14 -06:00
Andy Miller
193eda633c Added an option to disable the shutdown close connection to help with debugging 2014-09-07 09:00:29 -06:00
Djamil Legato
da0fafa800 Why does this keep getting reverted on my merges! 2014-09-06 15:31:45 -07:00
Djamil Legato
bfb5a74197 Merge branch 'develop' into feature/gpm 2014-09-06 15:23:36 -07:00
Djamil Legato
cceaeb8d42 Manual merge from develop 2014-09-06 15:20:15 -07:00
Andy Miller
2a43983ede added onShutdown event that fires after the connection has been closed (background processing) 2014-09-06 14:32:21 -06:00
Andy Miller
cd5707f2d2 removed barDump() test :) 2014-09-06 13:31:34 -06:00
Andy Miller
5667f8b023 Added shutddown event to flush buffer and set content length (tracy-safe) 2014-09-06 13:29:56 -06:00
Andy Miller
09248f2917 Added barDump() to Debugger 2014-09-06 13:28:47 -06:00
Djamil Legato
162409053f Merge branch 'develop' into feature/gpm 2014-09-05 22:42:00 -07:00
Djamil Legato
528f85642f Reverted streams for theme 2014-09-05 22:39:26 -07:00
Djamil Legato
9d1b50ffbb Merge branch 'develop' into feature/gpm 2014-09-05 22:33:50 -07:00
Andy Miller
95a806c8f8 removed stray test page 2014-09-05 22:41:48 -06:00
Andy Miller
7a38ac0810 override modified only if a non-markdown file was modified 2014-09-05 22:25:48 -06:00
Andy Miller
0fec4c003b getList was returning nothing 2014-09-05 22:25:13 -06:00
Andy Miller
80d08eace7 fixes for themes streams and such 2014-09-05 20:52:46 -06:00
Djamil Legato
e7568bf8d0 Merge branch 'develop' into feature/gpm 2014-09-05 18:58:48 -07:00
Djamil Legato
353f69deb3 Merge branch 'develop' into feature/gpm 2014-09-05 18:55:51 -07:00
Djamil Legato
e2917e36f8 Fixed use of streams. Using YAML_EXT constant for the extension. Changed get and all methods to static 2014-09-05 18:51:14 -07:00
Djamil Legato
8ddfa571d3 Merge pull request #48 from rindeal/feature/config-fixes
fix for PHP warning "is_file(): open_basedir restriction in effect", round 2
2014-09-05 18:14:07 -07:00
Jan Chren
393c8cab2a final workaround for PHP bug 52065 2014-09-06 03:09:07 +02:00
Jan Chren
cb03c7ab0d Revert "reverted back to file-only checks, changed to file_exists()"
This reverts commit d234b8e212.
2014-09-06 00:46:59 +02:00
Andy Miller
bce0797c55 Merge pull request #47 from rindeal/feature/config-fixes
fix for PHP warning "is_file(): open_basedir restriction in effect"
2014-09-05 16:04:54 -06:00
Jan Chren
d234b8e212 reverted back to file-only checks, changed to file_exists() 2014-09-06 00:00:47 +02:00
Djamil Legato
481e61b08b Merge pull request #46 from rindeal/feature/configurable-assets-attributes
Feature/configurable assets attributes
2014-09-05 14:51:34 -07:00
Jan Chren
f8fe108db5 PHPDocs updated 2014-09-05 23:19:18 +02:00
Jan Chren
103abc5f6d assets attributes implemented 2014-09-05 23:11:02 +02:00
Djamil Legato
f515438c62 Merge branch 'feature/vendor_free' into develop 2014-09-05 14:07:01 -07:00
Djamil Legato
470bac23bc Removed vendors from the mapping in SetupCommand 2014-09-05 13:40:35 -07:00
Djamil Legato
123c75ae2d Changed command suggestion to run for installing the dependencies 2014-09-05 12:58:56 -07:00
Djamil Legato
d05c049d4c Autorun composer install when running the grav cli 2014-09-05 12:58:36 -07:00
Djamil Legato
c7ece89fea Including compohser.phar 2014-09-05 12:58:15 -07:00
Djamil Legato
4c9e02d648 Ignoring vendor folder 2014-09-05 12:58:06 -07:00
Djamil Legato
57ab99fb13 Getting away from providing vendor with the repo 2014-09-05 12:56:43 -07:00
Andy Miller
217db86b10 accidental commit reverted 2014-09-05 08:36:11 -06:00
Jan Chren
e5037eb69b json_encode() is faster than serialize()
ref: http://stackoverflow.com/a/7723730/2566213
2014-09-05 16:17:05 +02:00
Jan Chren
8da3eb32f0 fix for PHP warning "is_file(): open_basedir restriction in effect"
Example:
PHP Warning: is_file(): open_basedir restriction in effect. File(/home/users/.../user/plugins/.gitkeep/.gitkeep.yaml) is not within the allowed path(s): (/home/users/.../:/usr/share/pear/) in .../web/system/src/Grav/Common/Config.php:237
2014-09-05 15:46:18 +02:00
Djamil Legato
24d1d4774e Cleanup police: using traits for GPM 2014-09-04 20:34:36 -07:00
Djamil Legato
b0f37079b0 Removed unused var declaration 2014-09-04 20:33:22 -07:00
Djamil Legato
026646cc46 Forgot to stage the gpm with last commit 2014-09-04 16:29:48 -07:00
Djamil Legato
258f55fe96 Vendors get now installed via composer automatically through first run of GPM or Grav cli. 2014-09-04 16:29:10 -07:00
Djamil Legato
d2cd2a3772 Including composer phar in bin/ 2014-09-04 16:26:55 -07:00
Djamil Legato
c8db29ce46 Removed copying of vendor/ since it's now vendor-free. Install command will take care of it. 2014-09-04 16:26:04 -07:00
Djamil Legato
8a4a810771 Fixed old setupCommand references 2014-09-04 16:19:24 -07:00
Djamil Legato
412216188d Ignoring vendors from now on 2014-09-04 15:41:46 -07:00
Djamil Legato
9103ea0c52 Finally vendor free! 2014-09-04 15:40:38 -07:00
Djamil Legato
ea1c26caa3 Added more info for the BackupCommand help 2014-09-04 15:13:08 -07:00
Djamil Legato
0977d330e6 Reworded description and help of Install and NewProject commands 2014-09-04 14:48:58 -07:00
Djamil Legato
f7c2df0050 Renamed setup command to sandbox. 2014-09-04 14:48:24 -07:00
Djamil Legato
e690e50d86 Merge branch 'develop' into feature/vendor_free 2014-09-04 14:41:14 -07:00
Andy Miller
0b0ce2eb27 Merge pull request #43 from rindeal/hotfix/debugger-mode
Some tiny fixes in Debugger
2014-09-04 11:50:18 -06:00
Jan Chren
cc81ea7e3e disable display_errors in production environment 2014-09-04 02:50:27 +02:00
Djamil Legato
32dfc001af Switch to old fashion array declaration in the index.php to allow PHP to parse without failing and then check for the required PHP version (5.4+) [Fixes #36] 2014-09-03 17:43:15 -07:00
Andy Miller
a0f5e35ad5 removed memcahed from the list of cache options 2014-09-03 17:54:50 -06:00
Andy Miller
971508f613 Removed memcache from auto setup, added memcache server config, removed memcached support 2014-09-03 17:49:55 -06:00
Jan Chren
144d64e0c8 detect mode only if debugger enabled 2014-09-04 00:29:45 +02:00
Jan Chren
a999046882 removed unused functions 2014-09-04 00:27:30 +02:00
Matias Griese
943921eadb Fix broken password validation 2014-09-03 21:19:16 +03:00
Matias Griese
0f87946276 Move new Blueprints class into RocketTheme\Toolbox 2014-09-03 19:13:57 +03:00
Matias Griese
9e032a7120 Fix wrong namespace declaration 2014-09-03 15:50:17 +03:00
Matias Griese
17e218bdb3 Turn debugger into production mode 2014-09-03 15:23:18 +03:00
Matias Griese
cb705f2d96 Remove debug message 2014-09-03 14:48:53 +03:00
Matias Griese
ac8483f894 Start using RocketTheme\Toolbox and remove local copies of the Toolbox classes 2014-09-03 14:34:40 +03:00
Matias Griese
3098ac9998 Update autoload files 2014-09-03 10:39:39 +03:00
Matias Griese
30246a8666 Post merge fixes 2014-09-03 10:33:45 +03:00
Matias Griese
f606999c40 Merge branch 'develop' of https://github.com/getgrav/grav into feature/multi-config
Conflicts:
	composer.json
	system/config/streams.yaml
	system/src/Grav/Common/Page/Page.php
	system/src/Grav/Common/Theme.php
	system/src/Grav/Common/Themes.php
	system/src/Grav/Component/Filesystem/ResourceLocator.php
	vendor/autoload.php
	vendor/composer/autoload_classmap.php
	vendor/composer/autoload_files.php
	vendor/composer/autoload_real.php
	vendor/composer/installed.json
2014-09-03 10:33:02 +03:00
Matias Griese
affa768efb Ground work for allowing multiple configurations 2014-09-03 09:57:46 +03:00
Matias Griese
c73e0d140d Go back to PSR-4 2014-09-03 09:07:29 +03:00
Djamil Legato
615c91b1c5 Minor change in the output 2014-09-02 22:52:00 -07:00
Djamil Legato
a0bc5bf765 Added Update command that scans for installed extensions and look for available updates (as well as install them!) 2014-09-02 22:41:07 -07:00
Djamil Legato
2ab433c339 Added an --all-yes option to assume yes to all questions (or automatically skip if it's a better approach) 2014-09-02 22:39:00 -07:00
Djamil Legato
267fa23d51 Super minor aesthetic change 2014-09-02 22:35:43 -07:00
Djamil Legato
e6565d9394 Fixed InstallCommand description 2014-09-02 17:51:28 -07:00
Djamil Legato
3b848e4a91 Merge branch 'develop' into feature/vendor_free 2014-09-02 17:40:56 -07:00
Your Name
891751d09e Fixed case where base_url was empty 2014-09-02 16:58:55 -05:00
user.email
15db488abc Merge branch 'release/0.9.1' 2014-09-02 14:05:39 -07:00
user.email
e40ea38dd6 Merge branch 'release/0.9.1' into develop 2014-09-02 14:05:39 -07:00
user.email
bbc8f6b293 Preparing for release v0.9.1 2014-09-02 14:05:15 -07:00
Djamil Legato
2c4a724fc0 Merge branch 'develop' into feature/vendor_free 2014-09-02 11:02:32 -07:00
Andy Miller
848e3e473f removed unused multi views reference 2014-09-02 11:54:57 -06:00
Matias Griese
263b7b3b1c Add support for twig markup like {{ url('theme://images/logo.png') }} 2014-09-02 20:04:54 +03:00
Matias Griese
48d7332e6a Make basic template inheritance to work 2014-09-02 19:25:18 +03:00
Djamil Legato
3568c25ef1 Merge branch 'develop' into feature/vendor_free 2014-09-01 23:21:41 -07:00
Djamil Legato
3948aa77a8 Cleanup 2014-09-01 23:21:18 -07:00
Matias Griese
7ca98baa34 Fix autoloading without composer update -o 2014-09-02 08:22:50 +03:00
Djamil Legato
0d0c22c940 Added destination option to allow using the IndexCommand on a different grav instance
IndexCommand displays the local and remote versions. It also detects if the package is already installed
2014-09-01 22:12:55 -07:00
Djamil Legato
1ac4014d64 Updated autoload 2014-09-01 17:59:03 -07:00
Djamil Legato
627646bf35 Merge branch 'develop' into feature/vendor_free 2014-09-01 17:57:48 -07:00
Djamil Legato
67a325e8e9 Removing the tmp folder when finished installing 2014-09-01 17:57:13 -07:00
Djamil Legato
69e345f64f Added InstallCommand that allows to install packages from the index list of grav 2014-09-01 17:53:34 -07:00
Matias Griese
ac7d1459d6 Move configure() from Theme to Themes class 2014-09-01 14:25:12 +03:00
Matias Griese
f66bc37b1b Fix ResourceLocator not working with multiple paths 2014-09-01 14:23:50 +03:00
Djamil Legato
37980d15e4 Added InfoCommand that provides more detailed informations about a package like description, author, version, etc. 2014-08-31 22:59:01 -07:00
Djamil Legato
3fc6c47b5e Added IndexCommand that lists all the resources available to download and install 2014-08-31 22:58:10 -07:00
Djamil Legato
dee24787ba Added FetchCommand to grab all the resources informations from getgrav.org and cache it for a day 2014-08-31 22:57:18 -07:00
Djamil Legato
7d9142fa03 Initial commit for GPM (Grav Package Manager) 2014-08-31 22:56:25 -07:00
Djamil Legato
51cca81bc8 Moved all console commands under the Cli folder for better organization (this requires a composer update -o to pick up) 2014-08-31 22:54:35 -07:00
Djamil Legato
8de6231116 Added fallbacks for URI class so that it can be used in the CLI 2014-08-31 22:52:04 -07:00
Andy Miller
e0ade6c658 Merge branch 'develop' of github.com:getgrav/grav into develop 2014-08-31 14:34:44 -06:00
Andy Miller
8cbe0ec591 added fix for file-based paths including .md filenames 2014-08-31 14:34:32 -06:00
Andy Miller
015490fc21 Update README.md 2014-08-30 14:35:29 -06:00
Andy Miller
a8c5d70967 Updated README with some more core feature links 2014-08-30 14:34:47 -06:00
Andy Miller
f95d6b11de Added support for absolute path images and refactored a little 2014-08-30 14:19:14 -06:00
Andy Miller
a33bfd8cb0 Added support for relative links and actual path links to markdown links 2014-08-30 13:40:55 -06:00
Andy Miller
188e7ddb75 Added updated minify package with fix for @supports, vendor commits and cleanup 2014-08-29 17:22:59 -06:00
Andy Miller
d3d02a2c79 Revert "Added Content-Length header"
This reverts commit a737cfc2ec.
2014-08-29 15:03:19 -06:00
Andy Miller
365f7bb596 Should address issue #38 where some people are not getting cache clears 2014-08-29 14:42:50 -06:00
Djamil Legato
a737cfc2ec Added Content-Length header 2014-08-29 10:01:05 -07:00
Matias Griese
6ce32e753d Fix theme thumbnail urls 2014-08-29 12:07:40 +03:00
Matias Griese
8f8e0b08a4 Add theme:/// to be able to refer all the themes 2014-08-29 11:57:49 +03:00
Matias Griese
a702af2348 Fix some misc bugs for admin plugin 2014-08-29 11:26:05 +03:00
Andy Miller
4b6be8e513 just a variable name change 2014-08-28 18:05:50 -06:00
Andy Miller
aa9d77cf8b Forgot to return the last_modified timestamp, and some cleanup 2014-08-28 18:01:12 -06:00
Andy Miller
bea19e008f Added method to check for changes by file rather than by folder, made it default option as its safest 2014-08-28 17:04:44 -06:00
Matias Griese
4e3db05d10 Some changes to make custom template object instantiation to work 2014-08-28 20:26:14 +03:00
Djamil Legato
9b3b463909 Moved Browser class into its proper location 2014-08-27 18:25:25 -07:00
Andy Miller
ad5d9eb3b7 Merge pull request #37 from ghLoser/patch-2
Update Markdown.php - Looks good! thanks.
2014-08-27 12:14:52 -06:00
ghLoser
3c1673d112 Update Markdown.php 2014-08-27 19:28:23 +02:00
Andy Miller
8f9780a443 Removed old unused processString method 2014-08-26 17:06:25 -06:00
Djamil Legato
2935d3cc0b Uri extension set for specific types only rather than all 2014-08-25 17:15:39 -07:00
Djamil Legato
fbc7ca4ef2 Merge branch 'release/0.9.0' 2014-08-25 10:30:24 -07:00
Djamil Legato
e3626fcbcd Merge branch 'release/0.9.0' into develop 2014-08-25 10:30:24 -07:00
Djamil Legato
1e39c7d925 Preparing for release 2014-08-25 10:06:34 -07:00
Djamil Legato
8a37278f79 Removing events settings from user config as they are now in the core 2014-08-25 09:11:16 -07:00
Andy Miller
c1638a2b9d Merge branch 'feature/updated_htaccess' into develop 2014-08-25 09:35:31 -06:00
Andy Miller
aee8895f8d Merge branch 'develop' into feature/updated_htaccess 2014-08-25 09:35:05 -06:00
Andy Miller
b442aa2dd6 Added page option for markdown_extra 2014-08-25 09:29:35 -06:00
Andy Miller
65720df28d Added a new random sort order option 2014-08-25 08:56:38 -06:00
Andy Miller
4a1989e3a8 Added a default 'detect' mode for Tracy, can be changed via debugger.mode 2014-08-25 08:40:41 -06:00
Andy Miller
a585430a5c This would cause problem 2014-08-24 12:07:45 -06:00
Andy Miller
c1073fec0c Different tweaks 2014-08-24 11:51:14 -06:00
Andy Miller
9367228800 Added an entry about potential need of RewriteBase and tweaked the index.php rewrite rule 2014-08-24 11:33:29 -06:00
Djamil Legato
6add01da97 Added php version check on the CLI with message when exiting #25 2014-08-23 10:50:18 -07:00
Andy Miller
74b838c1bc just a question... 2014-08-23 09:44:08 -06:00
Andy Miller
a5a97b5396 enabled page plugins by default as these are not called when cached 2014-08-23 06:54:13 -06:00
Andy Miller
ca519dd5c4 Refactored the markdown link trait to use call_user_func_array() rather eval() which is slow and rather dangerous. 2014-08-22 20:39:25 -06:00
Andy Miller
7a622c6f5b switching user agent package 2014-08-22 14:07:45 -06:00
Matias Griese
b507815eef Fix some random bugs 2014-08-22 19:30:17 +03:00
Andy Miller
f3aec65624 Added support for custom inline images with markdown #10, no more twig needed! 2014-08-21 21:55:37 -06:00
Andy Miller
447abb928b Added a fix that should help on some hosting providers 2014-08-21 14:46:13 -06:00
Andy Miller
045fce62a6 updates to remove pimple tests from autoloader 2014-08-21 11:40:51 -06:00
Matias Griese
c56efb9a10 Use stream wrapper in pages, plugins and themes 2014-08-21 13:06:48 +03:00
Matias Griese
eba9002400 Implement ArrayTraits on Blueprint 2014-08-21 13:01:30 +03:00
Matias Griese
7d5426144d Implement more ArrayTraits and use them in Iterator 2014-08-21 12:58:48 +03:00
Andy Miller
ec3b5d6c73 OMG, cache was OFF by default, wandering what was going on! 2014-08-20 18:26:46 -06:00
Andy Miller
3f0af44e8c Added /asssets dir to the setup command 2014-08-20 18:26:14 -06:00
Andy Miller
ec8cbbb920 updated composer configuration 2014-08-20 13:57:16 -06:00
Andy Miller
5e07b1ebfd added a helpful dump wrapper 2014-08-20 13:47:10 -06:00
Andy Miller
ea227913da switched assets to use new php stream locators 2014-08-20 13:46:46 -06:00
Matias Griese
dd4a2aa288 Add scheme support for Assets class 2014-08-20 21:37:19 +03:00
Matias Griese
48d8284124 Add support for theme stream wrappers 2014-08-20 20:55:27 +03:00
Matias Griese
fae64f5b7a Add basic theme configuration support 2014-08-20 20:51:49 +03:00
Andy Miller
9d0e917e60 vendor commits 2014-08-20 09:25:34 -06:00
Matias Griese
a6483f0333 Prevent fatal error when composer hasn't been run 2014-08-20 14:51:03 +03:00
Matias Griese
77172e7978 Optimize autoloading and allow APC to cache plugin PHP 2014-08-20 12:18:26 +03:00
Andy Miller
05b2296594 more semantic naming of variables 2014-08-19 21:57:44 -06:00
Andy Miller
49244c3f0b made $grav public so its accessible in plugins 2014-08-19 21:57:29 -06:00
Andy Miller
ba9e813fbb Merge branch 'feature/setup_symlink_patch' into develop 2014-08-19 20:38:36 -06:00
Andy Miller
cb4428068d #18 Refactoring per the new page events system 2014-08-19 19:10:00 -06:00
Andy Miller
ecb412ea6d vendor updates 2014-08-19 14:58:47 -06:00
Andy Miller
3794d757f4 Merge branch 'feature/streamwrappers' into develop 2014-08-19 10:53:22 -06:00
Matias Griese
7561ace5e2 Merge branches 'develop' and 'feature/streamwrappers' of https://github.com/getgrav/grav into feature/streamwrappers
Conflicts:
	system/src/Grav/Common/Grav.php
2014-08-19 19:39:48 +03:00
Matias Griese
a85c2638e4 Fix #17: Use Symfony EventDispatcher 2014-08-19 18:45:42 +03:00
Matias Griese
dcc4335f8e Fix regression: plugins not firing 2014-08-19 09:56:18 +03:00
Andy Miller
19b23746dc minor trim fix 2014-08-18 17:12:47 -06:00
Andy Miller
1295637355 #15 Workaround for apache style userdir (~username) routing 2014-08-18 17:12:32 -06:00
Andy Miller
620c204128 extra Tracy use in Assets 2014-08-18 17:09:13 -06:00
Matias Griese
3952e5d73e Add a few stream wrappers 2014-08-18 22:27:27 +03:00
Matias Griese
1a2d528131 Merge branch 'feature/DI' into develop 2014-08-18 21:27:29 +03:00
Matias Griese
a42144c3f7 Implement stream wrappers 2014-08-18 21:24:32 +03:00
Djamil Legato
53cec9b620 Fixed setup command in combination of symlink not linking properly directories. 2014-08-18 09:56:26 -07:00
Matias Griese
e3da090768 Use similar way to get Grav context across the classes 2014-08-18 19:05:20 +03:00
Matias Griese
285874caf5 Fix exception using wrong namespace in Assets 2014-08-18 17:22:44 +03:00
Matias Griese
b0bf847208 Convert DI variables to lower case 2014-08-18 15:23:53 +03:00
Matias Griese
846c836cb8 Add DEV/PROD parameter to debugger constructor 2014-08-18 14:47:11 +03:00
Matias Griese
fcedae6085 Add missing vendor dependencies 2014-08-18 14:41:06 +03:00
Matias Griese
0c374eac23 Merge branch 'develop' of https://github.com/getgrav/grav into feature/DI
Conflicts:
	composer.json
	index.php
	system/src/Grav/Common/Grav.php
	system/src/Grav/Common/Themes.php
	system/src/Grav/Common/Twig.php
2014-08-18 14:39:50 +03:00
Matias Griese
e91b9c84bf Implement DI container 2014-08-18 14:13:51 +03:00
Andy Miller
9f2236dea3 Added support for Markdown Extra - optional and default is GFM style markdown 2014-08-17 18:54:10 -06:00
Andy Miller
8374d03a36 Added some additional input filtering on query and param options 2014-08-17 18:20:23 -06:00
Andy Miller
419bceb47a GRAV_VERSION available in twig for use in templates 2014-08-17 17:46:22 -06:00
Andy Miller
54de6f2ce5 set tracy debugger to production mode by default, set to development by enabling in the configuration 2014-08-17 17:31:05 -06:00
Andy Miller
5f8b37507e fixes for pipeline assets at root / rather than /subdir 2014-08-17 17:16:54 -06:00
Andy Miller
b716ca3270 removed left over debug :( 2014-08-17 16:01:19 -06:00
Andy Miller
fe73d829e5 Added empty assets folder 2014-08-17 15:55:56 -06:00
Andy Miller
36181dbce1 Merge branch 'feature/asset_management' into develop 2014-08-17 15:53:38 -06:00
Andy Miller
57f969c806 Merge branch 'develop' of github.com:getgrav/grav into feature/asset_management 2014-08-17 15:50:47 -06:00
Andy Miller
215c8da542 clean up codebase a little 2014-08-17 15:49:38 -06:00
Andy Miller
c1b2b5f56f Merge pull request #3 from pborreli/typos
Fixed typos/CS
2014-08-17 14:01:30 -06:00
Pascal Borreli
41aadf25ed Fixed typos/CS 2014-08-17 20:29:40 +01:00
Andy Miller
0e9417ab1b initial commit of new asset pipeline work + user agent handling 2014-08-15 22:05:22 -06:00
Ryan Matthew Pierson
69ca8bc934 Update README.md 2014-08-15 15:31:49 -05:00
Djamil Legato
ae2b6e1f74 Added Contributing section 2014-08-15 13:26:02 -07:00
Matias Griese
9ed16512fe Fix PHP warning on locking without file handle 2014-08-15 15:07:48 +03:00
Matias Griese
ec8c3d9e60 Better error message when template file isn't found 2014-08-15 14:47:48 +03:00
Matias Griese
0c0cc03394 Exit instead of throwing exception when PHP < 5.4.0 2014-08-15 14:47:10 +03:00
Matias Griese
fee9518134 Update system default settings to be more suitable for production 2014-08-15 14:37:44 +03:00
Matias Griese
7612cee9d6 Make images progressive by default 2014-08-15 14:17:14 +03:00
Andy Miller
558a7b9323 Merge pull request #2 from 810/patch-1
Fix: error 500 IIS - Thanks!
2014-08-14 12:51:14 -06:00
Jelle Kok
ab868a4c13 Fix: error 500
When you have already set index.php as default, you get a error 500. 
So first you need to remove it then add it again. This is solving the error 500.
2014-08-14 20:49:29 +02:00
475 changed files with 11197 additions and 44544 deletions

View File

@@ -11,3 +11,8 @@ trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
# 2 space indentation
[*.yaml, *.yml]
indent_style = space
indent_size = 2

4
.gitignore vendored Executable file → Normal file
View File

@@ -1,5 +1,7 @@
# Composer
composer.lock
.composer
vendor/
# Sass
.sass-cache
@@ -7,6 +9,8 @@ composer.lock
# Grav Specific
cache/*
!cache/.*
assets/*
!assets/.*
logs/*
!logs/.*
images/*

65
.htaccess Executable file → Normal file
View File

@@ -1,29 +1,56 @@
<IfModule mod_rewrite.c>
RewriteEngine On
# access site
## Begin RewriteBase
# If you are getting 404 errors on subpages, you may have to uncomment the RewriteBase entry
# You should change the '/' to your appropriate subfolder. For example if you have
# your Grav install at the root of your site '/' should work, else it might be something
# along the lines of: RewriteBase /<your_sub_folder>
##
# RewriteBase /
## End - RewriteBase
## Begin - Exploits
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
#
# Block out any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block out any script that includes a <script> tag in URL.
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
# Block out any script trying to set a PHP GLOBALS variable via URL.
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
# Block out any script trying to modify a _REQUEST variable via URL.
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
# Return 403 Forbidden header and show the content of the root homepage
RewriteRule .* index.php [F]
#
## End - Exploits
## Begin - Index
# If the requested path and file is not /index.php and the request
# has not already been internally rewritten to the index.php script
RewriteCond %{REQUEST_URI} !^/index\.php
# and the requested path and file doesn't directly match a physical file
RewriteCond %{REQUEST_FILENAME} !-f
# and the requested path and file doesn't directly match a physical folder
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]
# internally rewrite the request to the index.php script
RewriteRule .* index.php [L]
## End - Index
# block various user files from being accessed directly
RewriteRule ^user/accounts/(.*)$ error [R=301,L]
RewriteRule ^user/config/(.*)$ error [R=301,L]
RewriteRule ^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ error [R=301,L]
# block cache
RewriteRule ^cache/(.*) error [R=301,L]
# block bin
RewriteRule ^bin/(.*)$ error [R=301,L]
# block system
RewriteRule ^system/(.*)$ error [R=301,L]
# block vendor
RewriteRule ^vendor/(.*)$ error [R=301,L]
## Begin - Security
# Block all direct access for these folders
RewriteRule ^(cache|bin|logs)/(.*) 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
</IfModule>
# Prevent file browsing
# Begin - Prevent Browsing
Options -Indexes
# End - Prevent Browsing

338
CHANGELOG.md Normal file
View File

@@ -0,0 +1,338 @@
# v0.9.16
## 01/30/2015
1. [](#new)
* Added **Retina** and **Responsive** image support via Grav media and `srcset` image attribute
* Added image debug option that overlays responsive resolution
* Added a new image cache stream
2. [](#improved)
* 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
* Updated Parsedown and Parsedown Extra to address bugs
* Various PSR fixes
3. [](#bugfix)
* Fix bug with image dispatch in traditionally _non-routable_ pages
* Fix for markdown link not working on non-current pages
* Fix for markdown images not being found on homepage
# v0.9.15
## 01/23/2015
3. [](#bugfix)
* Typo in video mime types
* Fix for old `markdown_extra` system setting not getting picked up
* Fix in regex for Markdown links with numeric values in path
* Fix for broken image routing mechanism that got broken at some point
* Fix for markdown images/links in pages with page slug override
# v0.9.14
## 01/23/2015
1. [](#new)
* Added **GZip** support
* Added multiple configurations via `setup.php`
* Added base structure for unit tests
* New `onPageContentRaw()` plugin event that processes before any page processing
* Added ability to dynamically set Metadata on page
* Added ability to dynamically configure Markdown processing via Parsedown options
2. [](#improved)
* Refactored `page.content()` method to be more flexible and reliable
* Various updates and fixes for streams resulting in better multi-site support
* Updated Twig, Parsedown, ParsedownExtra, DoctrineCache libraries
* Refactored Parsedown trait
* Force modular pages to be non-visible in menus
* Moved RewriteBase before Exploits in `.htaccess`
* Added standard video formats to Media support
* Added priority for inline assets
* Check for uniqueness when adding multiple inline assets
* Improved support for Twig-based URLs inside Markdown links and images
* Improved Twig `url()` function
3. [](#bugfix)
* Fix for HTML entities quotes in Metadata values
* Fix for `published` setting to have precedent of `publish_date` and `unpublish_date`
* Fix for `onShutdown()` events not closing connections properly in **php-fpm** environments
# v0.9.13
## 01/09/2015
1. [](#new)
* Added new published `true|false` state in page headers
* 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 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)
* Modified all Collection methods so they can be chained together: `$collection->published()->visible()`
* Set default Cache lifetime to default of 1 week (604800 seconds) - was infinite
* House-cleaning of some unused methods in Pages object
3. [](#bugfix)
* Fix `uninstall` GPM command that was broken in last release
* Fix for intermittent `undefined index` error when working with Collections
* Fix for date of some pages being set to incorrect future timestamps
# v0.9.12
## 01/06/2015
1. [](#new)
* Added an all-access robots.txt file for search engines
* Added new GPM `uninstall` command
* Added support for **in-page** Twig processing in **modular** pages
* Added configurable support for `undefined` Twig functions and filters
2. [](#improved)
* Fall back to default `.html` template if error occurs on non-html pages
* Added ability to have PSR-1 friendly plugin names (CamelCase, no-dashes)
* Fix to `composer.json` to deter API rate-limit errors
* Added **non-exception-throwing** handler for undefined methods on `Medium` objects
3. [](#bugfix)
* Fix description for `self-upgrade` method of GPM command
* Fix for incorrect version number when performing GPM `update`
* Fix for argument description of GPM `install` command
* Fix for recalcitrant CodeKit mac application
# v0.9.11
## 12/21/2014
1. [](#new)
* Added support for simple redirects as well as routes
2. [](#improved)
* Handle Twig errors more cleanly
3. [](#bugfix)
* Fix for error caused by invalid or missing user agent string
* Fix for directory relative links and URL fragments (#pagelink)
* Fix for relative links with no subfolder in `base_url`
# v0.9.10
## 12/12/2014
1. [](#new)
* Added Facebook-style `nicetime` date Twig filter
2. [](#improved)
* Moved `clear-cache` functionality into Cache object required for Admin plugin
3. [](#bugfix)
* Fix for undefined index with previous/next buttons
# v0.9.9
## 12/05/2014
1. [](#new)
* Added new `@page` collection type
* Added `ksort` and `contains` Twig filters
* Added `gist` Twig function
2. [](#improved)
* Refactored Page previous/next/adjacent functionality
* Updated to Symfony 2.6 for yaml/console/event-dispatcher libraries
* More PSR code fixes
3. [](#bugfix)
* Fix for over-escaped apostrophes in YAML
# v0.9.8
## 12/01/2014
1. [](#new)
* Added configuration option to set default lifetime on cache saves
* Added ability to set HTTP status code from page header
* Implemented simple wild-card custom routing
2. [](#improved)
* Fixed elusive double load to fully cache issue (crossing fingers...)
* Ensure Twig tags are treated as block items in markdown
* Removed some older deprecated methods
* Ensure onPageContentProcessed() event only fires when not cached
* More PSR code fixes
3. [](#bugfix)
* Fix issue with miscalculation of blog separator location `===`
# v0.9.7
## 11/24/2014
1. [](#improved)
* Nginx configuration updated
* Added gitter.im badge to README
* Removed `set_time_limit()` and put checks around `ignore_user_abort`
* More PSR code fixes
2. [](#bugfix)
* Fix issue with non-valid asset path showing up when they shouldn't
* Fix for JS asset pipeline and scripts that don't end in `;`
* Fix for schema-based markdown URLs broken routes (eg `mailto:`)
# v0.9.6
## 11/17/2014
1. [](#improved)
* Moved base_url variables into Grav container
* Forced media sorting to use natural sort order by default
* Various PSR code tidying
* Added filename, extension, thumb to all medium objects
2. [](#bugfix)
* Fix for infinite loop in page.content()
* Fix hostname for configuration overrides
* Fix for cached configuration
* Fix for relative URLs in markdown on installs with no base_url
* Fix for page media images with uppercase extension
# v0.9.5
## 11/09/2014
1. [](#new)
* Added quality setting to medium for compression configuration of images
* Added new onPageContentProcessed() event that is post-content processing but pre-caching
2. [](#improved)
* Added support for AND and OR taxonomy filtering. AND by default (was OR)
* Added specific clearing options for CLI clear-cache command
* Moved environment method to URI so it can be accessible in plugins and themes
* Set Grav's output variable to public so it can be manipulated in onOutputGenerated event
* Updated vendor libraries to latest versions
* Better handing of 'home' in active menu state detection
* Various PSR code tidying
* Improved some error messages and notices
3. [](#bugfix)
* Force route rebuild when configuration changes
* Fix for 'installed undefined' error in CLI versions command
* Do not remove the JSON/Text error handlers
* Fix for supporting inline JS and CSS when Asset pipeline enabled
* Fix for Data URLs in CSS being badly formed
* Fix Markdown links with fragment and query elements
# v0.9.4
## 10/29/2014
1. [](#new)
* New improved Debugbar with messages, timing, config, twig information
* New exception handling system utilizing Whoops
* New logging system utilizing Monolog
* Support for auto-detecting environment configuration
* New version command for CLI
* Integrate Twig dump() calls into Debugbar
2. [](#improved)
* Selfupgrade now clears cache on successful upgrade
* Selfupgrade now supports files without extensions
* Improved error messages when plugin is missing
* Improved security in .htaccess
* Support CSS/JS/Image assets in vendor/system folders via .htaccess
* Add support for system timers
* Improved and optimized configuration loading
* Automatically disable Debugbar on non-HTML pages
* Disable Debugbar by default
3. [](#bugfix)
* More YAML blueprint fixes
* Fix potential double // in assets
* Load debugger as early as possible
# v0.9.3
## 10/09/2014
1. [](#new)
* GPM (Grav Package Manager) Added
* Support for multiple Grav configurations
* Dynamic media support via URL
* Added inlineCss and inlineJs support for Assets
2. [](#improved)
* YAML caching for increased performance
* Use stream wrapper in pages, plugins and themes
* Switched to RocketTheme toolbox for some core functionality
* Renamed `setup` CLI command to `sandbox`
* 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
* Added ability to filter page children for normal/modular/all
* Added `sort_by_key` twig filter
* Added `visible()` and `routable()` filters to page collections
* Use session class in shutdown process
* Improvements to modular page loading
* Various code cleanup and optimizations
3. [](#bugfix)
* Fixed file checking not updating the last modified time. For real this time!
* Switched debugger to PRODUCTION mode by default
* Various fixes in URI class for increased reliability
# v0.9.2
## 09/15/2014
1. [](#new)
* New flexible site and page metadata support including ObjectGraph and Facebook
* New method to get user IP address in URI object
* Added new onShutdown() event that fires after connection is closed for Async features
2. [](#improved)
* Skip assets pipeline minify on Windows platforms by default due to PHP issue 47689
* Fixed multiple level menus not highlighting correctly
* Updated some blueprints in preparation for admin plugin
* Fail gracefully when theme does not exist
* Add stream support into ResourceLocator::addPath()
* Separate themes from plugins, add themes:// stream and onTask events
* Added barDump() to Debugger
* Removed stray test page
* Override modified only if a non-markdown file was modified
* Added assets attributes support
* Auto-run composer install when running the Grav CLI
* Vendor folder removed from repository
* Minor configuration performance optimizations
* Minor debugger performance optimizations
3. [](#bugfix)
* Fix url() twig function when Grav isn't installed at root
* Workaround for PHP bug 52065
* Fixed getList() method on Pages object that was not working
* Fix for open_basedir error
* index.php now warns if not running on PHP 5.4
* Removed memcached option (redundant)
* Removed memcache from auto setup, added memcache server configuration option
* Fix broken password validation
* Back to proper PSR-4 Autoloader
# v0.9.1
## 09/02/2014
1. [](#new)
* Added new `theme://` PHP stream for current theme
2. [](#improved)
* Default to new `file` modification checking rather than `folder`
* Added support for various markdown link formats to convert to Grav-friendly URLs
* Moved configure() from Theme to Themes class
* Fix autoloading without composer update -o
* Added support for Twig url method
* Minor code cleanup
3. [](#bugfix)
* Fixed issue with page changes not being picked up
* Fixed Minify to provide `@supports` tag compatibility
* Fixed ResourceLocator not working with multiple paths
* Fixed issue with Markdown process not stripping LFs
* Restrict file type extensions for added security
* Fixed template inheritance
* Moved Browser class to proper location
# v0.9.0
## 08/25/2014
1. [](#new)
* 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
* Grav Media support directly in Markdown syntax
* Additional Grav Generator meta tag in default themes
* Added support for PHP Stream Wrapper for resource location
* Markdown Extra support
* Browser object for fast browser detection
2. [](#improved)
* PSR-4 Autoloader mechanism
* Tracy Debugger new `detect` option to detect running environment
* Added new `random` collection sort option
* Make media images progressive by default
* Additional URI filtering for improved security
* Safety checks to ensure PHP 5.4.0+
* Move to Slidebars side navigation in default Antimatter theme
* Updates to `.htaccess` including section on `RewriteBase` which is needed for some hosting providers
3. [](#bugfix)
* Fixed issue when installing in an apache userdir (~username) folder
* Various mobile CSS issues in default themes
* Various minor bug fixes
# v0.8.0
## 08/13/2014
1. [](#new)
* Initial Release

View File

@@ -1,13 +1,19 @@
# ![](https://avatars1.githubusercontent.com/u/8237355?v=2&s=50) Grav
[![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 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.
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:
* [Twig Templating](http://twig.sensiolabs.org/): for powerful control of the user interface
* [Markdown](http://en.wikipedia.org/wiki/Markdown): for easy content creation
* [YAML](http://yaml.org): for simple configuration
* [Doctrine Cache](http://docs.doctrine-project.org/en/2.0.x/reference/caching.html): layer for incredible performance
* [Parsedown](http://parsedown.org/): for fast Markdown and Mardown Extra support
* [Doctrine Cache](http://docs.doctrine-project.org/en/2.0.x/reference/caching.html): layer for performance
* [Pimple Dependency Injection Container](http://pimple.sensiolabs.org/): for extensibility and maintainability
* [Symfony Event Dispacher](http://symfony.com/doc/current/components/event_dispatcher/introduction.html): for plugin event handling
* [Symfony Console](http://symfony.com/doc/current/components/console/introduction.html): for CLI interface
* [Gregwar Image Library](https://github.com/Gregwar/Image): for dynamic image manipulation
# QuickStart
@@ -33,6 +39,47 @@ You can download a **ready-built** package from the [Downloads page on http://ge
Check out the [install procedures](http://learn.getgrav.org/basics/installation) for more information.
# Adding Functionality
You can download manually from the [Downloads page on http://getgrav.org](http://getgrav.org/downloads), but the preferred solution is to use the [Grav Package Manager](http://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
```
$ bin/gpm index
```
This will display all the available plugins and then you can install one ore more with:
```
$ bin/gpm install <plugin/theme>
```
# Updating
To update Grav you should use the [Grav Package Manager](http://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
```
$ bin/gpm selfupgrade
```
To update plugins and themes:
```
$ bin/gpm update
```
# Contributing
We appreciate any contribution to Grav, whether it is related to bugs, grammar, or simply a suggestion or improvement.
However, we ask that any contribution follow our simple guidelines in order to be properly received.
All our projects follow the [GitFlow branching model][gitflow-model], from development to release. If you are not familiar with it, there are several guides and tutorials to make you understand what it is about.
You will probably want to get started by installing [this very good collection of git extensions][gitflow-extensions].
What you mainly want to know is that:
- All the main activity happens in the `develop` branch. Any pull request should be addressed only to that branch. We will not consider pull requests made to the `master`.
- It's very well appreciated, and highly suggested, to start a new feature whenever you want to make changes or add functionalities. It will make it much easier for us to just checkout your feature branch and test it, before merging it into `develop`
# Getting Started
@@ -51,3 +98,7 @@ Check out the [install procedures](http://learn.getgrav.org/basics/installation)
# License
See [LICENSE](LICENSE)
[gitflow-model]: http://nvie.com/posts/a-successful-git-branching-model/
[gitflow-extensions]: https://github.com/nvie/gitflow

View File

@@ -1 +0,0 @@
0.8.0

0
assets/.gitkeep Normal file
View File

BIN
bin/composer.phar Executable file

Binary file not shown.

45
bin/gpm Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env php
<?php
define('GRAV_CLI', true);
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
}
if (!file_exists(__DIR__ . '/../vendor')){
// Before we can even start, we need to run composer first
echo "Preparing to install vendor dependencies...\n\n";
echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
echo "\n\n";
}
use Symfony\Component\Console\Application;
use Grav\Common\Grav;
$autoload = require_once(__DIR__ . '/../vendor/autoload.php');
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
if (!file_exists(ROOT_DIR . 'index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}
$grav = Grav::instance(array('loader' => $autoload));
$grav['config']->init();
$grav['streams'];
$grav['plugins']->init();
$grav['themes']->init();
$app = new Application('Grav Package Manager', GRAV_VERSION);
$app->addCommands(array(
new \Grav\Console\Gpm\IndexCommand(),
new \Grav\Console\Gpm\VersionCommand(),
new \Grav\Console\Gpm\InfoCommand(),
new \Grav\Console\Gpm\InstallCommand(),
new \Grav\Console\Gpm\UninstallCommand(),
new \Grav\Console\Gpm\UpdateCommand(),
new \Grav\Console\Gpm\SelfupgradeCommand(),
));
$app->run();

View File

@@ -1,9 +1,21 @@
#!/usr/bin/env php
<?php
define('GRAV_CLI', true);
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
}
if (!file_exists(__DIR__ . '/../vendor')){
// Before we can even start, we need to run composer first
echo "Preparing to install vendor dependencies...\n\n";
echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
echo "\n\n";
}
use Symfony\Component\Console\Application;
require_once(__DIR__ . '/../system/autoload.php');
require_once __DIR__ . '/../vendor/autoload.php';
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
@@ -15,11 +27,11 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
$app = new Application('Grav CLI Application', '0.1.0');
$app->addCommands(array(
new Grav\Console\InstallCommand(),
new Grav\Console\SetupCommand(),
new Grav\Console\CleanCommand(),
new Grav\Console\ClearCacheCommand(),
new Grav\Console\BackupCommand(),
new Grav\Console\NewProjectCommand(),
new Grav\Console\Cli\InstallCommand(),
new Grav\Console\Cli\SandboxCommand(),
new Grav\Console\Cli\CleanCommand(),
new Grav\Console\Cli\ClearCacheCommand(),
new Grav\Console\Cli\BackupCommand(),
new Grav\Console\Cli\NewProjectCommand(),
));
$app->run();

View File

@@ -1,25 +1,42 @@
{
"name": "rhuk/grav",
"name": "getgrav/grav",
"type": "library",
"description": "Grav is a powerful flat CMS influenced by Pico, Stacey, Kirby and others...",
"keywords": ["cms"],
"description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS",
"keywords": ["cms","flat-file cms","flat cms","flatfile cms","php"],
"homepage": "http://getgrav.org",
"license": "MIT",
"authors": [
"require": {
"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",
"maximebf/debugbar": "dev-master",
"filp/whoops": "1.2.*@dev",
"monolog/monolog": "~1.1",
"gregwar/image": "~2.0",
"ircmaxell/password-compat": "1.0.*",
"mrclay/minify": "dev-master",
"donatj/phpuseragentparser": "dev-master",
"pimple/pimple": "~3.0",
"rockettheme/toolbox": "dev-develop"
},
"repositories": [
{
"name": "Andy Miller",
"email": "rhuk@getgrav.org"
"type": "vcs",
"no-api": true,
"url": "https://github.com/rockettheme/toolbox"
}
],
"require": {
"php": ">=5.3.10",
"twig/twig": "1.16.*@dev",
"erusev/parsedown": "dev-master",
"symfony/yaml": "2.5.*@dev",
"symfony/console": "2.5.*@dev",
"doctrine/cache": "1.4.*@dev",
"tracy/tracy": "dev-master",
"gregwar/image": "dev-master",
"ircmaxell/password-compat": "1.0.*"
"autoload": {
"psr-4": {
"Grav\\": "system/src/Grav"
},
"files": ["system/defines.php"]
},
"archive": {
"exclude": ["VERSION"]
}
}

56
htaccess.txt Normal file
View File

@@ -0,0 +1,56 @@
<IfModule mod_rewrite.c>
RewriteEngine On
## Begin RewriteBase
# If you are getting 404 errors on subpages, you may have to uncomment the RewriteBase entry
# You should change the '/' to your appropriate subfolder. For example if you have
# your Grav install at the root of your site '/' should work, else it might be something
# along the lines of: RewriteBase /<your_sub_folder>
##
# RewriteBase /
## End - RewriteBase
## Begin - Exploits
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
#
# Block out any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block out any script that includes a <script> tag in URL.
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
# Block out any script trying to set a PHP GLOBALS variable via URL.
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
# Block out any script trying to modify a _REQUEST variable via URL.
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
# Return 403 Forbidden header and show the content of the root homepage
RewriteRule .* index.php [F]
#
## End - Exploits
## Begin - Index
# If the requested path and file is not /index.php and the request
# has not already been internally rewritten to the index.php script
RewriteCond %{REQUEST_URI} !^/index\.php
# and the requested path and file doesn't directly match a physical file
RewriteCond %{REQUEST_FILENAME} !-f
# and the requested path and file doesn't directly match a physical folder
RewriteCond %{REQUEST_FILENAME} !-d
# internally rewrite the request to the index.php script
RewriteRule .* index.php [L]
## End - Index
## Begin - Security
# Block all direct access for these folders
RewriteRule ^(cache|bin|logs)/(.*) 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
</IfModule>
# Begin - Prevent Browsing
Options -Indexes
# End - Prevent Browsing

View File

@@ -1,46 +1,35 @@
<?php
namespace Grav\Common;
namespace Grav;
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
throw new \RuntimeException(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
exit(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
}
use Tracy\Debugger;
$autoload = __DIR__ . '/vendor/autoload.php';
if (!is_file($autoload)) {
exit('Please run: <i>bin/grav install</i>');
}
// Register system libraries to the auto-loader.
$loader = require_once __DIR__ . '/system/autoload.php';
use Grav\Common\Grav;
// Register the auto-loader.
$loader = require_once $autoload;
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
// Use output buffering to prevent headers from being sent too early.
ob_start();
// Start the timer and enable debugger in production mode as we do not have system configuration yet.
// Debugger catches all errors and logs them, for example if the script doesn't have write permissions.
Debugger::timer();
Debugger::enable(Debugger::DEVELOPMENT, is_dir(LOG_DIR) ? LOG_DIR : null);
$grav = new Grav;
$grav = Grav::instance(
array(
'loader' => $loader
)
);
try {
// Register all the Grav bits into registry.
$registry = Registry::instance();
$registry->store('autoloader', $loader);
$registry->store('Grav', $grav);
$registry->store('Uri', new Uri);
$registry->store('Config', Config::instance(CACHE_DIR . 'config.php'));
$registry->store('Cache', new Cache);
$registry->store('Twig', new Twig);
$registry->store('Pages', new Page\Pages);
$registry->store('Taxonomy', new Taxonomy);
$grav->process();
} catch (\Exception $e) {
$grav->fireEvent('onFatalException', $e);
$grav->fireEvent('onFatalException');
throw $e;
}
ob_end_flush();

32
nginx.conf Executable file → Normal file
View File

@@ -27,26 +27,26 @@ http {
}
location /user {
rewrite ^/user/accounts/(.*)$ /error redirect;
rewrite ^/user/config/(.*)$ /error redirect;
rewrite ^/user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
rewrite ^/user/accounts/(.*)$ /error redirect;
rewrite ^/user/config/(.*)$ /error redirect;
rewrite ^/user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
location /cache {
rewrite ^/cache/(.*) /error redirect;
}
location /cache {
rewrite ^/cache/(.*) /error redirect;
}
location /bin {
rewrite ^/bin/(.*)$ /error redirect;
}
location /bin {
rewrite ^/bin/(.*)$ /error redirect;
}
location /system {
rewrite ^/system/(.*)$ /error redirect;
}
location /system {
rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
location /vendor {
rewrite ^/vendor/(.*)$ /error redirect;
}
location /vendor {
rewrite ^/vendor/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
# Remember to change 127.0.0.1:9000 to the Ip/port
# you configured php-cgi.exe to run from

2
robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

View File

@@ -0,0 +1,54 @@
div.phpdebugbar {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.phpdebugbar pre {
padding: 1rem;
}
.phpdebugbar div.phpdebugbar-header > div > * {
padding: 5px 15px;
}
.phpdebugbar div.phpdebugbar-header > div.phpdebugbar-header-right > * {
padding: 5px 8px;
}
.phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn {
background-image: url(grav.png);
}
.phpdebugbar a.phpdebugbar-restore-btn {
width: 13px;
}
.phpdebugbar a.phpdebugbar-tab.phpdebugbar-active {
background: #3DB9EC;
color: #fff;
margin-top: -1px;
padding-top: 6px;
}
.phpdebugbar .phpdebugbar-widgets-toolbar {
padding-left: 5px;
}
.phpdebugbar input[type=text] {
padding: 0;
display: inline;
}
.phpdebugbar dl.phpdebugbar-widgets-varlist, ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
font-size: 12px;
}
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
top: 0;
}
.phpdebugbar pre, .phpdebugbar code {
margin: 0;
font-size: 14px;
}

BIN
system/assets/grav.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

110
system/assets/whoops.css Normal file
View File

@@ -0,0 +1,110 @@
body {
background-color: #eee;
}
body header {
background: #349886;
border-left: 8px solid #29796B;
}
body .clipboard {
width: 28px;
height: 28px;
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcAQMAAABIw03XAAAAA3NCSVQICAjb4U/gAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAFnRFWHRDcmVhdGlvbiBUaW1lADEwLzE1LzE0xr/LJAAAADhJREFUCJlj+P///wcGBPGDQR5E8OMi2IEEczOIaAQRHSCioQBGHAAR/7AT/z+DiA8MMALVXhABAJf9Sr5aY+UFAAAAAElFTkSuQmCC);
}
body .exc-title-primary {
color: #1C3631;
text-shadow: none;
}
body .exc-title {
color: #2F5B52;
text-shadow: none;
}
body .data-table-container label {
color: #0082BA;
}
body .frame {
border: 0;
}
body .frames-container {
overflow-y: auto;
overflow-x: hidden;
}
body .active .frame-class {
color: #E3D8E9;
}
body .frame-class {
color: #9055AF;
}
body .frame.active {
border: 0;
box-shadow: none;
background-color: #9055AF;
}
body .frame:not(.active):hover {
background: #e9e9e9;
}
body .frame-file, body .data-table tbody {
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
font-size: 13px;
}
body .frame-code {
background: #305669;
border-left: 8px solid #253A47;
padding: 1rem;
}
body .frame-code .frame-file {
background: #253A47;
color: #eee;
text-shadow: none;
box-shadow: none;
font-family: inherit;
}
body .frame-code .frame-file strong {
color: #fff;
font-weight: normal;
}
body .frame-comments {
background: #283E4D;
box-shadow: none;
}
body .frame-comments.empty:before {
color: #789AAB;
}
body .details-container {
border: 0;
}
body .details {
background-color: #eee;
border-left: 8px solid #ddd;
padding: 1rem;
}
body .code-block {
background: #2C4454;
box-shadow: none;
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
font-size: 13px;
}
body .handler.active {
background: #666;
}

View File

@@ -1,8 +0,0 @@
<?php
require_once(__DIR__ . '/../system/defines.php');
// Use composer auto-loader and just add our namespace into it.
$loader = require_once(__DIR__ . '/../vendor/autoload.php');
$loader->addPsr4('Grav\\', LIB_DIR . 'Grav');
return $loader;

View File

@@ -1,5 +1,5 @@
title: Media
validation: loose
form:
validation: loose
fields:

View File

@@ -0,0 +1,70 @@
title: Site
form:
validation: loose
fields:
content:
type: section
title: Defaults
fields:
title:
type: text
label: Site Title
size: large
placeholder: "Site wide title"
help: Default title for your site
author.name:
type: text
size: large
label: Default Author
author.email:
type: text
size: large
label: Default Email
taxonomies:
type: text
size: large
label: Taxonomy Types
classes: fancy
validate:
type: commalist
metadata:
type: array
label: Metadata
placeholder_key: Name
placeholder_value: Content
blog:
type: section
title: Blog
fields:
blog.route:
type: text
size: large
label: Blog URL
summary.size:
type: text
size: x-small
label: Summary Size
validate:
type: int
min: 0
max: 65536
routes:
type: section
title: Routes
fields:
routes:
type: array
label: Custom
placeholder_key: /your/alias
placeholder_value: /your/route

View File

@@ -0,0 +1,7 @@
title: File Streams
form:
validation: loose
fields:
schemes.xxx:
type: array

View File

@@ -0,0 +1,364 @@
title: System
form:
validation: loose
fields:
content:
type: section
title: Content
underline: true
fields:
home.alias:
type: pages
size: medium
classes: fancy
label: Home Page
show_all: false
show_modular: false
show_root: false
help: "The page that Grav will use as the default landing page"
pages.theme:
type: themeselect
classes: fancy
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
pages.process:
type: checkboxes
label: Process
default: [markdown: true, twig: true]
options:
markdown: Markdown
twig: Twig
use: keys
pages.dateformat.short:
type: select
size: medium
classes: fancy
label: Short Date Format
help: "Set the short date format"
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"
pages.dateformat.long:
type: select
size: medium
classes: fancy
label: Long Date Format
help: "Set the long date format"
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"
pages.order.by:
type: select
size: medium
classes: fancy
label: Default Ordering
options:
default: Default - based on folder name
folder: Folder - based on prefix-less folder name
title: Title - based on title field in header
date: Date - based on date field in header
pages.order.dir:
type: toggle
label: Default Order Direction
highlight: asc
default: desc
options:
asc: Ascending
desc: Descending
pages.list.count:
type: text
size: x-small
label: Default Item Count
help: "Default max pages count"
validate:
type: number
min: 1
events:
type: section
title: Events
underline: true
fields:
pages.events.page:
type: toggle
label: Page events
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
pages.events.twig:
type: toggle
label: Twig events
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
caching:
type: section
title: Caching
underline: true
fields:
cache.enabled:
type: toggle
label: Caching
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
cache.check.method:
type: select
size: small
classes: fancy
label: Cache Check Method
options:
file: File
folder: Folder
none: None
cache.driver:
type: select
size: small
classes: fancy
label: Cache driver
options:
auto: Auto detect
file: File
apc: APC
xcache: XCache
memcache: MemCache
wincache: WinCache
cache.prefix:
type: text
size: x-small
label: Cache Prefix
placeholder: "Derived from base URL (override by entering random string)"
twig:
type: section
title: Twig Templating
underline: true
fields:
twig.cache:
type: toggle
label: Twig caching
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
twig.debug:
type: toggle
label: Twig debug
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
twig.auto_reload:
type: toggle
label: Detect changes
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
twig.autoescape:
type: toggle
label: Autoescape variables
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
assets:
type: section
title: Assets
underline: true
fields:
assets.css_pipeline:
type: toggle
label: CSS Pipeline
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
assets.css_minify:
type: toggle
label: CSS Minify
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
assets.css_minify_windows:
type: toggle
label: CSS Minify Windows Override
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
assets.css_rewrite:
type: toggle
label: CSS Rewrite
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
assets.js_pipeline:
type: toggle
label: JavaScript Pipeline
highlight: 01
options:
1: Yes
0: No
validate:
type: bool
assets.js_minify:
type: toggle
label: JavaScript Minify
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
debugger:
type: section
title: Debugger
underline: true
fields:
debugger.enabled:
type: toggle
label: Debugger
highlight: 1
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:
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
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
debugger.shutdown.close_connection:
type: toggle
label: Shutdown Close Connection
highlight: 1
options:
1: Yes
0: No
validate:
type: bool

View File

@@ -0,0 +1,54 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
section:
type: section
title: Add Modular Content
title:
type: text
label: Page Title
validate:
required: true
folder:
type: text
label: Folder Name
validate:
type: slug
required: true
route:
type: select
label: Page
classes: fancy
@data-options: '\Grav\Common\Page\Pages::parents'
@data-default: '\Grav\Plugin\admin::route'
options:
'': '- Select -'
validate:
required: true
type:
type: select
classes: fancy
label: Modular Template
default: default
@data-options: '\Grav\Common\Page\Pages::modularTypes'
validate:
required: true
modular:
type: hidden
default: 1
validate:
type: bool

View File

@@ -0,0 +1,84 @@
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:
frontmatter:
type: frontmatter
label: Frontmatter
content:
type: markdown
label: Content
uploads:
type: uploads
label: Page Media
options:
type: tab
title: Options
fields:
columns:
type: columns
fields:
column1:
type: column
fields:
folder:
type: text
label: Filename
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: Modular Template
default: default
@data-options: '\Grav\Common\Page\Pages::modularTypes'
validate:
required: true
column2:
type: column
fields:
order:
type: order
label: Ordering

View File

@@ -0,0 +1,48 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
section:
type: section
title: Add Page
title:
type: text
label: Page Title
validate:
required: true
folder:
type: text
label: Folder Name
validate:
type: slug
required: true
route:
type: select
label: Parent Page
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

@@ -5,14 +5,14 @@ rules:
max: 80
form:
validation: loose
fields:
route:
type: select
label: Parent
@data-options: '\Grav\Common\Page\Pages::parents'
@data-default: '\Grav\Plugin\admin::route'
options:
'': '- Root -'
title:
type: text
label: Title
validate:
required: true
folder:
type: text
@@ -21,8 +21,22 @@ form:
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
label: Page Type
classes: fancy
label: Display Template
default: default
@data-options: '\Grav\Common\Page\Pages::types'
validate:
required: true

View File

@@ -0,0 +1,84 @@
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:
frontmatter:
type: frontmatter
label: Frontmatter
content:
type: markdown
label: Content
uploads:
type: uploads
label: Page Media
options:
type: tab
title: Options
fields:
columns:
type: columns
fields:
column1:
type: column
fields:
folder:
type: text
label: Folder Name
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
column2:
type: column
fields:
order:
type: order
label: Ordering

View File

@@ -1,42 +0,0 @@
title: Site settings
validation: strict
form:
fields:
title:
type: text
label: Site title
description:
type: textarea
label: Description
summary.size:
type: text
label: Summary size
validate:
type: int
min: 0
max: 65536
author.name:
type: text
label: Default author
author.email:
type: text
label: Default email
taxonomies:
type: text
label: Taxonomy types
validate:
type: commalist
blog.route:
type: text
label: Blog URL
routes:
type: array
label: Custom routes

View File

@@ -1,275 +0,0 @@
title: Configuration
validation: strict
form:
fields:
basics:
type: section
title: Basics
underline: true
fields:
title:
type: text
label: Site Title
placeholder: "Site wide title"
help: Default title for your site
base_url_absolute:
type: text
label: Absolute Base URL
placeholder: "Override Absolute base URL (e.g. http://example.com)"
help: You can provide a base URL to use rather than letting Grav guess what it is
base_url_relative:
type: text
label: Relative Base URL
placeholder: "Override Relative base URL (e.g. /subdirectory/site)"
help: You can provide a base URL to use rather than letting Grav guess what it is
pages.dateformat.short:
type: select
label: PHP date format
help: "Set the PHP date format"
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"
pages.dateformat.long:
type: select
label: Default date format
help: "Set default date format rather than default for |date() filter"
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"
pages.theme:
type: themeselect
label: Default Theme
help: "Set the theme (defaults to 'default')"
content:
type: section
title: Content
underline: true
fields:
home.alias:
type: pages
label: Home page
show_all: false
show_modular: false
show_root: false
help: "The page that Grav will use as the default landing page"
pages.order.by:
type: select
label: Default ordering
options:
default: Default - based on folder name
folder: Folder - based on prefix-less folder name
title: Title - based on title field in header
date: Date - based on date field in header
pages.order.dir:
type: toggle
label: Ordering direction
default: asc
options:
asc: Ascending
desc: Descending
pages.list.count:
type: text
label: Item count
help: "Default max pages count"
validate:
type: number
min: 1
pages.process:
type: checkboxes
label: Process
default: [markdown: true, twig: true]
options:
markdown: Markdown
twig: Twig
use: keys
events:
type: section
title: Events
underline: true
fields:
pages.events.page:
type: toggle
label: Page events
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
pages.events.twig:
type: toggle
label: Twig events
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
caching:
type: section
title: Caching
underline: true
fields:
cache.enabled:
type: toggle
label: Caching
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
cache.check.pages:
type: toggle
label: Detect changes in pages
help: Be careful changing this setting. If you disable this setting, Grav no longer automatically updates the pages when the underlaying files are changed.
highlight: 1
default: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
cache.prefix:
type: text
label: Cache prefix
placeholder: "Derived from base URL (override by entering random string)"
cache.driver:
type: select
label: Cache driver
options:
auto: Auto detect
file: File
apc: APC
xcache: XCache
memcache: MemCache
memcached: MemCached
wincache: WinCache
twig:
type: section
title: Twig Templating
underline: true
fields:
twig.cache:
type: toggle
label: Twig caching
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
twig.debug:
type: toggle
label: Twig debug
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
twig.auto_reload:
type: toggle
label: Detect changes
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
twig.autoescape:
type: toggle
label: Autoescape variables
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
debugger:
type: section
title: Debugger
underline: true
fields:
debugger.enabled:
type: toggle
label: Debugger
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
debugger.max_depth:
type: select
label: Detail levels
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
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool
debugger.log.timing:
type: toggle
label: Log timings
highlight: 1
options:
1: Enabled
0: Disabled
validate:
type: bool

View File

@@ -0,0 +1,86 @@
title: Site
form:
validation: loose
fields:
content:
type: section
title: Account
fields:
username:
type: text
size: large
label: Username
readonly: true
email:
type: text
size: large
label: Email
validate:
required: true
password:
type: password
size: large
label: Password
validate:
required: true
fullname:
type: text
size: large
label: Full name
validate:
required: true
title:
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

@@ -0,0 +1,15 @@
title: Add Account
form:
validation: loose
fields:
content:
type: section
title: Add Account
username:
type: text
label: Username
validate:
required: true

View File

@@ -1,3 +1,12 @@
defaults:
type: file
thumb: media/thumb.png
mime: application/octet-stream
image:
filters:
default:
- enableProgressive
jpg:
type: image
thumb: media/thumb-jpg.png
@@ -31,6 +40,31 @@ swf:
type: video
thumb: media/thumb-swf.png
mime: video/x-flv
flv:
type: video
thumb: media/thumb-flv.png
mime: video/x-flv
mp3:
type: audio
thumb: media/thumb-mp3.png
mime: audio/mp3
ogg:
type: audio
thumb: media/thumb-ogg.png
mime: audio/ogg
wma:
type: audio
thumb: media/thumb-wma.png
mime: audio/wma
m4a:
type: audio
thumb: media/thumb-m4a.png
mime: audio/m4a
wav:
type: audio
thumb: media/thumb-wav.png
mime: audio/wav
txt:
type: file

View File

@@ -1,13 +1,15 @@
title: Grav # Name of the site
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
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
description: 'Grav Site Description' # Site description
route: '/blog' # Route to blog
metadata:
description: 'My Grav Site' # Site description
summary:
size: 300 # Maximum length of summary (characters)
size: 300 # Maximum length of summary (characters)
routes:
/something/else: '/blog/sample-3' # Alias for /blog/sample-3
/another/one/here: '/blog/sample-3' # Another alias for /blog/sample-3
/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

View File

@@ -0,0 +1,20 @@
schemes:
asset:
type: ReadOnlyStream
paths:
- assets
image:
type: ReadOnlyStream
paths:
- user://images
page:
type: ReadOnlyStream
paths:
- user://pages
account:
type: ReadOnlyStream
paths:
- user://accounts

View File

@@ -1,3 +1,5 @@
absolute_urls: false # Absolute or relative URLs for `base_url`
home:
alias: '/home' # Default path for home, ie /
@@ -11,29 +13,56 @@ pages:
dateformat:
short: 'jS M Y' # Short date format
long: 'F jS \a\t g:ia' # Long date format
publish_dates: true # automatically publish/unpublish based on dates
process:
markdown: true # Process Markdown
twig: false # Process Twig
events:
page: false # Enable page level events
page: true # Enable page level events
twig: true # Enable twig level events
markdown:
extra: false # Enable support for Markdown Extra support (GFM by default)
auto_line_breaks: false # Enable automatic line breaks
auto_url_links: false # Enable automatic HTML links
escape_markup: false # Escape markup tags into entities
special_chars: # List of special characters to automatically convert to entities
'>': 'gt'
'<': 'lt'
cache:
enabled: true # Set to true to enable caching
check:
pages: true # Check to see if page has been modifying to flush the cache
driver: auto # One of: auto|file|apc|xcache|memcache|memcached|wincache
method: file # Method to check for updates in pages: file|folder|none
driver: auto # One of: auto|file|apc|xcache|memcache|wincache
prefix: 'g' # Cache prefix string (prevents cache conflicts)
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
gzip: false # GZip compress the page output
twig:
cache: false # Set to true to enable twig caching
debug: true # Enable Twig debug
auto_reload: true # Refresh cache on changes
autoescape: false # Autoescape Twig vars
cache: true # Set to true to enable twig caching
debug: false # Enable Twig debug
auto_reload: true # Refresh cache on changes
autoescape: false # Autoescape Twig vars
undefined_functions: true # Allow undefined functions
undefined_filters: true # Allow undefined filters
assets: # Configuration for Assets Manager (JS, CSS)
css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file
css_minify: true # Minify the CSS during pipelining
css_minify_windows: false # Minify Override for Windows platforms. False by default due to ThreadStackSize
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_minify: true # Minify the JS during pipelining
errors:
display: true # Display full backtrace-style error page
log: true # Log errors to /logs folder
debugger:
enabled: true # Enable Grav debugger
max_depth: 10 # How many nested levels to display for objects or arrays
log:
enabled: true # Enable logging
timing: false # Enable timing logging
enabled: false # Enable Grav debugger and following settings
twig: true # Enable debugging of Twig templates
shutdown:
close_connection: true # Close the connection before calling onShutdown(). false for debugging
images:
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example

View File

@@ -2,28 +2,31 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '0.8.0');
define('GRAV_VERSION', '0.9.16');
define('DS', '/');
// Directories and Paths
if (!defined('ROOT_DIR')) {
define('ROOT_DIR', getcwd() .'/');
if (!defined('GRAV_ROOT')) {
define('GRAV_ROOT', getcwd());
}
define('ROOT_DIR', GRAV_ROOT . '/');
define('USER_PATH', 'user/');
define('USER_DIR', ROOT_DIR . USER_PATH);
define('SYSTEM_DIR', ROOT_DIR .'system/');
define('CACHE_DIR', ROOT_DIR .'cache/');
define('ASSETS_DIR', ROOT_DIR . 'assets/');
define('CACHE_DIR', ROOT_DIR . 'cache/');
define('IMAGES_DIR', ROOT_DIR . 'images/');
define('LOG_DIR', ROOT_DIR .'logs/');
define('VENDOR_DIR', ROOT_DIR .'vendor/');
define('LIB_DIR', SYSTEM_DIR .'src/');
define('ACCOUNTS_DIR', USER_DIR .'accounts/');
define('DATA_DIR', USER_DIR .'data/');
define('PAGES_DIR', USER_DIR .'pages/');
define('BLOCKS_DIR', USER_DIR .'blocks/');
// DEPRECATED: Do not use!
define('DATA_DIR', USER_DIR .'data/');
define('LIB_DIR', SYSTEM_DIR .'src/');
define('PLUGINS_DIR', USER_DIR .'plugins/');
define('THEMES_DIR', USER_DIR .'themes/');
define('VENDOR_DIR', ROOT_DIR .'vendor/');
// END DEPRECATED
// Some extensions
define('CONTENT_EXT', '.md');

View File

@@ -0,0 +1,904 @@
<?php
namespace Grav\Common;
use Closure;
use Exception;
use FilesystemIterator;
use Grav\Common\Config\Config;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RegexIterator;
define('CSS_ASSET', true);
define('JS_ASSET', false);
/**
* Handles Asset management (CSS & JS) and also pipelining (combining into a single file for each asset)
*
* Based on stolz/assets (https://github.com/Stolz/Assets) package modified for use with Grav
*
* @author RocketTheme
* @license MIT
*/
class Assets
{
use GravTrait;
/** @const Regex to match CSS and JavaScript files */
const DEFAULT_REGEX = '/.\.(css|js)$/i';
/** @const Regex to match CSS files */
const CSS_REGEX = '/.\.css$/i';
/** @const Regex to match JavaScript files */
const JS_REGEX = '/.\.js$/i';
/** @const Regex to match CSS urls */
const CSS_URL_REGEX = '{url\([\'\"]?((?!http|//).*?)[\'\"]?\)}';
/** @const Regex to match CSS sourcemap comments */
const CSS_SOURCEMAP_REGEX = '{\/\*# (.*) \*\/}';
/** @const Regex to match CSS import content */
const CSS_IMPORT_REGEX = '{@import(.*);}';
/**
* 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
* your assets before they get pipelined.
*
* The closure will receive as the only parameter a string with the path/URL of the asset and
* it should return the content of the asset file as a string.
*
* @var Closure
*/
protected $fetch_command;
// Configuration toggles to enable/disable the pipelining feature
protected $css_pipeline = false;
protected $js_pipeline = false;
// The asset holding arrays
protected $collections = array();
protected $css = array();
protected $js = array();
protected $inline_css = array();
protected $inline_js = array();
// Some configuration variables
protected $config;
protected $base_url;
// Default values for pipeline settings
protected $css_minify = true;
protected $css_minify_windows = false;
protected $css_rewrite = true;
protected $js_minify = true;
// Arrays to hold assets that should NOT be pipelined
protected $css_no_pipeline = array();
protected $js_no_pipeline = array();
public function __construct(array $options = array())
{
// Forward config options
if ($options) {
$this->config((array)$options);
}
}
/**
* Set up configuration options.
*
* All the class properties except 'js' and 'css' are accepted here.
* Also, an extra option 'autoload' may be passed containing an array of
* assets and/or collections that will be automatically added on startup.
*
* @param array $config Configurable options.
*
* @return $this
* @throws \Exception
*/
public function config(array $config)
{
// Set pipeline modes
if (isset($config['css_pipeline'])) {
$this->css_pipeline = $config['css_pipeline'];
}
if (isset($config['js_pipeline'])) {
$this->js_pipeline = $config['js_pipeline'];
}
// Pipeline requires public dir
if (($this->js_pipeline || $this->css_pipeline) && !is_dir(ASSETS_DIR)) {
throw new \Exception('Assets: Public dir not found');
}
// Set custom pipeline fetch command
if (isset($config['fetch_command']) and ($config['fetch_command'] instanceof Closure)) {
$this->fetch_command = $config['fetch_command'];
}
// Set CSS Minify state
if (isset($config['css_minify'])) {
$this->css_minify = $config['css_minify'];
}
if (isset($config['css_minify_windows'])) {
$this->css_minify_windows = $config['css_minify_windows'];
}
if (isset($config['css_rewrite'])) {
$this->css_rewrite = $config['css_rewrite'];
}
// Set JS Minify state
if (isset($config['js_minify'])) {
$this->js_minify = $config['js_minify'];
}
// Set collections
if (isset($config['collections']) and is_array($config['collections'])) {
$this->collections = $config['collections'];
}
// Autoload assets
if (isset($config['autoload']) and is_array($config['autoload'])) {
foreach ($config['autoload'] as $asset) {
$this->add($asset);
}
}
return $this;
}
/**
* Initialization called in the Grav lifecycle to initialize the Assets with appropriate configuration
*/
public function init()
{
/** @var Config $config */
$config = self::$grav['config'];
$base_url = self::$grav['base_url'];
$asset_config = (array)$config->get('system.assets');
$this->config($asset_config);
$this->base_url = $base_url . '/';
}
/**
* Add an asset or a collection of assets.
*
* It automatically detects the asset type (JavaScript, CSS or collection).
* You may add more than one asset passing an array as argument.
*
* @param mixed $asset
* @param int $priority the priority, bigger comes first
* @param bool $pipeline false if this should not be pipelined
*
* @return $this
*/
public function add($asset, $priority = 10, $pipeline = true)
{
// More than one asset
if (is_array($asset)) {
foreach ($asset as $a) {
$this->add($a, $priority, $pipeline);
}
} elseif (isset($this->collections[$asset])) {
$this->add($this->collections[$asset], $priority, $pipeline);
} else {
// Get extension
$extension = pathinfo(parse_url($asset, PHP_URL_PATH), PATHINFO_EXTENSION);
// JavaScript or CSS
if (strlen($extension) > 0) {
$extension = strtolower($extension);
if ($extension === 'css') {
$this->addCss($asset, $priority, $pipeline);
} elseif ($extension === 'js') {
$this->addJs($asset, $priority, $pipeline);
}
}
}
return $this;
}
/**
* Add a CSS asset.
*
* It checks for duplicates.
* You may add more than one asset passing an array as argument.
*
* @param mixed $asset
* @param int $priority the priority, bigger comes first
* @param bool $pipeline false if this should not be pipelined
*
* @return $this
*/
public function addCss($asset, $priority = 10, $pipeline = true)
{
if (is_array($asset)) {
foreach ($asset as $a) {
$this->addCss($a, $priority, $pipeline);
}
return $this;
}
if (!$this->isRemoteLink($asset)) {
$asset = $this->buildLocalLink($asset);
}
$key = md5($asset);
if ($asset && !array_key_exists($key, $this->css)) {
$this->css[$key] = [
'asset' => $asset,
'priority' => $priority,
'order' => count($this->css),
'pipeline' => $pipeline
];
}
return $this;
}
/**
* Add a JavaScript asset.
*
* It checks for duplicates.
* You may add more than one asset passing an array as argument.
*
* @param mixed $asset
* @param int $priority the priority, bigger comes first
* @param bool $pipeline false if this should not be pipelined
*
* @return $this
*/
public function addJs($asset, $priority = 10, $pipeline = true)
{
if (is_array($asset)) {
foreach ($asset as $a) {
$this->addJs($a, $priority, $pipeline);
}
return $this;
}
if (!$this->isRemoteLink($asset)) {
$asset = $this->buildLocalLink($asset);
}
$key = md5($asset);
if ($asset && !array_key_exists($key, $this->js)) {
$this->js[$key] = [
'asset' => $asset,
'priority' => $priority,
'order' => count($this->js),
'pipeline' => $pipeline
];
}
return $this;
}
/**
* Add an inline CSS asset.
*
* It checks for duplicates.
* For adding chunks of string-based inline CSS
*
* @param mixed $asset
* @param int $priority the priority, bigger comes first
*
* @return $this
*/
public function addInlineCss($asset, $priority = 10)
{
$key = md5($asset);
if (is_string($asset) && !array_key_exists($key, $this->inline_css)) {
$this->inline_css[$key] = [
'priority' => $priority,
'order' => count($this->inline_css),
'asset' => $asset
];
}
return $this;
}
/**
* Add an inline JS asset.
*
* It checks for duplicates.
* For adding chunks of string-based inline JS
*
* @param mixed $asset
* @param int $priority the priority, bigger comes first
*
* @return $this
*/
public function addInlineJs($asset, $priority = 10)
{
$key = md5($asset);
if (is_string($asset) && !array_key_exists($key, $this->inline_js)) {
$this->inline_js[$key] = [
'priority' => $priority,
'order' => count($this->inline_js),
'asset' => $asset
];
}
return $this;
}
/**
* Build the CSS link tags.
*
* @param array $attributes
*
* @return string
*/
public function css($attributes = [])
{
if (!$this->css) {
return null;
}
// Sort array by priorities (larger priority first)
if (self::$grav) {
usort($this->css, function ($a, $b) {
if ($a['priority'] == $b['priority']) {
return $b['order'] - $a['order'];
}
return $a['priority'] - $b['priority'];
});
usort($this->inline_css, function ($a, $b) {
if ($a['priority'] == $b['priority']) {
return $b['order'] - $a['order'];
}
return $a['priority'] - $b['priority'];
});
}
$this->css = array_reverse($this->css);
$this->inline_css = array_reverse($this->inline_css);
$attributes = $this->attributes(array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes));
$output = '';
if ($this->css_pipeline) {
$output .= '<link href="' . $this->pipeline(CSS_ASSET) . '"' . $attributes . ' />' . "\n";
foreach ($this->css_no_pipeline as $file) {
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
}
} else {
foreach ($this->css as $file) {
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
}
}
// Render Inline CSS
if (count($this->inline_css) > 0) {
$output .= "<style>\n";
foreach ($this->inline_css as $inline) {
$output .= $inline['asset'] . "\n";
}
$output .= "</style>\n";
}
return $output;
}
/**
* Build the JavaScript script tags.
*
* @param array $attributes
*
* @return string
*/
public function js($attributes = [])
{
if (!$this->js) {
return null;
}
// Sort array by priorities (larger priority first)
usort($this->js, function ($a, $b) {
if ($a['priority'] == $b['priority']) {
return $b['order'] - $a['order'];
}
return $a['priority'] - $b['priority'];
});
usort($this->inline_js, function ($a, $b) {
if ($a['priority'] == $b['priority']) {
return $b['order'] - $a['order'];
}
return $a['priority'] - $b['priority'];
});
$this->js = array_reverse($this->js);
$this->inline_js = array_reverse($this->inline_js);
$attributes = $this->attributes(array_merge(['type' => 'text/javascript'], $attributes));
$output = '';
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 . ' ></script>' . "\n";
}
} else {
foreach ($this->js as $file) {
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ></script>' . "\n";
}
}
// Render Inline JS
if (count($this->inline_js) > 0) {
$output .= "<script>\n";
foreach ($this->inline_js as $inline) {
$output .= $inline['asset'] . "\n";
}
$output .= "</script>\n";
}
return $output;
}
/**
* Minifiy and concatenate CSS / JS files.
*
* @return string
*/
protected function pipeline($css = true)
{
/** @var Cache $cache */
$cache = self::$grav['cache'];
$key = '?' . $cache->getKey();
if ($css) {
$file = md5(json_encode($this->css) . $this->js_minify . $this->css_minify . $this->css_rewrite) . '.css';
foreach ($this->css as $id => $asset) {
if (!$asset['pipeline']) {
$this->css_no_pipeline[] = $asset;
unset($this->css[$id]);
}
}
} else {
$file = md5(json_encode($this->js) . $this->js_minify . $this->css_minify . $this->css_rewrite) . '.js';
foreach ($this->js as $id => $asset) {
if (!$asset['pipeline']) {
$this->js_no_pipeline[] = $asset;
unset($this->js[$id]);
}
}
}
$relative_path = "{$this->base_url}" . basename(ASSETS_DIR) . "/{$file}";
$absolute_path = ASSETS_DIR . $file;
// If pipeline exist return it
if (file_exists($absolute_path)) {
return $relative_path . $key;
}
$css_minify = $this->css_minify;
// If this is a Windows server, and minify_windows is false (default value) skip the
// minification process because it will cause Apache to die/crash due to insufficient
// ThreadStackSize in httpd.conf - See: https://bugs.php.net/bug.php?id=47689
if (strtoupper(substr(php_uname('s'), 0, 3)) === 'WIN' && !$this->css_minify_windows) {
$css_minify = false;
}
// Concatenate files
if ($css) {
$buffer = $this->gatherLinks($this->css, CSS_ASSET);
if ($css_minify) {
$min = new \CSSmin();
$buffer = $min->run($buffer);
}
} else {
$buffer = $this->gatherLinks($this->js, JS_ASSET);
if ($this->js_minify) {
$buffer = \JSMin::minify($buffer);
}
}
// Write file
file_put_contents($absolute_path, $buffer);
return $relative_path . $key;
}
/**
* Add/replace collection.
*
* @param string $collectionName
* @param array $assets
*
* @return $this
*/
public function registerCollection($collectionName, Array $assets)
{
$this->collections[$collectionName] = $assets;
return $this;
}
/**
* Reset all assets.
*
* @return $this
*/
public function reset()
{
return $this->resetCss()->resetJs();
}
/**
* Reset JavaScript assets.
*
* @return $this
*/
public function resetJs()
{
$this->js = array();
return $this;
}
/**
* Reset CSS assets.
*
* @return $this
*/
public function resetCss()
{
$this->css = array();
return $this;
}
/**
* Get all CSS assets already added.
*
* @return array
*/
public function getCss()
{
return $this->css;
}
/**
* Get all JavaScript assets already added.
*
* @return array
*/
public function getJs()
{
return $this->js;
}
/**
* Add all CSS assets within $directory (relative to public dir).
*
* @param string $directory Relative to $this->public_dir
*
* @return $this
*/
public function addDirCss($directory)
{
return $this->addDir($directory, self::CSS_REGEX);
}
/**
* Add all assets matching $pattern within $directory.
*
* @param string $directory Relative to $this->public_dir
* @param string $pattern (regex)
*
* @return $this
* @throws Exception
*/
public function addDir($directory, $pattern = self::DEFAULT_REGEX)
{
// Check if public_dir exists
if (!is_dir(ASSETS_DIR)) {
throw new Exception('Assets: Public dir not found');
}
// Get files
$files = $this->rglob(ASSETS_DIR . DIRECTORY_SEPARATOR . $directory, $pattern, ASSETS_DIR);
// No luck? Nothing to do
if (!$files) {
return $this;
}
// Add CSS files
if ($pattern === self::CSS_REGEX) {
$this->css = array_unique(array_merge($this->css, $files));
return $this;
}
// Add JavaScript files
if ($pattern === self::JS_REGEX) {
$this->js = array_unique(array_merge($this->js, $files));
return $this;
}
// Unknown pattern. We must poll to know the extension :(
foreach ($files as $asset) {
$info = pathinfo($asset);
if (isset($info['extension'])) {
$ext = strtolower($info['extension']);
if ($ext === 'css' and !in_array($asset, $this->css)) {
$this->css[] = $asset;
} elseif ($ext === 'js' and !in_array($asset, $this->js)) {
$this->js[] = $asset;
}
}
}
return $this;
}
/**
* Determine whether a link is local or remote.
*
* Undestands both "http://" and "https://" as well as protocol agnostic links "//"
*
* @param string $link
*
* @return bool
*/
protected function isRemoteLink($link)
{
return ('http://' === substr($link, 0, 7) or 'https://' === substr($link, 0, 8)
or '//' === substr($link, 0, 2));
}
/**
* Build local links including grav asset shortcodes
*
* @param string $asset the asset string reference
*
* @return string the final link url to the asset
*/
protected function buildLocalLink($asset)
{
try {
$asset = self::$grav['locator']->findResource($asset, false);
} catch (\Exception $e) {
}
return $asset ? $this->base_url . ltrim($asset, '/') : false;
}
/**
* Build an HTML attribute string from an array.
*
* @param array $attributes
*
* @return string
*/
protected function attributes(array $attributes)
{
$html = '';
foreach ($attributes as $key => $value) {
// For numeric keys we will assume that the key and the value are the same
// as this will convert HTML attributes such as "required" to a correct
// form like required="required" instead of using incorrect numerics.
if (is_numeric($key)) {
$key = $value;
}
if (is_array($value)) {
$value = implode(' ', $value);
}
$element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
$html .= ' ' . $element;
}
return $html;
}
/**
* Download and concatenate the content of several links.
*
* @param array $links
*
* @return string
*/
protected function gatherLinks(array $links, $css = true)
{
$buffer = '';
$local = true;
foreach ($links as $asset) {
$link = $asset['asset'];
$relative_path = $link;
if ($this->isRemoteLink($link)) {
$local = false;
if ('//' === substr($link, 0, 2)) {
$link = 'http:' . $link;
}
} 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);
}
$relative_dir = dirname($relative_path);
$link = ROOT_DIR . $relative_path;
}
$file = ($this->fetch_command instanceof Closure) ? $this->fetch_command->__invoke($link) : file_get_contents($link);
// Double check last character being
if (!$css) {
$file = rtrim($file, ' ;') . ';';
}
// If this is CSS + the file is local + rewrite enabled
if ($css && $local && $this->css_rewrite) {
$file = $this->cssRewrite($file, $relative_dir);
}
$buffer .= $file;
}
// Pull out @imports and move to top
if ($css) {
$buffer = $this->moveImports($buffer);
}
return $buffer;
}
/**
* Finds relative CSS urls() and rewrites the URL with an absolute one
*
* @param $file the css source file
* @param $relative_path relative path to the css file
*
* @return mixed
*/
protected function cssRewrite($file, $relative_path)
{
// Strip any sourcemap comments
$file = preg_replace(self::CSS_SOURCEMAP_REGEX, '', $file);
// Find any css url() elements, grab the URLs and calculate an absolute path
// Then replace the old url with the new one
$file = preg_replace_callback(
self::CSS_URL_REGEX,
function ($matches) use ($relative_path) {
$old_url = $matches[1];
// ensure this is not a data url
if (strpos($old_url, 'data:') === 0) {
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);
return str_replace($old_url, $new_url, $matches[0]);
},
$file
);
return $file;
}
/**
* Moves @import statements to the top of the file per the CSS specification
*
* @param string $file the file containing the combined CSS files
*
* @return string the modified file with any @imports at the top of the file
*/
protected function moveImports($file)
{
$this->imports = array();
$file = preg_replace_callback(
self::CSS_IMPORT_REGEX,
function ($matches) {
$this->imports[] = $matches[0];
return '';
},
$file
);
return implode("\n", $this->imports) . "\n\n" . $file;
}
/**
* Recursively get files matching $pattern within $directory.
*
* @param string $directory
* @param string $pattern (regex)
* @param string $ltrim Will be trimed from the left of the file path
*
* @return array
*/
protected function rglob($directory, $pattern, $ltrim = null)
{
$iterator = new RegexIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$directory,
FilesystemIterator::SKIP_DOTS
)
),
$pattern
);
$offset = strlen($ltrim);
$files = array();
foreach ($iterator as $file) {
$files[] = substr($file->getPathname(), $offset);
}
return $files;
}
/**
* Add all JavaScript assets within $directory.
*
* @param string $directory Relative to $this->public_dir
*
* @return $this
*/
public function addDirJs($directory)
{
return $this->addDir($directory, self::JS_REGEX);
}
public function __toString()
{
return '';
}
/**
* @param $a
* @param $b
*
* @return mixed
*/
protected function priorityCompare($a, $b)
{
return $a ['priority'] - $b ['priority'];
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Grav\Common;
/**
* Simple wrapper for the very simple parse_user_agent() function
*/
class Browser
{
protected $useragent = [];
public function __construct()
{
try {
$this->useragent = parse_user_agent();
} catch (\InvalidArgumentException $e) {
$this->useragent = parse_user_agent("Mozilla/5.0 (compatible; Unknown;)");
}
}
public function getBrowser()
{
return strtolower($this->useragent['browser']);
}
public function getPlatform()
{
return strtolower($this->useragent['platform']);
}
public function getLongVersion()
{
return $this->useragent['version'];
}
public function getVersion()
{
$version = explode('.', $this->getLongVersion());
return intval($version[0]);
}
}

View File

@@ -1,6 +1,10 @@
<?php
namespace Grav\Common;
use \Doctrine\Common\Cache\Cache as DoctrineCache;
use Grav\Common\Config\Config;
use Grav\Common\Filesystem\Folder;
/**
* The GravCache object is used throughout Grav to store and retrieve cached data.
* It uses DoctrineCache library and supports a variety of caching mechanisms. Those include:
@@ -22,8 +26,13 @@ class Cache extends Getters
*/
protected $key;
protected $lifetime;
protected $now;
protected $config;
/**
* @var \Doctrine\Common\Cache\Cache
* @var DoctrineCache
*/
protected $driver;
@@ -32,25 +41,100 @@ class Cache extends Getters
*/
protected $enabled;
protected $cache_dir;
protected static $standard_remove = [
'cache/twig/',
'cache/doctrine/',
'cache/compiled/',
'cache/validated-',
'images/',
'assets/',
];
protected static $all_remove = [
'cache/',
'images/',
'assets/'
];
protected static $assets_remove = [
'assets/'
];
protected static $images_remove = [
'images/'
];
protected static $cache_remove = [
'cache/'
];
/**
* Constructor
*
* @params Grav $grav
*/
public function __construct(Grav $grav)
{
$this->init($grav);
}
/**
* Initialization that sets a base key and the driver based on configuration settings
*
* @param Grav $grav
* @return void
*/
public function init()
public function init(Grav $grav)
{
/** @var Config $config */
$config = Registry::get('Config');
$prefix = $config->get('system.cache.prefix');
/** @var Uri $uri */
$uri = Registry::get('Uri');
$this->config = $grav['config'];
$this->now = time();
$this->enabled = (bool) $config->get('system.cache.enabled');
$this->cache_dir = $grav['locator']->findResource('cache://doctrine', true, true);
/** @var Uri $uri */
$uri = $grav['uri'];
$prefix = $this->config->get('system.cache.prefix');
$this->enabled = (bool) $this->config->get('system.cache.enabled');
// Cache key allows us to invalidate all cache on configuration changes.
$this->key = substr(md5(($prefix ? $prefix : 'g') . $uri->rootUrl(true) . $config->key . GRAV_VERSION), 2, 8);
$this->key = substr(md5(($prefix ? $prefix : 'g') . $uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8);
switch ($this->getCacheDriverName($config->get('system.cache.driver'))) {
$this->driver = $this->getCacheDriver();
// Set the cache namespace to our unique key
$this->driver->setNamespace($this->key);
}
/**
* Automatically picks the cache mechanism to use. If you pick one manually it will use that
* If there is no config option for $driver in the config, or it's set to 'auto', it will
* pick the best option based on which cache extensions are installed.
*
* @return DoctrineCacheDriver The cache driver to use
*/
public function getCacheDriver()
{
$setting = $this->config->get('system.cache.driver');
$driver_name = 'file';
if (!$setting || $setting == 'auto') {
if (extension_loaded('apc')) {
$driver_name = 'apc';
} elseif (extension_loaded('wincache')) {
$driver_name = 'wincache';
} elseif (extension_loaded('xcache')) {
$driver_name = 'xcache';
}
} else {
$driver_name = $setting;
}
switch ($driver_name) {
case 'apc':
$driver = new \Doctrine\Common\Cache\ApcCache();
break;
@@ -64,44 +148,19 @@ class Cache extends Getters
break;
case 'memcache':
$memcache = new \Memcache();
$memcache->connect($this->config->get('system.cache.memcache.server','localhost'),
$this->config->get('system.cache.memcache.port', 11211));
$driver = new \Doctrine\Common\Cache\MemcacheCache();
break;
case 'memcached':
$driver = new \Doctrine\Common\Cache\MemcachedCache();
$driver->setMemcache($memcache);
break;
default:
$driver = new \Doctrine\Common\Cache\FilesystemCache(CACHE_DIR);
$driver = new \Doctrine\Common\Cache\FilesystemCache($this->cache_dir);
break;
}
$this->driver = $driver;
}
/**
* Automatically picks the cache mechanism to use. If you pick one manually it will use that
* If there is no config option for $driver in the config, or it's set to 'auto', it will
* pick the best option based on which cache extensions are installed.
*
* @param string $setting
* @return string The name of the best cache driver to use
*/
protected function getCacheDriverName($setting = null)
{
if (!$setting || $setting == 'auto') {
if (extension_loaded('apc') && ini_get('apc.enabled')) {
return 'apc';
} elseif (extension_loaded('wincache')) {
return 'wincache';
} elseif (extension_loaded('xcache') && ini_get('xcache.size') && ini_get('xcache.cacher')) {
return 'xcache';
} else {
return 'file';
}
} else {
return $setting;
}
return $driver;
}
/**
@@ -113,7 +172,6 @@ class Cache extends Getters
public function fetch($id)
{
if ($this->enabled) {
$id = $this->key . $id;
return $this->driver->fetch($id);
} else {
return false;
@@ -130,7 +188,10 @@ class Cache extends Getters
public function save($id, $data, $lifetime = null)
{
if ($this->enabled) {
$id = $this->key . $id;
if ($lifetime == null) {
$lifetime = $this->getLifetime();
}
$this->driver->save($id, $data, $lifetime);
}
}
@@ -142,4 +203,103 @@ class Cache extends Getters
{
return $this->key;
}
/**
* Helper method to clear all Grav caches
*
* @param string $remove standard|all|assets-only|images-only|cache-only
*
* @return array
*/
public static function clearCache($remove = 'standard')
{
$output = [];
$user_config = USER_DIR . 'config/system.yaml';
switch($remove) {
case 'all':
$remove_paths = self::$all_remove;
break;
case 'assets-only':
$remove_paths = self::$assets_remove;
break;
case 'images-only':
$remove_paths = self::$images_remove;
break;
case 'cache-only':
$remove_paths = self::$cache_remove;
break;
default:
$remove_paths = self::$standard_remove;
}
foreach ($remove_paths as $path) {
$anything = false;
$files = glob(ROOT_DIR . $path . '*');
foreach ($files as $file) {
if (is_file($file)) {
if (@unlink($file)) {
$anything = true;
}
} elseif (is_dir($file)) {
if (@Folder::delete($file)) {
$anything = true;
}
}
}
if ($anything) {
$output[] = '<red>Cleared: </red>' . $path . '*';
}
}
$output[] = '';
if (($remove == 'all' || $remove == 'standard') && file_exists($user_config)) {
touch($user_config);
$output[] = '<red>Touched: </red>' . $user_config;
$output[] = '';
}
return $output;
}
/**
* Set the cache lifetime programatically
*
* @param int $future timestamp
*/
public function setLifetime($future)
{
if (!$future) {
return;
}
$interval = $future - $this->now;
if ($interval > 0 && $interval < $this->getLifetime()) {
$this->lifetime = $interval;
}
}
/**
* Retrieve the cache lifetime (in seconds)
*
* @return mixed
*/
public function getLifetime()
{
if ($this->lifetime === null) {
$this->lifetime = $this->config->get('system.cache.lifetime') ?: 604800; // 1 week default
}
return $this->lifetime;
}
}

View File

@@ -1,258 +0,0 @@
<?php
namespace Grav\Common;
use Grav\Common\Data\Blueprints;
use Grav\Common\Data\Data;
use Grav\Common\Filesystem\File;
use Grav\Common\Filesystem\Folder;
/**
* The Config class contains configuration information.
*
* @author RocketTheme
* @license MIT
*/
class Config extends Data
{
/**
* @var string Configuration location in the disk.
*/
public $filename;
/**
* @var string MD5 from the files.
*/
public $key;
/**
* @var array Configuration file list.
*/
public $files = array();
/**
* @var bool Flag to tell if configuration needs to be saved.
*/
public $updated = false;
public $issues = array();
/**
* Constructor.
*/
public function __construct($filename)
{
$this->filename = realpath(dirname($filename)) . '/' . basename($filename);
$this->reload(false);
}
/**
* Force reload of the configuration from the disk.
*
* @param bool $force
* @return $this
*/
public function reload($force = true)
{
// Build file map.
$files = $this->build();
$key = md5(serialize($files) . GRAV_VERSION);
if ($force || $key != $this->key) {
// First take non-blocking lock to the file.
File\Config::instance($this->filename)->lock(false);
// Reset configuration.
$this->items = array();
$this->files = array();
$this->init($files);
$this->key = $key;
}
return $this;
}
/**
* Save configuration into file.
*
* Note: Only saves the file if updated flag is set!
*
* @return $this
* @throws \RuntimeException
*/
public function save()
{
// If configuration was updated, store it as cached version.
try {
$file = File\Config::instance($this->filename);
// Only save configuration file if it wasn't locked. Also invalidate opcache after saving.
// This prevents us from saving the file multiple times in a row and gives faster recovery.
if ($file->locked() !== false) {
$file->save($this);
$file->unlock();
}
$this->updated = false;
} catch (\Exception $e) {
$this->issues[] = 'Writing configuration into cache failed.';
//throw new \RuntimeException('Writing configuration into cache failed.', 500, $e);
}
return $this;
}
/**
* Gets configuration instance.
*
* @param string $filename
* @return \Grav\Common\Config
*/
public static function instance($filename)
{
// Load cached version if available..
if (file_exists($filename)) {
clearstatcache(true, $filename);
require_once $filename;
if (class_exists('\Grav\Config')) {
$instance = new \Grav\Config($filename);
}
}
// Or initialize new configuration object..
if (!isset($instance)) {
$instance = new static($filename);
}
// If configuration was updated, store it as cached version.
if ($instance->updated) {
$instance->save();
}
// If not set, add manually current base url.
if (empty($instance->items['system']['base_url_absolute'])) {
$instance->items['system']['base_url_absolute'] = Registry::get('Uri')->rootUrl(true);
}
if (empty($instance->items['system']['base_url_relative'])) {
$instance->items['system']['base_url_relative'] = Registry::get('Uri')->rootUrl(false);
}
return $instance;
}
/**
* Convert configuration into an array.
*
* @return array
*/
public function toArray()
{
return array('key' => $this->key, 'files' => $this->files, 'items' => $this->items);
}
/**
* Initialize object by loading all the configuration files.
*
* @param array $files
*/
protected function init(array $files)
{
$this->updated = true;
// Combine all configuration files into one larger lookup table (only keys matter).
$allFiles = $files['user'] + $files['plugins'] + $files['system'];
// Then sort the files to have all parent nodes first.
// This is to make sure that child nodes override parents content.
uksort(
$allFiles,
function($a, $b) {
$diff = substr_count($a, '/') - substr_count($b, '/');
return $diff ? $diff : strcmp($a, $b);
}
);
$systemBlueprints = new Blueprints(SYSTEM_DIR . 'blueprints');
$pluginBlueprints = new Blueprints(USER_DIR);
$items = array();
foreach ($allFiles as $name => $dummy) {
$lookup = array(
'system' => SYSTEM_DIR . 'config/' . $name . YAML_EXT,
'plugins' => USER_DIR . $name . '/' . basename($name) . YAML_EXT,
'user' => USER_DIR . 'config/' . $name . YAML_EXT,
);
if (strpos($name, 'plugins/') === 0) {
$blueprint = $pluginBlueprints->get("{$name}/blueprints");
} else {
$blueprint = $systemBlueprints->get($name);
}
$data = new Data(array(), $blueprint);
foreach ($lookup as $key => $path) {
if (is_file($path)) {
$data->merge(File\Yaml::instance($path)->content());
}
}
// $data->validate();
// $data->filter();
// Find the current sub-tree location.
$current = &$items;
$parts = explode('/', $name);
foreach ($parts as $part) {
if (!isset($current[$part])) {
$current[$part] = array();
}
$current = &$current[$part];
}
// Handle both updated and deleted configuration files.
$current = $data->toArray();
}
$this->items = $items;
$this->files = $files;
}
/**
* Build a list of configuration files with their timestamps. Used for loading settings and caching them.
*
* @return array
* @internal
*/
protected function build()
{
// Find all plugins with default configuration options.
$plugins = array();
$iterator = new \DirectoryIterator(PLUGINS_DIR);
/** @var \DirectoryIterator $plugin */
foreach ($iterator as $plugin) {
$name = $plugin->getBasename();
$file = $plugin->getPathname() . DS . $name . YAML_EXT;
if (!is_file($file)) {
continue;
}
$modified = filemtime($file);
$plugins["plugins/{$name}"] = $modified;
}
// Find all system and user configuration files.
$options = array(
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => array('key' => '|\.yaml$|'),
'key' => 'SubPathname',
'value' => 'MTime'
);
$system = Folder::all(SYSTEM_DIR . 'config', $options);
$user = Folder::all(USER_DIR . 'config', $options);
return array('system' => $system, 'plugins' => $plugins, 'user' => $user);
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Common\Filesystem\Folder;
use RocketTheme\Toolbox\Blueprints\Blueprints as BaseBlueprints;
use RocketTheme\Toolbox\File\PhpFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* The Blueprints class contains configuration rules.
*
* @author RocketTheme
* @license MIT
*/
class Blueprints extends BaseBlueprints
{
protected $grav;
protected $files = [];
protected $blueprints;
public function __construct(array $serialized = null, Grav $grav = null)
{
parent::__construct($serialized);
$this->grav = $grav ?: Grav::instance();
}
public function init()
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$blueprints = $locator->findResources('blueprints://config');
$plugins = $locator->findResources('plugins://');
$blueprintFiles = $this->getBlueprintFiles($blueprints, $plugins);
$this->loadCompiledBlueprints($plugins + $blueprints, $blueprintFiles);
}
protected function loadCompiledBlueprints($blueprints, $blueprintFiles)
{
$checksum = md5(serialize($blueprints));
$filename = CACHE_DIR . 'compiled/blueprints/' . $checksum .'.php';
$checksum .= ':'.md5(serialize($blueprintFiles));
$class = get_class($this);
$file = PhpFile::instance($filename);
if ($file->exists()) {
$cache = $file->exists() ? $file->content() : null;
} else {
$cache = null;
}
// Load real file if cache isn't up to date (or is invalid).
if (
!is_array($cache)
|| empty($cache['checksum'])
|| empty($cache['$class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Load blueprints.
$this->blueprints = new Blueprints();
foreach ($blueprintFiles as $key => $files) {
$this->loadBlueprints($key);
}
$cache = [
'@class' => $class,
'checksum' => $checksum,
'files' => $blueprintFiles,
'data' => $this->blueprints->toArray()
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$file->save($cache);
$file->unlock();
}
} else {
$this->blueprints = new Blueprints($cache['data']);
}
}
/**
* Load global blueprints.
*
* @param string $key
* @param array $files
*/
public function loadBlueprints($key, array $files = null)
{
if (is_null($files)) {
$files = $this->files[$key];
}
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->blueprints->embed($name, $file->content(), '/');
}
}
/**
* Get all blueprint files (including plugins).
*
* @param array $blueprints
* @param array $plugins
* @return array
*/
protected function getBlueprintFiles(array $blueprints, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->detectPlugins($folder, true);
}
foreach (array_reverse($blueprints) as $folder) {
$list += $this->detectConfig($folder, true);
}
return $list;
}
/**
* Detects all plugins with a configuration file and returns last modification time.
*
* @param string $lookup Location to look up from.
* @param bool $blueprints
* @return array
* @internal
*/
protected function detectPlugins($lookup = SYSTEM_DIR, $blueprints = false)
{
$find = $blueprints ? 'blueprints.yaml' : '.yaml';
$location = $blueprints ? 'blueprintFiles' : 'configFiles';
$path = trim(Folder::getRelativePath($lookup), '/');
if (isset($this->{$location}[$path])) {
return [$path => $this->{$location}[$path]];
}
$list = [];
if (is_dir($lookup)) {
$iterator = new \DirectoryIterator($lookup);
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
if (!$directory->isDir() || $directory->isDot()) {
continue;
}
$name = $directory->getBasename();
$filename = "{$path}/{$name}/" . ($find && $find[0] != '.' ? $find : $name . $find);
if (is_file($filename)) {
$list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
}
}
}
$this->{$location}[$path] = $list;
return [$path => $list];
}
/**
* Detects all plugins with a configuration file and returns last modification time.
*
* @param string $lookup Location to look up from.
* @param bool $blueprints
* @return array
* @internal
*/
protected function detectConfig($lookup = SYSTEM_DIR, $blueprints = false)
{
$location = $blueprints ? 'blueprintFiles' : 'configFiles';
$path = trim(Folder::getRelativePath($lookup), '/');
if (isset($this->{$location}[$path])) {
return [$path => $this->{$location}[$path]];
}
if (is_dir($lookup)) {
// Find all system and user configuration files.
$options = [
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => [
'key' => '|\.yaml$|',
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
}],
'key' => 'SubPathname'
];
$list = Folder::all($lookup, $options);
} else {
$list = [];
}
$this->{$location}[$path] = $list;
return [$path => $list];
}
}

View File

@@ -0,0 +1,386 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Common\Uri;
use Grav\Common\Data\Data;
use Grav\Common\Filesystem\Folder;
use RocketTheme\Toolbox\Blueprints\Blueprints;
use RocketTheme\Toolbox\File\PhpFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* The Config class contains configuration information.
*
* @author RocketTheme
* @license MIT
*/
class Config extends Data
{
protected $grav;
protected $streams = [
'system' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['system'],
]
],
'user' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user'],
]
],
'blueprints' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://blueprints', 'system/blueprints'],
]
],
'config' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://config', 'system/config'],
]
],
'plugins' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://plugins'],
]
],
'plugin' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://plugins'],
]
],
'themes' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://themes'],
]
],
'cache' => [
'type' => 'Stream',
'prefixes' => [
'' => ['cache'],
'images' => ['images']
]
],
'log' => [
'type' => 'Stream',
'prefixes' => [
'' => ['logs']
]
]
];
protected $blueprintFiles = [];
protected $configFiles = [];
protected $checksum;
protected $timestamp;
protected $configLookup;
protected $blueprintLookup;
protected $pluginLookup;
protected $finder;
protected $environment;
protected $messages = [];
public function __construct(array $items = 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);
}
$this->check();
}
public function key()
{
return $this->checksum();
}
public function reload()
{
$this->check();
$this->init();
return $this;
}
protected function check()
{
$streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
if (!is_array($streams)) {
throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
}
$diff = array_keys(array_diff_key($this->streams, $streams));
if ($diff) {
throw new \InvalidArgumentException(
sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
);
}
}
public function debug()
{
foreach ($this->messages as $message) {
$this->grav['debugger']->addMessage($message);
}
}
public function init()
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$this->configLookup = $locator->findResources('config://');
$this->blueprintLookup = $locator->findResources('blueprints://config');
$this->pluginLookup = $locator->findResources('plugins://');
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');
$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 (!$checkBlueprints && !$checkConfig && !$checkSystem) {
$this->messages[] = 'Skip configuration timestamp check.';
return false;
}
// 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]));
}
protected function autoDetectEnvironmentConfig($items)
{
$environment = $this->environment;
$env_stream = 'user://'.$environment.'/config';
if (file_exists(USER_DIR.$environment.'/config')) {
array_unshift($items['streams']['schemes']['config']['prefixes'][''], $env_stream);
}
return $items;
}
protected function loadCompiledBlueprints($blueprints, $plugins, $filename = null)
{
$checksum = md5(json_encode($blueprints));
$filename = $filename
? CACHE_DIR . 'compiled/blueprints/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/blueprints/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$cache = $file->exists() ? $file->content() : null;
$blueprintFiles = $this->finder->locateBlueprintFiles($blueprints, $plugins);
$checksum .= ':'.md5(json_encode($blueprintFiles));
$class = get_class($this);
// Load real file if cache isn't up to date (or is invalid).
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Load blueprints.
$this->blueprints = new Blueprints;
foreach ($blueprintFiles as $files) {
$this->loadBlueprintFiles($files);
}
$cache = [
'@class' => $class,
'checksum' => $checksum,
'files' => $blueprintFiles,
'data' => $this->blueprints->toArray()
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$this->messages[] = 'Saving compiled blueprints.';
$file->save($cache);
$file->unlock();
}
} else {
$this->blueprints = new Blueprints($cache['data']);
}
}
protected function loadCompiledConfig($configs, $plugins, $filename = null)
{
$checksum = md5(json_encode($configs));
$filename = $filename
? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$cache = $file->exists() ? $file->content() : null;
$configFiles = $this->finder->locateConfigFiles($configs, $plugins);
$checksum .= ':'.md5(json_encode($configFiles));
$class = get_class($this);
// Load real file if cache isn't up to date (or is invalid).
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Load configuration.
foreach ($configFiles as $files) {
$this->loadConfigFiles($files);
}
$cache = [
'@class' => $class,
'timestamp' => time(),
'checksum' => $this->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'];
}
/**
* Load blueprints.
*
* @param array $files
*/
public function loadBlueprintFiles(array $files)
{
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->blueprints->embed($name, $file->content(), '/');
}
}
/**
* Load configuration.
*
* @param array $files
*/
public function loadConfigFiles(array $files)
{
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->join($name, $file->content(), '/');
}
}
/**
* Initialize resource locator by using the configuration.
*
* @param UniformResourceLocator $locator
*/
public function initializeLocator(UniformResourceLocator $locator)
{
$locator->reset();
$schemes = (array) $this->get('streams.schemes', []);
foreach ($schemes as $scheme => $config) {
if (isset($config['paths'])) {
$locator->addPath($scheme, '', $config['paths']);
}
if (isset($config['prefixes'])) {
foreach ($config['prefixes'] as $prefix => $paths) {
$locator->addPath($scheme, $prefix, $paths);
}
}
}
}
/**
* Get available streams and their types from the configuration.
*
* @return array
*/
public function getStreams()
{
$schemes = [];
foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
if ($type[0] != '\\') {
$type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
}
$schemes[$scheme] = $type;
}
return $schemes;
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\Filesystem\Folder;
/**
* The Configuration Finder class.
*
* @author RocketTheme
* @license MIT
*/
class ConfigFinder
{
/**
* Get all locations for blueprint files (including plugins).
*
* @param array $blueprints
* @param array $plugins
* @return array
*/
public function locateBlueprintFiles(array $blueprints, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->detectInFolder($folder, 'blueprints');
}
foreach (array_reverse($blueprints) as $folder) {
$list += $this->detectRecursive($folder);
}
return $list;
}
/**
* Get all locations for configuration files (including plugins).
*
* @param array $configs
* @param array $plugins
* @return array
*/
public function locateConfigFiles(array $configs, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->detectInFolder($folder);
}
foreach (array_reverse($configs) as $folder) {
$list += $this->detectRecursive($folder);
}
return $list;
}
/**
* Get all locations for a single configuration file.
*
* @param array $folders Locations to look up from.
* @param string $name Filename to be located.
* @return array
*/
public function locateConfigFile(array $folders, $name)
{
$filename = "{$name}.yaml";
$list = [];
foreach ($folders as $folder) {
$path = trim(Folder::getRelativePath($folder), '/');
if (is_file("{$folder}/{$filename}")) {
$modified = filemtime("{$folder}/{$filename}");
} else {
$modified = 0;
}
$list[$path] = [$name => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
}
return $list;
}
/**
* Detects all plugins with a configuration file and returns them with last modification time.
*
* @param string $folder Location to look up from.
* @param string $lookup Filename to be located.
* @return array
* @internal
*/
protected function detectInFolder($folder, $lookup = null)
{
$path = trim(Folder::getRelativePath($folder), '/');
$list = [];
if (is_dir($folder)) {
$iterator = new \DirectoryIterator($folder);
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
if (!$directory->isDir() || $directory->isDot()) {
continue;
}
$name = $directory->getBasename();
$find = ($lookup ?: $name) . '.yaml';
$filename = "{$path}/{$name}/$find";
if (file_exists($filename)) {
$list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
}
}
}
return [$path => $list];
}
/**
* Detects all plugins with a configuration file and returns them with last modification time.
*
* @param string $folder Location to look up from.
* @return array
* @internal
*/
protected function detectRecursive($folder)
{
$path = trim(Folder::getRelativePath($folder), '/');
if (is_dir($folder)) {
// Find all system and user configuration files.
$options = [
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => [
'key' => '|\.yaml$|',
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
}
],
'key' => 'SubPathname'
];
$list = Folder::all($folder, $options);
} else {
$list = [];
}
return [$path => $list];
}
}

View File

@@ -1,8 +1,7 @@
<?php
namespace Grav\Common\Data;
use \Grav\Common\Registry;
use \Symfony\Component\Yaml\Yaml;
use RocketTheme\Toolbox\ArrayTraits\Export;
/**
* Blueprint handles the inside logic of blueprints.
@@ -12,26 +11,41 @@ use \Symfony\Component\Yaml\Yaml;
*/
class Blueprint
{
use Export;
public $name;
public $initialized = false;
protected $blueprints;
protected $items;
protected $context;
protected $fields;
protected $rules = array();
protected $nested = array();
protected $filter = ['validation' => 1];
/**
* @param string $name
* @param array $data
* @param Blueprints $context
*/
public function __construct($name, array $data, Blueprints $context)
public function __construct($name, array $data = array(), Blueprints $context = null)
{
$this->name = $name;
$this->blueprints = $data;
$this->items = $data;
$this->context = $context;
}
/**
* Set filter for inherited properties.
*
* @param array $filter List of field names to be inherited.
*/
public function setFilter(array $filter)
{
$this->filter = array_flip($filter);
}
/**
* Get value by using dot notation for nested arrays/objects.
*
@@ -46,7 +60,7 @@ class Blueprint
public function get($name, $default = null, $separator = '.')
{
$path = explode($separator, $name);
$current = $this->blueprints;
$current = $this->items;
foreach ($path as $field) {
if (is_object($current) && isset($current->{$field})) {
$current = $current->{$field};
@@ -72,7 +86,7 @@ class Blueprint
public function set($name, $value, $separator = '.')
{
$path = explode($separator, $name);
$current = &$this->blueprints;
$current = &$this->items;
foreach ($path as $field) {
if (is_object($current)) {
// Handle objects.
@@ -102,8 +116,8 @@ class Blueprint
public function fields()
{
if (!isset($this->fields)) {
$this->fields = isset($this->blueprints['form']['fields']) ? $this->blueprints['form']['fields'] : array();
$this->getFields($this->fields);
$this->fields = [];
$this->embed('', $this->items);
}
return $this->fields;
@@ -132,9 +146,10 @@ class Blueprint
*
* @param array $data1
* @param array $data2
* @param string $name
* @return array
*/
public function mergeData(array $data1, array $data2)
public function mergeData(array $data1, array $data2, $name = null)
{
// Initialize data
$this->fields();
@@ -168,6 +183,71 @@ class Blueprint
return $this->extraArray($data, $this->nested, $prefix);
}
/**
* Extend blueprint with another blueprint.
*
* @param Blueprint $extends
* @param bool $append
*/
public function extend(Blueprint $extends, $append = false)
{
$blueprints = $append ? $this->items : $extends->toArray();
$appended = $append ? $extends->toArray() : $this->items;
$bref_stack = array(&$blueprints);
$head_stack = array($appended);
do {
end($bref_stack);
$bref = &$bref_stack[key($bref_stack)];
$head = array_pop($head_stack);
unset($bref_stack[key($bref_stack)]);
foreach (array_keys($head) as $key) {
if (isset($key, $bref[$key]) && is_array($bref[$key]) && is_array($head[$key])) {
$bref_stack[] = &$bref[$key];
$head_stack[] = $head[$key];
} else {
$bref = array_merge($bref, array($key => $head[$key]));
}
}
} while(count($head_stack));
$this->items = $blueprints;
}
/**
* Convert object into an array.
*
* @return array
*/
public function getState()
{
return ['name' => $this->name, 'items' => $this->items, 'rules' => $this->rules, 'nested' => $this->nested];
}
/**
* Embed an array to the blueprint.
*
* @param $name
* @param array $value
* @param string $separator
*/
public function embed($name, array $value, $separator = '.')
{
if (!isset($value['form']['fields']) || !is_array($value['form']['fields'])) {
return;
}
// Initialize data
$this->fields();
$prefix = $name ? strtr($name, $separator, '.') . '.' : '';
$params = array_intersect_key($this->filter, $value);
$this->parseFormFields($value['form']['fields'], $params, $prefix, $this->fields);
}
/**
* @param array $data
* @param array $rules
@@ -188,7 +268,7 @@ class Blueprint
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$this->validateArray($field, $val);
} elseif (isset($this->blueprints['validation']) && $this->blueprints['validation'] == 'strict') {
} elseif (isset($this->items['form']['validation']) && $this->items['form']['validation'] == 'strict') {
// Undefined/extra item.
throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));
}
@@ -214,7 +294,7 @@ class Blueprint
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$field = $this->filterArray($field, $val);
} elseif (isset($this->blueprints['validation']) && $this->blueprints['validation'] == 'strict') {
} elseif (isset($this->items['form']['validation']) && $this->items['form']['validation'] == 'strict') {
$field = null;
}
@@ -282,26 +362,32 @@ class Blueprint
* Gets all field definitions from the blueprints.
*
* @param array $fields
* @param array $params
* @param string $prefix
* @param array $current
* @internal
*/
protected function getFields(array &$fields)
protected function parseFormFields(array &$fields, $params, $prefix, array &$current)
{
// Go though all the fields in current level.
foreach ($fields as $key => &$field) {
$current[$key] = &$field;
// Set name from the array key.
$field['name'] = $key;
$field['name'] = $prefix . $key;
$field += $params;
if (isset($field['fields'])) {
// Recursively get all the nested fields.
$this->getFields($field['fields']);
$newParams = array_intersect_key($this->filter, $field);
$this->parseFormFields($field['fields'], $newParams, $prefix, $current[$key]['fields']);
} else {
// Add rule.
$this->rules[$key] = &$field;
$this->addProperty($key);
$this->rules[$prefix . $key] = &$field;
$this->addProperty($prefix . $key);
foreach ($field as $name => $value) {
// Support nested blueprints.
if ($name == '@import') {
if ($this->context && $name == '@import') {
$values = (array) $value;
if (!isset($field['fields'])) {
$field['fields'] = array();
@@ -380,8 +466,8 @@ class Blueprint
*/
protected function getRule($rule)
{
if (isset($this->blueprints['rules'][$rule]) && is_array($this->blueprints['rules'][$rule])) {
return $this->blueprints['rules'][$rule];
if (isset($this->items['rules'][$rule]) && is_array($this->items['rules'][$rule])) {
return $this->items['rules'][$rule];
}
return array();
}
@@ -405,69 +491,4 @@ class Blueprint
}
}
}
/**
* Convert blueprints into an array.
*
* @return array
*/
public function toArray()
{
return $this->blueprints;
}
/**
* Convert blueprints into YAML string.
*
* @return string
*/
public function toYaml()
{
return Yaml::dump($this->blueprints);
}
/**
* Convert blueprints into JSON string.
*
* @return string
*/
public function toJson()
{
return json_encode($this->blueprints);
}
/**
* Extend blueprint with another blueprint.
*
* @param Blueprint $extends
* @param bool $append
*/
public function extend(Blueprint $extends, $append = false)
{
$blueprints = $append ? $this->blueprints : $extends->toArray();
$appended = $append ? $extends->toArray() : $this->blueprints;
$bref_stack = array(&$blueprints);
$head_stack = array($appended);
do {
end($bref_stack);
$bref = &$bref_stack[key($bref_stack)];
$head = array_pop($head_stack);
unset($bref_stack[key($bref_stack)]);
foreach (array_keys($head) as $key) {
if (isset($key, $bref[$key]) && is_array($bref[$key]) && is_array($head[$key])) {
$bref_stack[] = &$bref[$key];
$head_stack[] = $head[$key];
} else {
$bref = array_merge($bref, array($key => $head[$key]));
}
}
} while(count($head_stack));
$this->blueprints = $blueprints;
}
}

View File

@@ -1,7 +1,7 @@
<?php
namespace Grav\Common\Data;
use \Symfony\Component\Yaml\Yaml;
use Grav\Common\File\CompiledYamlFile;
/**
* Blueprints class keeps track on blueprint instances.
@@ -16,11 +16,15 @@ class Blueprints
protected $instances = array();
/**
* @param string $search Search path.
* @param string|array $search Search path.
*/
public function __construct($search)
{
$this->search = rtrim($search, '\\/') . '/';
if (!is_string($search)) {
$this->search = $search;
} else {
$this->search = rtrim($search, '\\/') . '/';
}
}
/**
@@ -33,11 +37,18 @@ class Blueprints
public function get($type)
{
if (!isset($this->instances[$type])) {
if (is_file($this->search . $type . YAML_EXT)) {
$blueprints = (array) Yaml::parse($this->search . $type . YAML_EXT);
if (is_string($this->search)) {
$filename = $this->search . $type . YAML_EXT;
} else {
$filename = isset($this->search[$type]) ? $this->search[$type] : '';
}
if ($filename && is_file($filename)) {
$file = CompiledYamlFile::instance($filename);
$blueprints = $file->content();
} else {
// throw new \RuntimeException("Blueprints for '{$type}' cannot be found! {$this->search}{$type}");
$blueprints = array();
$blueprints = [];
}
$blueprint = new Blueprint($type, $blueprints, $this);

View File

@@ -1,9 +1,11 @@
<?php
namespace Grav\Common\Data;
use Grav\Common\Filesystem\FileInterface;
use \Grav\Common\Getters;
use \Grav\Common\Filesystem\File;
use RocketTheme\Toolbox\ArrayTraits\ArrayAccessWithGetters;
use RocketTheme\Toolbox\ArrayTraits\Countable;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\FileInterface;
/**
* Recursive data object
@@ -11,8 +13,10 @@ use \Grav\Common\Filesystem\File;
* @author RocketTheme
* @license MIT
*/
class Data extends Getters implements DataInterface
class Data implements DataInterface
{
use ArrayAccessWithGetters, Countable, Export;
protected $gettersVariable = 'items';
protected $items;
@@ -22,7 +26,7 @@ class Data extends Getters implements DataInterface
protected $blueprints;
/**
* @var File\General
* @var File
*/
protected $storage;
@@ -121,17 +125,64 @@ class Data extends Getters implements DataInterface
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @return mixed Value.
*/
public function def($name, $default = null, $separator = '.')
{
$this->set($name, $this->get($name, $default, $separator), $separator);
}
/**
* Join two values together by using blueprints if available.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
*/
public function join($name, $value, $separator = '.')
{
$old = $this->get($name, null, $separator);
if ($old === null) {
// Variable does not exist yet: just use the incoming value.
} elseif ($this->blueprints) {
// Blueprints: join values by using blueprints.
$value = $this->blueprints->mergeData($old, $value, $name, $separator);
} else {
// No blueprints: replace existing top level variables with the new ones.
$value = array_merge($old, $value);
}
$this->set($name, $value, $separator);
}
/**
* Join two values together by using blueprints if available.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
*/
public function joinDefaults($name, $value, $separator = '.')
{
$old = $this->get($name, null, $separator);
if ($old === null) {
// Variable does not exist yet: just use the incoming value.
} elseif ($this->blueprints) {
// Blueprints: join values by using blueprints.
$value = $this->blueprints->mergeData($value, $old, $name, $separator);
} else {
// No blueprints: replace existing top level variables with the new ones.
$value = array_merge($value, $old);
}
$this->set($name, $value, $separator);
}
/**
* Merge two sets of data together.
*
* @param array $data
* @return void
*/
public function merge(array $data)
{
@@ -142,6 +193,21 @@ class Data extends Getters implements DataInterface
}
}
/**
* Add default data to the set.
*
* @param array $data
* @return void
*/
public function setDefaults(array $data)
{
if ($this->blueprints) {
$this->items = $this->blueprints->mergeData($data, $this->items);
} else {
$this->items = array_merge($data, $this->items);
}
}
/**
* Return blueprints.
*

View File

@@ -1,8 +1,7 @@
<?php
namespace Grav\Common\Data;
use Grav\Common\Filesystem\FileInterface;
use \Grav\Common\Filesystem\File;
use RocketTheme\Toolbox\File\FileInterface;
/**
* Data interface

View File

@@ -114,12 +114,6 @@ class Validation
protected static function filterText($value, array $params, array $field)
{
if (!is_string($value)) {
var_dump($value);
var_dump($params);
var_dump($field);
die();
}
return (string) $value;
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Grav\Common;
use DebugBar\Bridge\Twig\TraceableTwigEnvironment;
use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
//use \Tracy\Debugger as TracyDebugger;
/**
* Class Debugger
* @package Grav\Common
*/
class Debugger
{
protected $grav;
protected $debugbar;
protected $renderer;
protected $enabled;
public function __construct()
{
$this->debugbar = new StandardDebugBar();
$this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true));
}
public function init()
{
$this->grav = Grav::instance();
if ($this->enabled()) {
$this->debugbar->addCollector(new \DebugBar\DataCollector\ConfigCollector((array)$this->grav['config']->get('system')));
}
return $this;
}
public function enabled($state = null)
{
if (isset($state)) {
$this->enabled = $state;
} else {
if (!isset($this->enabled)) {
$this->enabled = $this->grav['config']->get('system.debugger.enabled');
}
}
return $this->enabled;
}
public function addAssets()
{
if ($this->enabled()) {
$assets = $this->grav['assets'];
$this->renderer = $this->debugbar->getJavascriptRenderer();
$this->renderer->setIncludeVendors(false);
// Get the required CSS files
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
foreach ($css_files as $css) {
$assets->addCss($css);
}
$assets->addCss('/system/assets/debugger.css');
foreach ($js_files as $js) {
$assets->addJs($js);
}
}
return $this;
}
public function addCollector($collector)
{
$this->debugbar->addCollector($collector);
return $this;
}
public function getCollector($collector)
{
return $this->debugbar->getCollector($collector);
}
public function render()
{
if ($this->enabled()) {
echo $this->renderer->render();
}
return $this;
}
public function sendDataInHeaders()
{
$this->debugbar->sendDataInHeaders();
return $this;
}
public function startTimer($name, $desription = null)
{
if ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled')) {
$this->debugbar['time']->startMeasure($name, $desription);
}
return $this;
}
public function stopTimer($name)
{
if ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled')) {
$this->debugbar['time']->stopMeasure($name);
}
return $this;
}
public function addMessage($message, $label = 'info', $isString = true)
{
if ($this->enabled()) {
$this->debugbar['messages']->addMessage($message, $label, $isString);
}
return $this;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Grav\Common\Errors;
use Grav\Common\Grav;
use Whoops\Handler\CallbackHandler;
use Whoops\Handler\HandlerInterface;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Handler\PlainTextHandler;
use Whoops\Run;
/**
* Class Debugger
* @package Grav\Common
*/
class Errors extends \Whoops\Run
{
public function pushHandler($handler, $key = null)
{
if (is_callable($handler)) {
$handler = new CallbackHandler($handler);
}
if (!$handler instanceof HandlerInterface) {
throw new InvalidArgumentException(
"Argument to " . __METHOD__ . " must be a callable, or instance of"
. "Whoops\\Handler\\HandlerInterface"
);
}
// Store with key if provided
if ($key) {
$this->handlerStack[$key] = $handler;
} else {
$this->handlerStack[] = $handler;
}
return $this;
}
public function resetHandlers()
{
$grav = Grav::instance();
$config = $grav['config']->get('system.errors');
if (isset($config['display']) && !$config['display']) {
unset($this->handlerStack['pretty']);
$this->handlerStack = array('simple' => new SimplePageHandler()) + $this->handlerStack;
}
if (isset($config['log']) && !$config['log']) {
unset($this->handlerStack['log']);
}
}
}

View File

@@ -0,0 +1,51 @@
html, body {
height: 100%
}
body {
margin:0 3rem;
padding:0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 1.5rem;
line-height: 1.4;
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
display: -ms-flexbox; /* TWEENER - IE 10 */
display: -webkit-flex; /* NEW - Chrome */
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
}
.container {
margin: 0rem;
max-width: 600px;
padding-bottom:5rem;
}
header {
color: #000;
font-size: 4rem;
letter-spacing: 2px;
line-height: 1.1;
margin-bottom: 2rem;
}
p {
font-family: Optima, Segoe, "Segoe UI", Candara, Calibri, Arial, sans-serif;
color: #666;
}
h5 {
font-weight: normal;
color: #999;
font-size: 1rem;
}
h6 {
font-weight: normal;
color: #999;
}
code {
font-weight: bold;
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Layout template file for Whoops's pretty error output.
*/
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Whoops there was an error!</title>
<style><?php echo $stylesheet ?></style>
</head>
<body>
<div class="container">
<div class="details">
<header>
Server Error
</header>
<p>We're sorry! The server has encountered an internal error and was unable to complete your request.
Please contact the system administrator for more information.</p>
<h6>For further details please review your <code>logs/</code> folder, or enable displaying of errors in your system configuration.</h6>
<h6>Error Code: <b><?php echo $code ?></b></h6>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,91 @@
<?php
namespace Grav\Common\Errors;
use Whoops\Handler\Handler;
use Whoops\Util\Misc;
use Whoops\Util\TemplateHelper;
class SimplePageHandler extends Handler
{
private $searchPaths = array();
private $resourceCache = array();
public function __construct()
{
// Add the default, local resource search path:
$this->searchPaths[] = __DIR__ . "/Resources";
}
/**
* @return int|null
*/
public function handle()
{
$exception = $this->getException();
$inspector = $this->getInspector();
$run = $this->getRun();
$helper = new TemplateHelper();
$templateFile = $this->getResource("layout.html.php");
$cssFile = $this->getResource("error.css");
$code = $inspector->getException()->getCode();
if ($inspector->getException() instanceof \ErrorException) {
$code = Misc::translateErrorCode($code);
}
$vars = array(
"stylesheet" => file_get_contents($cssFile),
"code" => $code,
);
$helper->setVariables($vars);
$helper->render($templateFile);
return Handler::QUIT;
}
protected function getResource($resource)
{
// If the resource was found before, we can speed things up
// by caching its absolute, resolved path:
if (isset($this->resourceCache[$resource])) {
return $this->resourceCache[$resource];
}
// Search through available search paths, until we find the
// resource we're after:
foreach ($this->searchPaths as $path) {
$fullPath = $path . "/$resource";
if (is_file($fullPath)) {
// Cache the result:
$this->resourceCache[$resource] = $fullPath;
return $fullPath;
}
}
// If we got this far, nothing was found.
throw new RuntimeException(
"Could not find resource '$resource' in any resource paths."
. "(searched: " . join(", ", $this->searchPaths). ")"
);
}
public function addResourcePath($path)
{
if (!is_dir($path)) {
throw new InvalidArgumentException(
"'$path' is not a valid directory"
);
}
array_unshift($this->searchPaths, $path);
}
public function getResourcePaths()
{
return $this->searchPaths;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Grav\Common\File;
use RocketTheme\Toolbox\File\PhpFile;
/**
* Class CompiledFile
* @package Grav\Common\File
*
* @property string $filename
* @property string $extension
* @property string $raw
* @property array|string $content
*/
trait CompiledFile
{
/**
* Get/set parsed file contents.
*
* @param mixed $var
* @return string
*/
public function content($var = null)
{
// 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");
$modified = $this->modified();
if (!$modified) {
return $this->decode($this->raw());
}
$class = get_class($this);
$cache = $file->exists() ? $file->content() : null;
// Load real file if cache isn't up to date (or is invalid).
if (
!isset($cache['@class'])
|| $cache['@class'] != $class
|| $cache['modified'] != $modified
|| $cache['filename'] != $this->filename
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Decode RAW file into compiled array.
$data = $this->decode($this->raw());
$cache = [
'@class' => $class,
'filename' => $this->filename,
'modified' => $modified,
'data' => $data
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$file->save($cache);
$file->unlock();
}
}
$this->content = $cache['data'];
}
return parent::content($var);
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Grav\Common\File;
use RocketTheme\Toolbox\File\MarkdownFile;
class CompiledMarkdownFile extends MarkdownFile
{
use CompiledFile;
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Grav\Common\File;
use RocketTheme\Toolbox\File\YamlFile;
class CompiledYamlFile extends YamlFile
{
use CompiledFile;
}

View File

@@ -1,129 +0,0 @@
<?php
namespace Grav\Common\Filesystem\File;
/**
* File handling class.
*
* @author RocketTheme
* @license MIT
*/
class Config extends General
{
/**
* @var string
*/
protected $extension = '.php';
/**
* @var array|General[]
*/
static protected $instances = array();
/**
* Saves configuration file and invalidates opcache.
*
* @param mixed $data Optional data to be saved, usually array.
* @throws \RuntimeException
*/
public function save($data = null)
{
parent::save($data);
// Invalidate configuration file from the opcache.
if (function_exists('opcache_invalidate')) {
// PHP 5.5.5+
@opcache_invalidate($this->filename);
} elseif (function_exists('apc_invalidate')) {
// APC
@apc_invalidate($this->filename);
}
}
/**
* Check contents and make sure it is in correct format.
*
* @param \Grav\Common\Config $var
* @return \Grav\Common\Config
* @throws \RuntimeException
*/
protected function check($var)
{
if (!($var instanceof \Grav\Common\Config)) {
throw new \RuntimeException('Provided data is not configuration');
}
return $var;
}
/**
* Encode configuration object into RAW string (PHP class).
*
* @param \Grav\Common\Config $var
* @return string
* @throws \RuntimeException
*/
protected function encode($var)
{
if (!($var instanceof \Grav\Common\Config)) {
throw new \RuntimeException('Provided data is not configuration');
}
// Build the object variables string
$vars = array();
$options = $var->toArray();
foreach ($options as $k => $v) {
if (is_int($v)) {
$vars[] = "\tpublic $" . $k . " = " . $v . ";";
} elseif (is_bool($v)) {
$vars[] = "\tpublic $" . $k . " = " . ($v ? 'true' : 'false') . ";";
} elseif (is_scalar($v)) {
$vars[] = "\tpublic $" . $k . " = '" . addcslashes($v, '\\\'') . "';";
} elseif (is_array($v) || is_object($v)) {
$vars[] = "\tpublic $" . $k . " = " . $this->encodeArray((array) $v) . ";";
}
}
$vars = implode("\n", $vars);
return "<?php\nnamespace Grav;\n\nclass Config extends \\Grav\\Common\\Config {\n {$vars}\n}";
}
/**
* Method to get an array as an exported string.
*
* @param array $a The array to get as a string.
* @param int $level Used internally to indent rows.
*
* @return array
*/
protected function encodeArray($a, $level = 1)
{
$r = array();
foreach ($a as $k => $v) {
if (is_array($v) || is_object($v)) {
$r[] = '"' . $k . '" => ' . $this->encodeArray((array) $v, $level+1);
} elseif (is_int($v)) {
$r[] = "'" . $k . "' => " . $v;
} elseif (is_bool($v)) {
$r[] = "'" . $k . "' => " . ($v ? 'true' : 'false');
} else {
$r[] .= "'" . $k . "' => " . "'" . addslashes($v) . "'";
}
}
$tabs = str_repeat("\t", $level);
return "array(\n\t{$tabs}" . implode(",\n\t{$tabs}", $r) . "\n{$tabs})";
}
/**
* Decode RAW string into contents.
*
* @param string $var
* @return \Grav\Common\Config
*/
protected function decode($var)
{
// TODO: improve this one later, works only for single file...
return class_exists('\Grav\Config') ? new \Grav\Config($this->filename) : new Config($this->filename);
}
}

View File

@@ -1,352 +0,0 @@
<?php
namespace Grav\Common\Filesystem\File;
use Grav\Common\Filesystem\FileInterface;
/**
* General file handling class.
*
* @author RocketTheme
* @license MIT
*/
class General implements FileInterface
{
/**
* @var string
*/
protected $filename;
/**
* @var resource
*/
protected $handle;
/**
* @var bool|null
*/
protected $locked;
/**
* @var string
*/
protected $extension;
/**
* @var string Raw file contents.
*/
protected $raw;
/**
* @var array Parsed file contents.
*/
protected $content;
/**
* @var array|General[]
*/
static protected $instances = array();
/**
* Get file instance.
*
* @param string $filename
* @return FileInterface
*/
public static function instance($filename)
{
if (!isset(static::$instances[$filename])) {
static::$instances[$filename] = new static;
static::$instances[$filename]->init($filename);
}
return static::$instances[$filename];
}
/**
* Prevent constructor from being used.
*
* @internal
*/
protected function __construct()
{
}
/**
* Prevent cloning.
*
* @internal
*/
protected function __clone()
{
//Me not like clones! Me smash clones!
}
/**
* Set filename.
*
* @param $filename
*/
protected function init($filename)
{
$this->filename = $filename;
}
/**
* Get/set the file location.
*
* @param string $var
* @return string
*/
public function filename($var = null)
{
if ($var !== null) {
$this->filename = $var;
}
return $this->filename;
}
/**
* Return basename of the file.
*
* @return string
*/
public function basename()
{
return basename($this->filename, $this->extension);
}
/**
* Check if file exits.
*
* @return bool
*/
public function exists()
{
return is_file($this->filename);
}
/**
* Return file modification time.
*
* @return int|bool Timestamp or false if file doesn't exist.
*/
public function modified()
{
return is_file($this->filename) ? filemtime($this->filename) : false;
}
/**
* Lock file for writing. You need to manually unlock().
*
* @param bool $block For non-blocking lock, set the parameter to false.
* @return bool
*/
public function lock($block = true)
{
if (!$this->handle) {
if (!$this->mkdir(dirname($this->filename))) {
throw new \RuntimeException('Creating directory failed for ' . $this->filename);
}
$this->handle = fopen($this->filename, 'wb+');
}
$lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB;
return $this->locked = flock($this->handle, $lock);
}
/**
* Returns true if file has been locked for writing.
*
* @return bool|null True = locked, false = failed, null = not locked.
*/
public function locked()
{
return $this->locked;
}
/**
* Unlock file.
*
* @return bool
*/
public function unlock()
{
if (!$this->handle) {
return;
}
if ($this->locked) {
flock($this->handle, LOCK_UN);
$this->locked = null;
}
fclose($this->handle);
}
/**
* Check if file can be written.
*
* @return bool
*/
public function writable()
{
return is_writable($this->filename) || $this->writableDir(dirname($this->filename));
}
/**
* (Re)Load a file and return RAW file contents.
*
* @return string
*/
public function load()
{
$this->raw = $this->exists() ? (string) file_get_contents($this->filename) : '';
$this->content = null;
return $this->raw;
}
/**
* Get/set raw file contents.
*
* @param string $var
* @return string
*/
public function raw($var = null)
{
if ($var !== null) {
$this->raw = (string) $var;
$this->content = null;
}
if (!is_string($this->raw)) {
$this->raw = $this->load();
}
return $this->raw;
}
/**
* Get/set parsed file contents.
*
* @param mixed $var
* @return string
*/
public function content($var = null)
{
if ($var !== null) {
$this->content = $this->check($var);
// Update RAW, too.
$this->raw = $this->encode($this->content);
} elseif ($this->content === null) {
// Decode RAW file.
$this->content = $this->decode($this->raw());
}
return $this->content;
}
/**
* Save file.
*
* @param mixed $data Optional data to be saved, usually array.
* @throws \RuntimeException
*/
public function save($data = null)
{
if ($data !== null) {
$this->content($data);
}
if (!$this->locked) {
// Obtain blocking lock or fail.
if (!$this->lock()) {
throw new \RuntimeException('Obtaining write lock failed on file: ' . $this->filename);
}
$lock = true;
}
if (@fwrite($this->handle, $this->raw()) === false) {
$this->unlock();
throw new \RuntimeException('Saving file failed: ' . $this->filename);
}
if (isset($lock)) {
$this->unlock();
}
// Touch the directory as well, thus marking it modified.
@touch(dirname($this->filename));
}
/**
* Delete file from filesystem.
*
* @return bool
*/
public function delete()
{
return unlink($this->filename);
}
/**
* Check contents and make sure it is in correct format.
*
* Override in derived class.
*
* @param string $var
* @return string
*/
protected function check($var)
{
return (string) $var;
}
/**
* Encode contents into RAW string.
*
* Override in derived class.
*
* @param string $var
* @return string
*/
protected function encode($var)
{
return (string) $var;
}
/**
* Decode RAW string into contents.
*
* Override in derived class.
*
* @param string $var
* @return string mixed
*/
protected function decode($var)
{
return (string) $var;
}
/**
* @param string $dir
* @return bool
* @internal
*/
protected function mkdir($dir)
{
return is_dir($dir) || mkdir($dir, 0777, true);
}
/**
* @param string $dir
* @return bool
* @internal
*/
protected function writableDir($dir)
{
if ($dir && !file_exists($dir)) {
return $this->writableDir(dirname($dir));
}
return $dir && is_dir($dir) && is_writable($dir);
}
}

View File

@@ -1,54 +0,0 @@
<?php
namespace Grav\Common\Filesystem\File;
/**
* File handling class for JSON.
*
* @author RocketTheme
* @license MIT
*/
class Json extends General
{
/**
* @var string
*/
protected $extension = '.json';
/**
* @var array|General[]
*/
static protected $instances = array();
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
*/
protected function check($var)
{
return (array) $var;
}
/**
* Encode contents into RAW string.
*
* @param string $var
* @return string
*/
protected function encode($var)
{
return (string) json_encode($var);
}
/**
* Decode RAW string into contents.
*
* @param string $var
* @return array mixed
*/
protected function decode($var)
{
return (array) json_decode($var);
}
}

View File

@@ -1,69 +0,0 @@
<?php
namespace Grav\Common\Filesystem\File;
/**
* File handling class for Log files.
*
* @author RocketTheme
* @license MIT
*/
class Log extends General
{
/**
* @var array|General[]
*/
static protected $instances = array();
/**
* Constructor.
*/
protected function __construct()
{
parent::__construct();
$this->extension = '.log';
}
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
*/
protected function check($var)
{
return (array) $var;
}
/**
* Encode contents into RAW string (unsupported).
*
* @param string $var
* @throws \Exception
*/
protected function encode($var)
{
throw new \Exception('Saving log file is forbidden.');
}
/**
* Decode RAW string into contents.
*
* @param string $var
* @return array mixed
*/
protected function decode($var)
{
$lines = (array) preg_split('#(\r\n|\n|\r)#', $var);
$results = array();
foreach ($lines as $line) {
preg_match('#^\[(.*)\] (.*) @ (.*) @@ (.*)$#', $line, $matches);
if ($matches) {
$results[] = ['date' => $matches[1], 'message' => $matches[2], 'url' => $matches[3], 'file' => $matches[4]];
}
}
return $results;
}
}

View File

@@ -1,120 +0,0 @@
<?php
namespace Grav\Common\Filesystem\File;
use \Symfony\Component\Yaml\Yaml as YamlParser;
/**
* File handling class.
*
* @author RocketTheme
* @license MIT
*/
class Markdown extends General
{
/**
* @var string
*/
protected $extension = '.md';
/**
* @var array|General[]
*/
static protected $instances = array();
/**
* Get/set file header.
*
* @param array $var
*
* @return array
*/
public function header(array $var = null)
{
$content = $this->content();
if ($var !== null) {
$content['header'] = $var;
$this->content($content);
}
return $content['header'];
}
/**
* Get/set markdown content.
*
* @param string $var
*
* @return string
*/
public function markdown($var = null)
{
$content = $this->content();
if ($var !== null) {
$content['markdown'] = (string) $var;
$this->content($content);
}
return $content['markdown'];
}
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
*/
protected function check($var)
{
$var = (array) $var;
if (!isset($var['header']) || !is_array($var['header'])) {
$var['header'] = array();
}
if (!isset($var['markdown']) || !is_string($var['markdown'])) {
$var['markdown'] = '';
}
return $var;
}
/**
* Encode contents into RAW string.
*
* @param string $var
* @return string
*/
protected function encode($var)
{
// Create Markdown file with YAML header.
$o = (!empty($var['header']) ? "---\n" . trim(YamlParser::dump($var['header'])) . "\n---\n\n" : '') . $var['markdown'];
// Normalize line endings to Unix style.
$o = preg_replace("/(\r\n|\r)/", "\n", $o);
return $o;
}
/**
* Decode RAW string into contents.
*
* @param string $var
* @return array mixed
*/
protected function decode($var)
{
$content = array();
// Normalize line endings to Unix style.
$var = preg_replace("/(\r\n|\r)/", "\n", $var);
// Parse header.
preg_match("/---\n(.+?)\n---(\n\n|$)/uism", $this->raw(), $m);
$content['header'] = isset($m[1]) ? YamlParser::parse(preg_replace("/\n\t/", "\n ", $m[1])) : array();
// Strip header to get content.
$content['markdown'] = trim(preg_replace("/---\n(.+?)\n---(\n\n|$)/uism", '', $var));
return $content;
}
}

View File

@@ -1,61 +0,0 @@
<?php
namespace Grav\Common\Filesystem\File;
use \Symfony\Component\Yaml\Yaml as YamlParser;
/**
* File handling class for YAML.
*
* @author RocketTheme
* @license MIT
*/
class Yaml extends General
{
/**
* @var array|General[]
*/
static protected $instances = array();
/**
* Constructor.
*/
protected function __construct()
{
parent::__construct();
$this->extension = YAML_EXT;
}
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
*/
protected function check($var)
{
return (array) $var;
}
/**
* Encode contents into RAW string.
*
* @param string $var
* @return string
*/
protected function encode($var)
{
return (string) YamlParser::dump($var);
}
/**
* Decode RAW string into contents.
*
* @param string $var
* @return array mixed
*/
protected function decode($var)
{
return (array) YamlParser::parse($var);
}
}

View File

@@ -1,100 +0,0 @@
<?php
namespace Grav\Common\Filesystem;
/**
* File interface.
*
* @author RocketTheme
* @license MIT
*/
interface FileInterface
{
/**
* Get file instance.
*
* @param string $filename
* @return mixed
*/
public static function instance($filename);
/**
* Check if file exits.
*
* @return bool
*/
public function exists();
/**
* Return file modification time.
*
* @return int Timestamp
*/
public function modified();
/**
* Lock file for writing. Lock gets automatically released during the save().
*
* @param bool $block For non-blocking lock, set the parameter to false.
* @return bool
*/
public function lock($block = true);
/**
* Returns true if file has been locked for writing.
*
* @return bool|null True = locked, false = failed, null = not locked.
*/
public function locked();
/**
* Unlock file.
*
* @return bool
*/
public function unlock();
/**
* Check if file can be written.
*
* @return bool
*/
public function writable();
/**
* (Re)Load a file and return its contents.
*
* @return string
*/
public function load();
/**
* Get/set raw file contents.
*
* @param string $var
* @return string
*/
public function raw($var = null);
/**
* Get/set parsed file contents.
*
* @param string $var
* @return string
*/
public function content($var = null);
/**
* Save file.
*
* @param string $data Optional data to be saved.
* @throws \RuntimeException
*/
public function save($data = null);
/**
* Delete file from filesystem.
*
* @return bool
*/
public function delete();
}

View File

@@ -12,16 +12,16 @@ abstract class Folder
/**
* Recursively find the last modified time under given path.
*
* @param string $path
* @param string $path
* @return int
*/
public static function lastModified($path)
public static function lastModifiedFolder($path)
{
$last_modified = 0;
$directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
$last_modified = 0;
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
$dir_modified = $file->getMTime();
@@ -29,34 +29,98 @@ abstract class Folder
$last_modified = $dir_modified;
}
}
return $last_modified;
}
/**
* Get relative path between target and base path. If path isn't relative, return full path.
*
* @param string $path
* @param string $base
* @return string
*/
public static function getRelativePath($path, $base = GRAV_ROOT)
{
if ($base) {
$base = preg_replace('![\\|/]+!', '/', $base);
$path = preg_replace('![\\|/]+!', '/', $path);
if (strpos($path, $base) === 0) {
$path = ltrim(substr($path, strlen($base)), '/');
}
}
return $path;
}
/**
* Shift first directory out of the path.
*
* @param string $path
* @return string
*/
public static function shift(&$path)
{
$parts = explode('/', trim($path, '/'), 2);
$result = array_shift($parts);
$path = array_shift($parts);
return $result ?: null;
}
/**
* Recursively find the last modified time under given path by file.
*
* @param string $path
* @return int
*/
public static function lastModifiedFile($path)
{
$last_modified = 0;
$dirItr = new \RecursiveDirectoryIterator($path);
$filterItr = new GravRecursiveFilterIterator($dirItr);
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
/** @var \RecursiveDirectoryIterator $file */
foreach ($itr as $file) {
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
}
}
return $last_modified;
}
/**
* Return recursive list of all files and directories under given path.
*
* @param string $path
* @param array $params
* @param string $path
* @param array $params
* @return array
* @throws \RuntimeException
*/
public static function all($path, array $params = array())
{
$path = realpath($path);
if ($path === false) {
throw new \RuntimeException("Path to {$path} doesn't exist.");
}
$compare = $params['compare'] ? 'get' . $params['compare'] : null;
$pattern = $params['pattern'] ? $params['pattern'] : null;
$filters = $params['filters'] ? $params['filters'] : null;
$key = $params['key'] ? 'get' . $params['key'] : null;
$value = $params['value'] ? 'get' . $params['value'] : 'SubPathname';
$compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
$pattern = isset($params['pattern']) ? $params['pattern'] : null;
$filters = isset($params['filters']) ? $params['filters'] : null;
$recursive = isset($params['recursive']) ? $params['recursive'] : true;
$key = isset($params['key']) ? 'get' . $params['key'] : null;
$value = isset($params['value']) ? 'get' . $params['value'] : ($recursive ? 'getSubPathname' : 'getFilename');
$directory = new \RecursiveDirectoryIterator($path,
\RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
if ($recursive) {
$directory = new \RecursiveDirectoryIterator($path,
\RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
} else {
$iterator = new \FilesystemIterator($path);
}
$results = array();
@@ -72,20 +136,30 @@ abstract class Folder
$fileKey = preg_replace($filters['key'], '', $fileKey);
}
if (isset($filters['value'])) {
$filePath = preg_replace($filters['value'], '', $filePath);
$filter = $filters['value'];
if (is_callable($filter)) {
$filePath = call_user_func($filter, $file);
} else {
$filePath = preg_replace($filter, '', $filePath);
}
}
}
if ($fileKey !== null) {
$results[$fileKey] = $filePath;
} else {
$results[] = $filePath;
}
}
return $results;
}
/**
* Recursively copy directory in filesystem.
*
* @param string $source
* @param string $target
* @param string $source
* @param string $target
* @throws \RuntimeException
*/
public static function copy($source, $target)
@@ -129,8 +203,8 @@ abstract class Folder
/**
* Move directory in filesystem.
*
* @param string $source
* @param string $target
* @param string $source
* @param string $target
* @throws \RuntimeException
*/
public static function move($source, $target)
@@ -160,6 +234,7 @@ abstract class Folder
*
* @param string $target
* @throws \RuntimeException
* @return bool
*/
public static function delete($target)
{
@@ -176,6 +251,26 @@ abstract class Folder
// Make sure that the change will be detected when caching.
@touch(dirname($target));
return $success;
}
/**
* @param string $folder
* @throws \RuntimeException
* @internal
*/
public static function mkdir($folder)
{
if (is_dir($folder)) {
return;
}
$success = @mkdir($folder, 0777, true);
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
}
}
/**
@@ -199,23 +294,21 @@ abstract class Folder
return @rmdir($folder);
}
/**
* @param string $folder
* @throws \RuntimeException
* @internal
*/
protected static function mkdir($folder)
{
if (is_dir($folder)) {
return;
}
$success = @mkdir($folder, 0777, true);
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
}
}
}
class GravRecursiveFilterIterator extends \RecursiveFilterIterator
{
public static $FILTERS = array(
'..', '.DS_Store'
);
public function accept()
{
return !in_array(
$this->current()->getFilename(),
self::$FILTERS,
true
);
}
}

View File

@@ -0,0 +1,364 @@
<?php
namespace Grav\Common\GPM;
use Grav\Common\Iterator;
class GPM extends Iterator
{
/**
* Local installed Packages
* @var Local\Packages
*/
private $installed;
/**
* Remote available Packages
* @var Remote\Packages
*/
private $repository;
/**
* @var Remote\Grav
*/
public $grav;
/**
* Internal cache
* @var
*/
protected $cache;
/**
* Creates a new GPM instance with Local and Remote packages available
* @param boolean $refresh Applies to Remote Packages only and forces a refetch of data
* @param callable $callback Either a function or callback in array notation
*/
public function __construct($refresh = false, $callback = null)
{
$this->installed = new Local\Packages();
$this->repository = new Remote\Packages($refresh, $callback);
$this->grav = new Remote\Grav($refresh, $callback);
}
/**
* Returns the Locally installed packages
* @return Iterator The installed packages
*/
public function getInstalled()
{
return $this->installed;
}
/**
* Returns the amount of locally installed packages
* @return integer Amount of installed packages
*/
public function countInstalled()
{
$installed = $this->getInstalled();
return count($installed['plugins']) + count($installed['themes']);
}
/**
* Return the instance of a specific Plugin
* @param string $slug The slug of the Plugin
* @return Local\Package The instance of the Plugin
*/
public function getInstalledPlugin($slug)
{
return $this->installed['plugins'][$slug];
}
/**
* Returns the Locally installed plugins
* @return Iterator The installed plugins
*/
public function getInstalledPlugins()
{
return $this->installed['plugins'];
}
/**
* Checks if a Plugin is installed
* @param string $slug The slug of the Plugin
* @return boolean True if the Plugin has been installed. False otherwise
*/
public function isPluginInstalled($slug)
{
return isset($this->installed['plugins'][$slug]);
}
/**
* Return the instance of a specific Theme
* @param string $slug The slug of the Theme
* @return Local\Package The instance of the Theme
*/
public function getInstalledTheme($slug)
{
return $this->installed['themes'][$slug];
}
/**
* Returns the Locally installed themes
* @return Iterator The installed themes
*/
public function getInstalledThemes()
{
return $this->installed['themes'];
}
/**
* Checks if a Theme is installed
* @param string $slug The slug of the Theme
* @return boolean True if the Theme has been installed. False otherwise
*/
public function isThemeInstalled($slug)
{
return isset($this->installed['themes'][$slug]);
}
/**
* Returns the amount of updates available
* @return integer Amount of available updates
*/
public function countUpdates()
{
$count = 0;
$count += count($this->getUpdatablePlugins());
$count += count($this->getUpdatableThemes());
return $count;
}
/**
* Returns an array of Plugins and Themes that can be updated.
* Plugins and Themes are extended with the `available` property that relies to the remote version
* @return array Array of updatable Plugins and Themes.
* Format: ['total' => int, 'plugins' => array, 'themes' => array]
*/
public function getUpdatable()
{
$plugins = $this->getUpdatablePlugins();
$themes = $this->getUpdatableThemes();
$items = [
'total' => count($plugins)+count($themes),
'plugins' => $plugins,
'themes' => $themes
];
return $items;
}
/**
* Returns an array of Plugins that can be updated.
* The Plugins are extended with the `available` property that relies to the remote version
* @return Iterator Array of updatable Plugins
*/
public function getUpdatablePlugins()
{
$items = [];
$repository = $this->repository['plugins'];
// local cache to speed things up
if (isset($this->cache[__METHOD__])) {
return $this->cache[__METHOD__];
}
foreach ($this->installed['plugins'] as $slug => $plugin) {
if (!isset($repository[$slug]) || $plugin->symlink) {
continue;
}
$local_version = $plugin->version ? $plugin->version : 'Unknown';
$remote_version = $repository[$slug]->version;
if (version_compare($local_version, $remote_version) < 0) {
$repository[$slug]->available = $remote_version;
$repository[$slug]->version = $local_version;
$items[$slug] = $repository[$slug];
}
}
$this->cache[__METHOD__] = $items;
return $items;
}
/**
* Check if a Plugin or Theme is updatable
* @param string $slug The slug of the package
* @return boolean True if updatable. False otherwise or if not found
*/
public function isUpdatable($slug)
{
return $this->isPluginUpdatable($slug) || $this->isThemeUpdatable($slug);
}
/**
* Checks if a Plugin is updatable
* @param string $plugin The slug of the Plugin
* @return boolean True if the Plugin is updatable. False otherwise
*/
public function isPluginUpdatable($plugin)
{
return array_key_exists($plugin, (array) $this->getUpdatablePlugins());
}
/**
* Returns an array of Themes that can be updated.
* The Themes are extended with the `available` property that relies to the remote version
* @return Iterator Array of updatable Themes
*/
public function getUpdatableThemes()
{
$items = [];
$repository = $this->repository['themes'];
// local cache to speed things up
if (isset($this->cache[__METHOD__])) {
return $this->cache[__METHOD__];
}
foreach ($this->installed['themes'] as $slug => $plugin) {
if (!isset($repository[$slug]) || $plugin->symlink) {
continue;
}
$local_version = $plugin->version ? $plugin->version : 'Unknown';
$remote_version = $repository[$slug]->version;
if (version_compare($local_version, $remote_version) < 0) {
$repository[$slug]->available = $remote_version;
$repository[$slug]->version = $local_version;
$items[$slug] = $repository[$slug];
}
}
$this->cache[__METHOD__] = $items;
return $items;
}
/**
* Checks if a Theme is Updatable
* @param string $theme The slug of the Theme
* @return boolean True if the Theme is updatable. False otherwise
*/
public function isThemeUpdatable($theme)
{
return array_key_exists($theme, (array) $this->getUpdatableThemes());
}
/**
* Returns a Plugin from the repository
* @param string $slug The slug of the Plugin
* @return mixed Package if found, NULL if not
*/
public function getRepositoryPlugin($slug)
{
return @$this->repository['plugins'][$slug];
}
/**
* Returns the list of Plugins available in the repository
* @return Iterator The Plugins remotely available
*/
public function getRepositoryPlugins()
{
return $this->repository['plugins'];
}
/**
* Returns a Theme from the repository
* @param string $slug The slug of the Theme
* @return mixed Package if found, NULL if not
*/
public function getRepositoryTheme($slug)
{
return @$this->repository['themes'][$slug];
}
/**
* Returns the list of Themes available in the repository
* @return Iterator The Themes remotely available
*/
public function getRepositoryThemes()
{
return $this->repository['themes'];
}
/**
* Returns the list of Plugins and Themes available in the repository
* @return array Array of available Plugins and Themes
* Format: ['plugins' => array, 'themes' => array]
*/
public function getRepository()
{
return $this->repository;
}
/**
* Searches for a Package in the repository
* @param string $search Can be either the slug or the name
* @return Remote\Package Package if found, FALSE if not
*/
public function findPackage($search)
{
$search = strtolower($search);
if ($found = $this->getRepositoryTheme($search)) {
return $found;
}
if ($found = $this->getRepositoryPlugin($search)) {
return $found;
}
foreach ($this->getRepositoryThemes() as $slug => $theme) {
if ($search == $slug || $search == $theme->name) {
return $theme;
}
}
foreach ($this->getRepositoryPlugins() as $slug => $plugin) {
if ($search == $slug || $search == $plugin->name) {
return $plugin;
}
}
return false;
}
/**
* Returns the list of Plugins and Themes available in the repository
* @return array Array of available Plugins and Themes
* Format: ['plugins' => array, 'themes' => array]
*/
/**
* Searches for a list of Packages in the repository
* @param array $searches An array of either slugs or names
* @return array Array of found Packages
* Format: ['total' => int, 'not_found' => array, <found-slugs>]
*/
public function findPackages($searches = [])
{
$packages = ['total' => 0, 'not_found' => []];
foreach ($searches as $search) {
if ($found = $this->findPackage($search)) {
if (!isset($packages[$found->package_type])) {
$packages[$found->package_type] = [];
}
$packages[$found->package_type][$found->slug] = $found;
$packages['total']++;
} else {
$packages['not_found'][] = $search;
}
}
return $packages;
}
}

View File

@@ -0,0 +1,293 @@
<?php
namespace Grav\Common\GPM;
use Grav\Common\Filesystem\Folder;
class Installer
{
/** @const No error */
const OK = 0;
/** @const Target already exists */
const EXISTS = 1;
/** @const Target is a symbolic link */
const IS_LINK = 2;
/** @const Target doesn't exist */
const NOT_FOUND = 4;
/** @const Target is not a directory */
const NOT_DIRECTORY = 8;
/** @const Target is not a Grav instance */
const NOT_GRAV_ROOT = 16;
/** @const Error while trying to open the ZIP package */
const ZIP_OPEN_ERROR = 32;
/** @const Error while trying to extract the ZIP package */
const ZIP_EXTRACT_ERROR = 64;
/**
* Destination folder on which validation checks are applied
* @var string
*/
protected static $target;
/**
* Error Code
* @var integer
*/
protected static $error = 0;
/**
* Default options for the install
* @var array
*/
protected static $options = [
'overwrite' => true,
'ignore_symlinks' => true,
'sophisticated' => false,
'install_path' => '',
'exclude_checks' => [self::EXISTS, self::NOT_FOUND, self::IS_LINK]
];
/**
* Installs a given package to a given destination.
*
* @param string $package The local path to the ZIP package
* @param string $destination The local path to the Grav Instance
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
*
* @return boolean True if everything went fine, False otherwise.
*/
public static function install($package, $destination, $options = [])
{
$destination = rtrim($destination, DS);
$options = array_merge(self::$options, $options);
$install_path = rtrim($destination . DS . ltrim($options['install_path'], DS), DS);
if (!self::isGravInstance($destination) || !self::isValidDestination($install_path, $options['exclude_checks'])
) {
return false;
}
if (
self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] ||
self::lastErrorCode() == self::EXISTS && !$options['overwrite']
) {
return false;
}
$zip = new \ZipArchive();
$archive = $zip->open($package);
$tmp = CACHE_DIR . DS . 'tmp/Grav-' . uniqid();
if ($archive !== true) {
self::$error = self::ZIP_OPEN_ERROR;
return false;
}
Folder::mkdir($tmp);
$unzip = $zip->extractTo($tmp);
if (!$unzip) {
self::$error = self::ZIP_EXTRACT_ERROR;
$zip->close();
Folder::delete($tmp);
return false;
}
if (!$options['sophisticated']) {
self::nonSophisticatedInstall($zip, $install_path, $tmp);
} else {
self::sophisticatedInstall($zip, $install_path, $tmp);
}
Folder::delete($tmp);
$zip->close();
self::$error = self::OK;
return true;
}
public static function nonSophisticatedInstall($zip, $install_path, $tmp)
{
$container = $zip->getNameIndex(0); // TODO: better way of determining if zip has container folder
if (file_exists($install_path)) {
Folder::delete($install_path);
}
Folder::move($tmp . DS . $container, $install_path);
return true;
}
public static function sophisticatedInstall($zip, $install_path, $tmp)
{
for ($i = 0, $l = $zip->numFiles; $i < $l; $i++) {
$filename = $zip->getNameIndex($i);
$fileinfo = pathinfo($filename);
$depth = count(explode(DS, rtrim($filename, '/')));
if ($depth > 2) {
continue;
}
$path = $install_path . DS . $fileinfo['basename'];
if (is_link($path)) {
continue;
} else {
if (is_dir($path)) {
Folder::delete($path);
Folder::move($tmp . DS . $filename, $path);
if ($fileinfo['basename'] == 'bin') {
foreach (glob($path . DS . '*') as $file) {
@chmod($file, 0755);
}
}
} else {
@unlink($path);
@copy($tmp . DS . $filename, $path);
}
}
}
return true;
}
/**
* Unnstalls one or more given package
*
* @param string $package The slug of the package(s)
* @param array $options Options to use for uninstalling
*
* @return boolean True if everything went fine, False otherwise.
*/
public static function uninstall($path, $options = [])
{
$options = array_merge(self::$options, $options);
if (!self::isValidDestination($path, $options['exclude_checks'])
) {
return false;
}
return Folder::delete($path);
}
/**
* Runs a set of checks on the destination and sets the Error if any
*
* @param string $destination The directory to run validations at
* @param array $exclude An array of constants to exclude from the validation
*
* @return boolean True if validation passed. False otherwise
*/
public static function isValidDestination($destination, $exclude = [])
{
self::$error = 0;
self::$target = $destination;
if (is_link($destination)) {
self::$error = self::IS_LINK;
} elseif (file_exists($destination)) {
self::$error = self::EXISTS;
} elseif (!file_exists($destination)) {
self::$error = self::NOT_FOUND;
} elseif (!is_dir($destination)) {
self::$error = self::NOT_DIRECTORY;
}
if (count($exclude) && in_array(self::$error, $exclude)) {
return true;
}
return !(self::$error);
}
/**
* Validates if the given path is a Grav Instance
*
* @param string $target The local path to the Grav Instance
*
* @return boolean True if is a Grav Instance. False otherwise
*/
public static function isGravInstance($target)
{
self::$error = 0;
self::$target = $target;
if (
!file_exists($target . DS . 'index.php') ||
!file_exists($target . DS . 'bin') ||
!file_exists($target . DS . 'user') ||
!file_exists($target . DS . 'system' . DS . 'config' . DS . 'system.yaml')
) {
self::$error = self::NOT_GRAV_ROOT;
}
return !self::$error;
}
/**
* Returns the last error occurred in a string message format
* @return string The message of the last error
*/
public static function lastErrorMsg()
{
$msg = 'Unknown Error';
switch (self::$error) {
case 0:
$msg = 'No Error';
break;
case self::EXISTS:
$msg = 'The target path "' . self::$target . '" already exists';
break;
case self::IS_LINK:
$msg = 'The target path "' . self::$target . '" is a symbolic link';
break;
case self::NOT_FOUND:
$msg = 'The target path "' . self::$target . '" does not appear to exist';
break;
case self::NOT_DIRECTORY:
$msg = 'The target path "' . self::$target . '" does not appear to be a folder';
break;
case self::NOT_GRAV_ROOT:
$msg = 'The target path "' . self::$target . '" does not appear to be a Grav instance';
break;
case self::ZIP_OPEN_ERROR:
$msg = 'Unable to open the package file';
break;
case self::ZIP_EXTRACT_ERROR:
$msg = 'An error occurred while extracting the package';
break;
default:
return 'Unknown error';
break;
}
return $msg;
}
/**
* Returns the last error code of the occurred error
* @return integer The code of the last error
*/
public static function lastErrorCode()
{
return self::$error;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Grav\Common\GPM\Local;
use Grav\Common\GravTrait;
use Grav\Common\Iterator;
class Collection extends Iterator
{
use GravTrait;
public function toJson()
{
$items = [];
foreach ($this->items as $name => $theme) {
$items[$name] = $theme->toArray();
}
return json_encode($items);
}
public function toArray()
{
$items = [];
foreach ($this->items as $name => $theme) {
$items[$name] = $theme->toArray();
}
return $items;
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Grav\Common\GPM\Local;
use Grav\Common\Data\Data;
/**
* Class Package
* @package Grav\Common\GPM\Local
*/
class Package
{
/**
* @var Data
*/
protected $data;
/**
* @var \Grav\Common\Data\Blueprint
*/
protected $blueprints;
/**
* @param Data $package
* @param bool $package_type
*/
public function __construct(Data $package, $package_type = false)
{
$this->data = $package;
$this->blueprints = $this->data->blueprints();
if ($package_type) {
$html_description = \Parsedown::instance()->line($this->blueprints->get('description'));
$this->blueprints->set('package_type', $package_type);
$this->blueprints->set('slug', $this->blueprints->name);
$this->blueprints->set('description_html', $html_description);
$this->blueprints->set('description_plain', strip_tags($html_description));
$this->blueprints->set('symlink', is_link(USER_DIR . $package_type . DS . $this->blueprints->name));
}
}
/**
* @return mixed
*/
public function isEnabled()
{
return $this->data['enabled'];
}
/**
* @return Data
*/
public function getData()
{
return $this->data;
}
/**
* @param $key
* @return mixed
*/
public function __get($key)
{
return $this->blueprints->get($key);
}
/**
* @return string
*/
public function __toString()
{
return $this->toJson();
}
/**
* @return string
*/
public function toJson()
{
return $this->blueprints->toJson();
}
/**
* @return array
*/
public function toArray()
{
return $this->blueprints->toArray();
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Grav\Common\GPM\Local;
use Grav\Common\Iterator;
class Packages extends Iterator
{
private $plugins;
private $themes;
protected static $cache;
public function __construct()
{
// local cache to speed things up
if (!isset(self::$cache[__METHOD__])) {
self::$cache[__METHOD__] = [
'plugins' => new Plugins(),
'themes' => new Themes()
];
}
$this->plugins = self::$cache[__METHOD__]['plugins'];
$this->themes = self::$cache[__METHOD__]['themes'];
$this->append(['plugins' => $this->plugins]);
$this->append(['themes' => $this->themes]);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Grav\Common\GPM\Local;
/**
* Class Plugins
* @package Grav\Common\GPM\Local
*/
class Plugins extends Collection
{
/**
* @var string
*/
private $type = 'plugins';
/**
* Local Plugins Constructor
*/
public function __construct()
{
$grav = self::$grav;
foreach ($grav['plugins']->all() as $name => $data) {
$this->items[$name] = new Package($data, $this->type);
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Grav\Common\GPM\Local;
class Themes extends Collection
{
private $type = 'themes';
public function __construct()
{
$grav = self::$grav;
foreach ($grav['themes']->all() as $name => $data) {
$this->items[$name] = new Package($data, $this->type);
}
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Grav\Common\GPM\Remote;
use Grav\Common\GPM\Response;
use Grav\Common\GravTrait;
use Grav\Common\Iterator;
use \Doctrine\Common\Cache\Cache as DoctrineCache;
use \Doctrine\Common\Cache\FilesystemCache;
class Collection extends Iterator {
use GravTrait;
/**
* The cached data previously fetched
* @var string
*/
protected $raw;
/**
* The lifetime to store the entry in seconds
* @var integer
*/
private $lifetime = 86400;
private $repository;
private $cache;
private $plugins, $themes;
public function __construct($repository = null) {
if ($repository == null) {
throw new \RuntimeException("A repository is required for storing the cache");
}
$cache_dir = self::$grav['locator']->findResource('cache://gpm', true, true);
$this->cache = new FilesystemCache($cache_dir);
$this->repository = $repository;
$this->raw = $this->cache->fetch(md5($this->repository));
}
public function toJson() {
$items = [];
foreach ($this->items as $name => $theme) {
$items[$name] = $theme->toArray();
}
return json_encode($items);
}
public function toArray() {
$items = [];
foreach ($this->items as $name => $theme) {
$items[$name] = $theme->toArray();
}
return $items;
}
public function fetch($refresh = false, $callback = null) {
if (!$this->raw || $refresh) {
$response = Response::get($this->repository, [], $callback);
$this->raw = $response;
$this->cache->save(md5($this->repository), $this->raw, $this->lifetime);
}
return $this->raw;
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Grav\Common\GPM\Remote;
class Grav extends Collection
{
private $repository = 'http://getgrav.org/downloads/grav.json';
private $data;
private $version;
private $date;
/**
* @param bool $refresh
* @param null $callback
*/
public function __construct($refresh = false, $callback = null)
{
parent::__construct($this->repository);
$this->fetch($refresh, $callback);
$this->data = json_decode($this->raw);
$this->version = @$this->data->version ?: '-';
$this->date = @$this->data->date ?: '-';
foreach ($this->data->assets as $slug => $data) {
$this->items[$slug] = new Package($data);
}
}
/**
* Returns the list of assets associated to the latest version of Grav
* @return array list of assets
*/
public function getAssets()
{
return $this->data->assets;
}
/**
* Returns the changelog list for each version of Grav
* @param string $diff the version number to start the diff from
*
* @return array changelog list for each version
*/
public function getChangelog($diff = null)
{
if (!$diff) {
return $this->data->changelog;
}
$diffLog = [];
foreach ($this->data->changelog as $version => $changelog) {
preg_match("/[\d\.]+/", $version, $cleanVersion);
if (!$cleanVersion || version_compare($diff, $cleanVersion[0], ">=")) { continue; }
$diffLog[$version] = $changelog;
}
return $diffLog;
}
/**
* Returns the latest version of Grav available remotely
* @return string
*/
public function getVersion()
{
return $this->version;
}
/**
* Return the release date of the latest Grav
* @return string
*/
public function getDate()
{
return $this->date;
}
public function isUpdatable()
{
return version_compare(GRAV_VERSION, $this->getVersion(), '<');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Grav\Common\GPM\Remote;
class Package {
public function __construct($package, $package_type = false) {
$this->data = $package;
if ($package_type) {
$this->data->package_type = $package_type;
}
}
public function getData() {
return $this->data;
}
public function __get($key) {
return $this->data->$key;
}
public function __toString() {
return $this->toJson();
}
public function toJson() {
return json_encode($this->data);
}
public function toArray() {
return $this->data;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Grav\Common\GPM\Remote;
use Grav\Common\Iterator;
class Packages extends Iterator
{
private $plugins;
private $themes;
protected static $cache;
public function __construct($refresh = false, $callback = null)
{
// local cache to speed things up
if (!isset(self::$cache[__METHOD__])) {
self::$cache[__METHOD__] = [
'plugins' => new Plugins($refresh, $callback),
'themes' => new Themes($refresh, $callback)
];
}
$this->plugins = self::$cache[__METHOD__]['plugins']->toArray();
$this->themes = self::$cache[__METHOD__]['themes']->toArray();
$this->append(['plugins' => $this->plugins]);
$this->append(['themes' => $this->themes]);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Grav\Common\GPM\Remote;
class Plugins extends Collection
{
private $repository = 'http://getgrav.org/downloads/plugins.json';
private $type = 'plugins';
private $data;
public function __construct($refresh = false, $callback = null)
{
parent::__construct($this->repository);
$this->fetch($refresh, $callback);
$this->data = json_decode($this->raw);
foreach ($this->data as $slug => $data) {
$this->items[$slug] = new Package($data, $this->type);
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Grav\Common\GPM\Remote;
class Themes extends Collection
{
private $repository = 'http://getgrav.org/downloads/themes.json';
private $type = 'themes';
private $data;
public function __construct($refresh = false, $callback = null)
{
parent::__construct($this->repository);
$this->fetch($refresh, $callback);
$this->data = json_decode($this->raw);
foreach ($this->data as $slug => $data) {
$this->items[$slug] = new Package($data, $this->type);
}
}
}

View File

@@ -0,0 +1,220 @@
<?php
namespace Grav\Common\GPM;
class Response
{
/**
* The callback for the progress
* @var callable Either a function or callback in array notation
*/
public static $callback = null;
/**
* Which method to use for HTTP calls, can be 'curl', 'fopen' or 'auto'. Auto is default and fopen is the preferred method
* @var string
*/
private static $method = 'auto';
/**
* Default parameters for `curl` and `fopen`
* @var array
*/
private static $defaults = [
'curl' => [
CURLOPT_REFERER => 'Grav GPM',
CURLOPT_USERAGENT => 'Grav GPM',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HEADER => false,
/**
* Example of callback parameters from within your own class
*/
//CURLOPT_NOPROGRESS => false,
//CURLOPT_PROGRESSFUNCTION => [$this, 'progress']
],
'fopen' => [
'method' => 'GET',
'user_agent' => 'Grav GPM',
'max_redirects' => 5,
'follow_location' => 1,
'timeout' => 15,
/**
* Example of callback parameters from within your own class
*/
//'notification' => [$this, 'progress']
]
];
/**
* Sets the preferred method to use for making HTTP calls.
* @param string $method Default is `auto`
*/
public static function setMethod($method = 'auto')
{
if (!in_array($method, ['auto', 'curl', 'fopen'])) {
$method = 'auto';
}
self::$method = $method;
return new self();
}
/**
* Makes a request to the URL by using the preferred method
* @param string $uri URL to call
* @param array $options An array of parameters for both `curl` and `fopen`
* @return string The response of the request
*/
public static function get($uri = '', $options = [], $callback = null)
{
if (!self::isCurlAvailable() && !self::isFopenAvailable()) {
throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available');
}
$options = array_replace_recursive(self::$defaults, $options);
$method = 'get' . ucfirst(strtolower(self::$method));
self::$callback = $callback;
return static::$method($uri, $options, $callback);
}
/**
* Progress normalized for cURL and Fopen
* @param args Variable length of arguments passed in by stream method
* @return array Normalized array with useful data.
* Format: ['code' => int|false, 'filesize' => bytes, 'transferred' => bytes, 'percent' => int]
*/
public static function progress()
{
static $filesize = null;
$args = func_get_args();
$isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) == 'curl';
$notification_code = !$isCurlResource ? $args[0] : false;
$bytes_transferred = $isCurlResource ? $args[2] : $args[4];
if ($isCurlResource) {
$filesize = $args[1];
} elseif ($notification_code == STREAM_NOTIFY_FILE_SIZE_IS) {
$filesize = $args[5];
}
if ($bytes_transferred > 0) {
if ($notification_code == STREAM_NOTIFY_PROGRESS|STREAM_NOTIFY_COMPLETED || $isCurlResource) {
$progress = [
'code' => $notification_code,
'filesize' => $filesize,
'transferred' => $bytes_transferred,
'percent' => $filesize <= 0 ? '-' : round(($bytes_transferred * 100) / $filesize, 1)
];
if (self::$callback !== null) {
call_user_func_array(self::$callback, [$progress]);
}
}
}
}
/**
* Checks if cURL is available
* @return boolean
*/
public static function isCurlAvailable()
{
return function_exists('curl_version');
}
/**
* Checks if the remote fopen request is enabled in PHP
* @return boolean
*/
public static function isFopenAvailable()
{
return preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
}
/**
* Automatically picks the preferred method
* @return string The response of the request
*/
private static function getAuto()
{
if (self::isFopenAvailable()) {
return self::getFopen(func_get_args());
}
if (self::isCurlAvailable()) {
return self::getCurl(func_get_args());
}
}
/**
* Starts a HTTP request via cURL
* @return string The response of the request
*/
private static function getCurl()
{
$args = func_get_args();
$uri = $args[0];
$options = $args[1];
$callback = $args[2];
$ch = curl_init($uri);
curl_setopt_array($ch, $options['curl']);
if ($callback) {
curl_setopt_array(
$ch,
[
CURLOPT_NOPROGRESS => false,
CURLOPT_PROGRESSFUNCTION => ['self', 'progress']
]
);
}
$response = curl_exec($ch);
if ($errno = curl_errno($ch)) {
$error_message = curl_strerror($errno);
throw new \RuntimeException("cURL error ({$errno}):\n {$error_message}");
}
curl_close($ch);
return $response;
}
/**
* Starts a HTTP request via fopen
* @return string The response of the request
*/
private static function getFopen()
{
if (count($args = func_get_args()) == 1) {
$args = $args[0];
}
$uri = $args[0];
$options = $args[1];
$callback = $args[2];
if ($callback) {
$options['fopen']['notification'] = ['self', 'progress'];
}
$stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
$content = @file_get_contents($uri, false, $stream);
if ($content === false) {
throw new \RuntimeException("Error while trying to download '$uri'");
}
return $content;
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Grav\Common\GPM;
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\Installer;
class Upgrader
{
/**
* Remote details about latest Grav version
* @var Packages
*/
private $remote;
/**
* Internal cache
* @var Iterator
*/
protected $cache;
/**
* Creates a new GPM instance with Local and Remote packages available
* @param boolean $refresh Applies to Remote Packages only and forces a refetch of data
* @param callable $callback Either a function or callback in array notation
*/
public function __construct($refresh = false, $callback = null)
{
$this->remote = new Remote\Grav($refresh, $callback);
}
/**
* Returns the release date of the latest version of Grav
* @return string
*/
public function getReleaseDate()
{
return $this->remote->getDate();
}
/**
* Returns the version of the installed Grav
* @return string
*/
public function getLocalVersion()
{
return GRAV_VERSION;
}
/**
* Returns the version of the remotely available Grav
* @return string
*/
public function getRemoteVersion()
{
return $this->remote->getVersion();
}
/**
* Returns an array of assets available to download remotely
* @return array
*/
public function getAssets()
{
return $this->remote->getAssets();
}
/**
* 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
*/
public function getChangelog($diff = null)
{
return $this->remote->getChangelog($diff);
}
/**
* Checks if the currently installed Grav is upgradable to a newer version
* @return boolean True if it's upgradable, False otherwise.
*/
public function isUpgradable()
{
return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), "<");
}
}

View File

@@ -36,7 +36,7 @@ abstract class Getters implements \ArrayAccess, \Countable
*/
public function __get($offset)
{
return $this->offsetGet($offset);
return $this->offsetGet($offset);
}
/**

View File

@@ -1,10 +1,16 @@
<?php
namespace Grav\Common;
use \Tracy\Debugger;
use \Grav\Common\Page\Page;
use \Grav\Common\Page\Pages;
use Grav\Common\Filesystem\Folder;
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 RocketTheme\Toolbox\DI\Container;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
use Grav\Common\Page\Medium;
/**
* Grav
@@ -12,126 +18,221 @@ use \Grav\Common\Page\Pages;
* @author Andy Miller
* @link http://www.rockettheme.com
* @license http://opensource.org/licenses/MIT
* @version 0.1
*
* Originally based on Pico by Gilbert Pellegrom - http://pico.dev7studios.com
* Influeced by Pico, Stacey, Kirby, PieCrust and other great platforms...
*
* @property Plugins $plugins
* @property Config $config
* @property Cache $cache
* @property Uri $uri
* @property Pages $pages
* @property Page $page
* Influenced by Pico, Stacey, Kirby, PieCrust and other great platforms...
*/
class Grav extends Getters
class Grav extends Container
{
/**
* @var string Grav output.
* @var string
*/
protected $output;
public $output;
/**
* @var array
* @var static
*/
protected $plugins;
protected static $instance;
/**
* @var Config
*/
protected $config;
public static function instance(array $values = array())
{
if (!self::$instance) {
self::$instance = static::load($values);
/**
* @var Cache
*/
protected $cache;
GravTrait::setGrav(self::$instance);
/**
* @var Uri
*/
protected $uri;
} elseif ($values) {
$instance = self::$instance;
foreach ($values as $key => $value) {
$instance->offsetSet($key, $value);
}
}
/**
* @var Pages
*/
protected $pages;
return self::$instance;
}
/**
* @var Page
*/
protected $page;
protected static function load(array $values)
{
$container = new static($values);
/**
* @var Twig
*/
protected $twig;
$container['grav'] = $container;
/**
* @var Taxonomy
*/
protected $taxonomy;
$container['debugger'] = new Debugger();
$container['debugger']->startTimer('_init', 'Initialize');
$container->register(new LoggerServiceProvider);
$container->register(new ErrorServiceProvider);
$container['uri'] = function ($c) {
return new Uri($c);
};
$container['task'] = function ($c) {
return !empty($_POST['task']) ? $_POST['task'] : $c['uri']->param('task');
};
$container['events'] = function ($c) {
return new EventDispatcher;
};
$container['cache'] = function ($c) {
return new Cache($c);
};
$container['plugins'] = function ($c) {
return new Plugins($c);
};
$container['themes'] = function ($c) {
return new Themes($c);
};
$container['twig'] = function ($c) {
return new Twig($c);
};
$container['taxonomy'] = function ($c) {
return new Taxonomy($c);
};
$container['pages'] = function ($c) {
return new Page\Pages($c);
};
$container['assets'] = function ($c) {
return 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()), '/');
$page = $pages->dispatch($path);
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;
}
}
// If no page found, fire event
$event = $c->fireEvent('onPageNotFound');
if (isset($event->page)) {
$page = $event->page;
} else {
throw new \RuntimeException('Page Not Found', 404);
}
}
return $page;
};
$container['output'] = function ($c) {
return $c['twig']->processSite($c['uri']->extension());
};
$container['browser'] = function ($c) {
return new Browser();
};
$container['base_url_absolute'] = function ($c) {
return $c['config']->get('system.base_url_absolute') ?: $c['uri']->rootUrl(true);
};
$container['base_url_relative'] = function ($c) {
return $c['config']->get('system.base_url_relative') ?: $c['uri']->rootUrl(false);
};
$container['base_url'] = function ($c) {
return $c['config']->get('system.absolute_urls') ? $c['base_url_absolute'] : $c['base_url_relative'];
};
$container->register(new StreamsServiceProvider);
$container->register(new ConfigServiceProvider);
$container['debugger']->stopTimer('_init');
return $container;
}
public function process()
{
// Get the URI and URL (needed for configuration)
$this->uri = Registry::get('Uri');
// Get the Configuration settings and caching
$this->config = Registry::get('Config');
Debugger::$logDirectory = $this->config->get('system.debugger.log.enabled') ? LOG_DIR : null;
Debugger::$maxDepth = $this->config->get('system.debugger.max_depth');
// Switch debugger into development mode if configured
if ($this->config->get('system.debugger.enabled')) {
if (function_exists('ini_set')) {
ini_set('display_errors', true);
}
Debugger::$productionMode = Debugger::DEVELOPMENT;
// Use output buffering to prevent headers from being sent too early.
ob_start();
if ($this['config']->get('system.cache.gzip')) {
ob_start('ob_gzhandler');
}
// Get the Caching setup
$this->cache = Registry::get('Cache');
$this->cache->init();
// Get Plugins
$plugins = new Plugins();
$this->plugins = $plugins->load();
$this->fireEvent('onAfterInitPlugins');
/** @var Debugger $debugger */
$debugger = $this['debugger'];
// Get current theme and hook it into plugins.
$themes = new Themes();
$this->plugins['Theme'] = $themes->load();
// Initialize configuration.
$debugger->startTimer('_config', 'Configuration');
$this['config']->init();
$this['errors']->resetHandlers();
$debugger->init();
$this['config']->debug();
$debugger->stopTimer('_config');
// Get twig object
$this->twig = Registry::get('Twig');
$this->twig->init();
$debugger->startTimer('streams', 'Streams');
$this['streams'];
$debugger->stopTimer('streams');
// Get all the Pages that Grav knows about
$this->pages = Registry::get('Pages');
$this->pages->init();
$this->fireEvent('onAfterGetPages');
$debugger->startTimer('plugins', 'Plugins');
$this['plugins']->init();
$this->fireEvent('onPluginsInitialized');
$debugger->stopTimer('plugins');
// Get the taxonomy and set it on the grav object
$this->taxonomy = Registry::get('Taxonomy');
$debugger->startTimer('themes', 'Themes');
$this['themes']->init();
$this->fireEvent('onThemeInitialized');
$debugger->stopTimer('themes');
// Get current page
$this->page = $this->pages->dispatch($this->uri->route());
$this->fireEvent('onAfterGetPage');
// If there's no page, throw exception
if (!$this->page) {
throw new \RuntimeException('Page Not Found', 404);
$task = $this['task'];
if ($task) {
$this->fireEvent('onTask.' . $task);
}
$this['assets']->init();
$this->fireEvent('onAssetsInitialized');
$debugger->startTimer('twig', 'Twig');
$this['twig']->init();
$debugger->stopTimer('twig');
$debugger->startTimer('pages', 'Pages');
$this['pages']->init();
$this->fireEvent('onPagesInitialized');
$debugger->stopTimer('pages');
$this->fireEvent('onPageInitialized');
$debugger->addAssets();
// Process whole page as required
$this->output = $this->twig->processSite($this->uri->extension());
$this->fireEvent('onAfterGetOutput');
$debugger->startTimer('render', 'Render');
$this->output = $this['output'];
$this->fireEvent('onOutputGenerated');
$debugger->stopTimer('render');
// Set the header type
$this->header();
echo $this->output;
$debugger->render();
$this->fireEvent('onOutputRendered');
register_shutdown_function([$this, 'shutdown']);
}
/**
@@ -142,7 +243,14 @@ class Grav extends Getters
*/
public function redirect($route, $code = 303)
{
header("Location: " . rtrim($this->uri->rootUrl(), '/') .'/'. trim($route, '/'), true, $code);
/** @var Uri $uri */
$uri = $this['uri'];
if (isset($this['session'])) {
$this['session']->close();
}
header("Location: " . rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/'), true, $code);
exit();
}
@@ -174,40 +282,68 @@ class Grav extends Getters
*/
public function header()
{
header('Content-type: ' . $this->mime($this->uri->extension()));
$extension = $this['uri']->extension();
header('Content-type: ' . $this->mime($extension));
// Set debugger data in headers
if (!($extension == null || $extension == 'html')) {
$this['debugger']->enabled(false);
// $this['debugger']->sendDataInHeaders();
}
// Set HTTP response code
if (isset($this['page']->header()->http_response_code)) {
http_response_code($this['page']->header()->http_response_code);
}
}
/**
* Log a message.
* Fires an event with optional parameters.
*
* @param string $message
* @param string $eventName
* @param Event $event
* @return Event
*/
protected static function log($message)
public function fireEvent($eventName, Event $event = null)
{
if (Debugger::$logDirectory) {
Debugger::log(sprintf($message, Debugger::timer() * 1000));
}
/** @var EventDispatcher $events */
$events = $this['events'];
return $events->dispatch($eventName, $event);
}
/**
* Processes any hooks and runs them.
* Set the final content length for the page and flush the buffer
*
*/
public function fireEvent()
public function shutdown()
{
$args = func_get_args();
$hook_id = array_shift($args);
$no_timing_hooks = array('onAfterPageProcessed','onAfterFolderProcessed', 'onAfterCollectionProcessed');
if ($this['config']->get('system.debugger.shutdown.close_connection')) {
if (!empty($this->plugins)) {
foreach ($this->plugins as $plugin) {
if (is_callable(array($plugin, $hook_id))) {
call_user_func_array(array($plugin, $hook_id), $args);
}
if (function_exists('ignore_user_abort')) {
@ignore_user_abort(true);
}
if (isset($this['session'])) {
$this['session']->close();
}
if ($this['config']->get('system.cache.gzip')) {
ob_end_flush(); // gzhandler buffer
}
header('Content-Length: ' . ob_get_length());
header("Connection: close\r\n");
ob_end_flush(); // regular buffer
ob_flush();
flush();
if (function_exists('fastcgi_finish_request')) {
@fastcgi_finish_request();
}
}
if ($this->config && $this->config->get('system.debugger.log.timing') && !in_array($hook_id, $no_timing_hooks)) {
static::log($hook_id.': %f ms');
}
$this->fireEvent('onShutdown');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Grav\Common;
trait GravTrait
{
/**
* @var Grav
*/
protected static $grav;
/**
* @return Grav
*/
public function getGrav()
{
return self::$grav;
}
/**
* @param Grav $grav
*/
public static function setGrav(Grav $grav)
{
self::$grav = $grav;
}
}

View File

@@ -90,9 +90,8 @@ class Inflector
/**
* Singularizes English nouns.
*
* @access static public
* @static
* @param string $word English noun to singularize
* @param int $count
* @return string Singular noun.
*/
public static function singularize($word, $count = 1)
@@ -353,10 +352,10 @@ class Inflector
public static function monthize($days)
{
$now = new JDate();
$end = new JDate();
$now = new \DateTime();
$end = new \DateTime();
$duration = new DateInterval("P{$days}D");
$duration = new \DateInterval("P{$days}D");
$diff = $end->add($duration)->diff($now);

View File

@@ -1,7 +1,12 @@
<?php
namespace Grav\Common;
use Symfony\Component\Yaml\Yaml;
use RocketTheme\Toolbox\ArrayTraits\ArrayAccessWithGetters;
use RocketTheme\Toolbox\ArrayTraits\Iterator as ArrayIterator;
use RocketTheme\Toolbox\ArrayTraits\Constructor;
use RocketTheme\Toolbox\ArrayTraits\Countable;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\Serializable;
/**
* Class Iterator
@@ -9,25 +14,12 @@ use Symfony\Component\Yaml\Yaml;
*/
class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
{
use Constructor, ArrayAccessWithGetters, ArrayIterator, Countable, Serializable, Export;
/**
* @var array
*/
protected $items = array();
/**
* @var bool
*/
protected $unset = false;
/**
* Constructor.
*
* @param array $items Initial items inside the iterator.
*/
public function __construct(array $items = array())
{
$this->items = $items;
}
protected $items = [];
/**
* Convert function calls for the existing keys into their values.
@@ -41,49 +33,6 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
return (isset($this->items[$key])) ? $this->items[$key] : null;
}
/**
* Array getter shorthand to get items.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return (isset($this->items[$key])) ? $this->items[$key] : null;
}
/**
* Array setter shorthand to set the value.
*
* @param string $key
* @param mixed $value
*/
public function __set($key, $value)
{
$this->items[$key] = $value;
}
/**
* Array isset shorthand to set the value.
*
* @param string $key
* @return bool
*/
public function __isset($key)
{
return isset($this->items[$key]);
}
/**
* Array unset shorthand to remove the key.
*
* @param string $key
*/
public function __unset($key)
{
$this->offsetUnset($key);
}
/**
* Clone the iterator.
*/
@@ -164,7 +113,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
shuffle($keys);
$new = array();
foreach($keys as $key) {
foreach ($keys as $key) {
$new[$key] = $this->items[$key];
}
@@ -215,180 +164,4 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
return $this;
}
// Implements export functions to array, YAML and JSON.
/**
* Return items as an array.
*
* @return array Array presentation of the iterator.
*/
public function toArray()
{
return $this->items;
}
/**
* Return YAML encoded string of items.
*
* @return string YAML presentation of the iterator.
*/
public function toYaml()
{
return Yaml::dump($this->items);
}
/**
* Return JSON encoded string of items.
*
* @return string JSON presentation of the iterator.
*/
public function toJson()
{
return json_encode($this->items);
}
// Implements Iterator.
/**
* Returns the current element.
*
* @return mixed Can return any type.
*/
public function current()
{
return current($this->items);
}
/**
* Returns the key of the current element.
*
* @return mixed Returns scalar on success, or NULL on failure.
*/
public function key()
{
return key($this->items);
}
/**
* Moves the current position to the next element.
*
* @return void
*/
public function next()
{
if ($this->unset) {
// If current item was unset, position is already in the next element (do nothing).
$this->unset = false;
} else {
next($this->items);
}
}
/**
* Rewinds back to the first element of the Iterator.
*
* @return void
*/
public function rewind()
{
$this->unset = false;
reset($this->items);
}
/**
* This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid.
*
* @return bool Returns TRUE on success or FALSE on failure.
*/
public function valid()
{
return key($this->items) !== null;
}
// Implements ArrayAccess
/**
* Whether or not an offset exists.
*
* @param mixed $offset An offset to check for.
* @return bool Returns TRUE on success or FALSE on failure.
*/
public function offsetExists($offset)
{
return isset($this->items[$offset]);
}
/**
* Returns the value at specified offset.
*
* @param mixed $offset The offset to retrieve.
* @return mixed Can return all value types.
*/
public function offsetGet($offset)
{
return isset($this->items[$offset]) ? $this->items[$offset] : null;
}
/**
* Assigns a value to the specified offset.
*
* @param mixed $offset The offset to assign the value to.
* @param mixed $value The value to set.
*/
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->items[] = $value;
} else {
$this->items[$offset] = $value;
}
}
/**
* Unsets an offset.
*
* @param mixed $offset The offset to unset.
*/
public function offsetUnset($offset)
{
if ($offset == key($this->items)) {
$this->unset = true;
}
unset($this->items[$offset]);
}
// Implements Countable
/**
* This method is executed when using the count() function.
*
* @return int The count of items.
*/
public function count()
{
return count($this->items);
}
// Implements Serializable
/**
* Returns string representation of the object.
*
* @return string Returns the string representation of the object.
*/
public function serialize()
{
return serialize($this->items);
}
/**
* Called during unserialization of the object.
*
* @param string $serialized The string representation of the object.
*/
public function unserialize($serialized)
{
$this->items = unserialize($serialized);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Grav\Common\Markdown;
class Parsedown extends \Parsedown
{
use ParsedownGravTrait;
public function __construct($page)
{
$this->init($page);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Grav\Common\Markdown;
class ParsedownExtra extends \ParsedownExtra
{
use ParsedownGravTrait;
public function __construct($page)
{
parent::__construct();
$this->init($page);
}
}

View File

@@ -0,0 +1,275 @@
<?php
namespace Grav\Common\Markdown;
use Grav\Common\Config\Config;
use Grav\Common\Debugger;
use Grav\Common\GravTrait;
use Grav\Common\Page\Medium;
use Grav\Common\Uri;
/**
* A trait to add some custom processing to the identifyLink() method in Parsedown and ParsedownExtra
*/
trait ParsedownGravTrait
{
use GravTrait;
protected $page;
protected $pages;
protected $base_url;
protected $pages_dir;
protected $special_chars;
protected $twig_link_regex = '/\!*\[(?:.*)\]\(([{{|{%|{#].*[#}|%}|}}])\)/';
/**
* Initialiazation function to setup key variables needed by the MarkdownGravLinkTrait
*
* @param $page
*/
protected function init($page)
{
$this->page = $page;
$this->pages = self::$grav['pages'];
$this->BlockTypes['{'] [] = "TwigTag";
$this->base_url = rtrim(self::$grav['base_url'] . self::$grav['pages']->base(), '/');
$this->pages_dir = self::$grav['locator']->findResource('page://');
$this->special_chars = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
}
/**
* Setter for special chars
*
* @param $special_chars
*
* @return $this
*/
function setSpecialChars($special_chars)
{
$this->special_chars = $special_chars;
return $this;
}
/**
* Ensure Twig tags are treated as block level items with no <p></p> tags
*/
protected function blockTwigTag($Line)
{
if (preg_match('/[{%|{{|{#].*[#}|}}|%}]/', $Line['body'], $matches)) {
$Block = array(
'markup' => $Line['body'],
);
return $Block;
}
}
protected function inlineSpecialCharacter($Excerpt)
{
if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) {
return array(
'markup' => '&amp;',
'extent' => 1,
);
}
if (isset($this->special_chars[$Excerpt['text'][0]])) {
return array(
'markup' => '&'.$this->special_chars[$Excerpt['text'][0]].';',
'extent' => 1,
);
}
}
protected function inlineImage($excerpt)
{
if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
$excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']);
$excerpt = parent::inlineImage($excerpt);
$excerpt['element']['attributes']['src'] = $matches[1];
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
return $excerpt;
} else {
$excerpt = parent::inlineImage($excerpt);
}
// Some stuff we will need
$actions = array();
$media = null;
// if this is an image
if (isset($excerpt['element']['attributes']['src'])) {
$alt = $excerpt['element']['attributes']['alt'] ?: '';
$title = $excerpt['element']['attributes']['title'] ?: '';
//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'])) {
// 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']), '/');
// 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']);
$ext_page = $this->pages->dispatch($page_route, true);
if ($ext_page) {
$media = $ext_page->media();
$url['path'] = $path_parts['basename'];
}
}
// if there is a media file that matches the path referenced..
if ($media && isset($media->images()[$url['path']])) {
// get the medium object
$medium = $media->images()[$url['path']];
// if there is a query, then parse it and build action calls
if (isset($url['query'])) {
parse_str($url['query'], $actions);
}
// loop through actions for the image and call them
foreach ($actions as $action => $params) {
// as long as it's a valid action
if (in_array($action, Medium::$valid_actions)) {
call_user_func_array(array(&$medium, $action), explode(',', $params));
}
}
$data = $medium->htmlRaw();
// set the src element with the new generated url
if (!isset($actions['lightbox'])) {
$excerpt['element']['attributes']['src'] = $data['img_src'];
if ($data['img_srcset']) {
$excerpt['element']['attributes']['srcset'] = $data['img_srcset'];;
$excerpt['element']['attributes']['sizes'] = '100vw';
}
} else {
// Create the custom lightbox element
$attributes = $data['a_attributes'];
$attributes['href'] = $data['a_href'];
$img_attributes = [
'src' => $data['img_src'],
'alt' => $alt,
'title' => $title
];
if ($data['img_srcset']) {
$img_attributes['srcset'] = $data['img_srcset'];
$img_attributes['sizes'] = '100vw';
}
$element = array(
'name' => 'a',
'attributes' => $attributes,
'handler' => 'element',
'text' => array(
'name' => 'img',
'attributes' => $img_attributes
)
);
// Set any custom classes on the lightbox element
if (isset($excerpt['element']['attributes']['class'])) {
$element['attributes']['class'] = $excerpt['element']['attributes']['class'];
}
// Set the lightbox element on the Excerpt
$excerpt['element'] = $element;
}
} else {
// not a current page media file, see if it needs converting to relative
$excerpt['element']['attributes']['src'] = Uri::build_url($url);
}
}
}
return $excerpt;
}
protected function inlineLink($excerpt)
{
// do some trickery to get around Parsedown requirement for valid URL if its Twig in there
if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
$excerpt['text'] = str_replace($matches[1], '/', $excerpt['text']);
$excerpt = parent::inlineLink($excerpt);
$excerpt['element']['attributes']['href'] = $matches[1];
$excerpt['extent'] = $excerpt['extent'] + strlen($matches[1]) - 1;
return $excerpt;
} else {
$excerpt = parent::inlineLink($excerpt);
}
// if this is a link
if (isset($excerpt['element']['attributes']['href'])) {
$url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['href']));
// if there is no scheme, the file is local
if (!isset($url['scheme'])) {
// convert the URl is required
$excerpt['element']['attributes']['href'] = $this->convertUrl(Uri::build_url($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

@@ -1,7 +1,8 @@
<?php
namespace Grav\Common\Page;
use Grav\Common\Grav;
use Grav\Common\Iterator;
use Grav\Common\Registry;
/**
* Collection of Pages.
@@ -21,11 +22,12 @@ class Collection extends Iterator
*/
protected $params;
public function __construct($items = array(), array $params = array(), Pages $pages = null) {
public function __construct($items = array(), array $params = array(), Pages $pages = null)
{
parent::__construct($items);
$this->params = $params;
$this->pages = $pages ? $pages : Registry::get('Pages');
$this->pages = $pages ? $pages : Grav::instance()->offsetGet('pages');
}
public function params()
@@ -33,6 +35,17 @@ class Collection extends Iterator
return $this->params;
}
/**
*
* Create a copy of this collection
*
* @return static
*/
public function copy()
{
return new static($this->items, $this->params, $this->pages);
}
/**
* Set parameters to the Collection
*
@@ -114,9 +127,10 @@ class Collection extends Iterator
}
/**
* Check to see if this item is the first in the collection
* Check to see if this item is the first in the collection.
*
* @param string $path
* @return boolean True if item is first
* @return boolean True if item is first.
*/
public function isFirst($path)
{
@@ -128,9 +142,10 @@ class Collection extends Iterator
}
/**
* Check to see if this item is the last in the collection
* Check to see if this item is the last in the collection.
*
* @param string $path
* @return boolean True if item is last
* @return boolean True if item is last.
*/
public function isLast($path)
{
@@ -142,9 +157,10 @@ class Collection extends Iterator
}
/**
* Gets the previous sibling based on current position
* Gets the previous sibling based on current position.
*
* @return Object the previous item
* @param string $path
* @return Page The previous item.
*/
public function prevSibling($path)
{
@@ -152,9 +168,10 @@ class Collection extends Iterator
}
/**
* Gets the next sibling based on current position
* Gets the next sibling based on current position.
*
* @return Object the next item
* @param string $path
* @return Page The next item.
*/
public function nextSibling($path)
{
@@ -162,26 +179,214 @@ class Collection extends Iterator
}
/**
* Returns the adjacent sibling based on a direction
* Returns the adjacent sibling based on a direction.
*
* @param string $path
* @param integer $direction either -1 or +1
* @return Object the sibling item
* @return Page The sibling item.
*/
public function adjacentSibling($path, $direction = 1)
{
$values = array_keys($this->items);
$keys = array_flip($values);
$index = $keys[$path] - $direction;
return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
if (array_key_exists($path, $keys)) {
$index = $keys[$path] - $direction;
return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
}
return $this;
}
/**
* Returns the item in the current position
* @param String $path the path the item
* @return Object item in the array the the current position
* Returns the item in the current position.
*
* @param string $path the path the item
* @return Page Item in the array the the current position.
*/
public function currentPosition($path) {
return array_search($path,array_keys($this->items));
public function currentPosition($path)
{
return array_search($path, array_keys($this->items));
}
/**
* Returns the items between a set of date ranges where second value is optional
* Dates can be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
* @param $startDate
* @param bool $endDate
*
* @return $this
* @throws \Exception
*/
public function dateRange($startDate, $endDate = false)
{
$start = strtotime($startDate);
$end = $endDate ? strtotime($endDate) : strtotime("now +1000 years");
$date_range = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page->date() > $start && $page->date() < $end) {
$date_range[$path] = $slug;
}
}
$this->items = $date_range;
return $this;
}
/**
* Creates new collection with only visible pages
*
* @return Collection The collection with only visible pages
*/
public function visible()
{
$visible = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page->visible()) {
$visible[$path] = $slug;
}
}
$this->items = $visible;
return $this;
}
/**
* Creates new collection with only non-visible pages
*
* @return Collection The collection with only non-visible pages
*/
public function nonVisible()
{
$visible = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if (!$page->visible()) {
$visible[$path] = $slug;
}
}
$this->items = $visible;
return $this;
}
/**
* Creates new collection with only modular pages
*
* @return Collection The collection with only modular pages
*/
public function modular()
{
$modular = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page->modular()) {
$modular[$path] = $slug;
}
}
$this->items = $modular;
return $this;
}
/**
* Creates new collection with only non-modular pages
*
* @return Collection The collection with only non-modular pages
*/
public function nonModular()
{
$modular = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if (!$page->modular()) {
$modular[$path] = $slug;
}
}
$this->items = $modular;
return $this;
}
/**
* Creates new collection with only published pages
*
* @return Collection The collection with only published pages
*/
public function published()
{
$published = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if ($page->published()) {
$published[$path] = $slug;
}
}
$this->items = $published;
return $this;
}
/**
* Creates new collection with only non-published pages
*
* @return Collection The collection with only non-published pages
*/
public function nonPublished()
{
$published = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if (!$page->published()) {
$published[$path] = $slug;
}
}
$this->items = $published;
return $this;
}
/**
* Creates new collection with only routable pages
*
* @return Collection The collection with only routable pages
*/
public function routable()
{
$routable = [];
foreach (array_keys($this->items) as $path => $slug) {
$page = $this->pages->get($path);
if ($page->routable()) {
$routable[$path] = $slug;
}
}
$this->items = $routable;
return $this;
}
/**
* Creates new collection with only non-routable pages
*
* @return Collection The collection with only non-routable pages
*/
public function nonRoutable()
{
$routable = [];
foreach ($this->items as $path => $slug) {
$page = $this->pages->get($path);
if (!$page->routable()) {
$routable[$path] = $slug;
}
}
$this->items = $routable;
return $this;
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Grav\Common\Page;
use RocketTheme\Toolbox\ArrayTraits\Constructor;
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess;
class Header implements \ArrayAccess
{
use NestedArrayAccess, Constructor;
}

View File

@@ -2,9 +2,9 @@
namespace Grav\Common\Page;
use Grav\Common\Getters;
use Grav\Common\Registry;
use Grav\Config;
use Symfony\Component\Yaml\Yaml;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Grav\Common\GravTrait;
/**
* Media is a holder object that contains references to the media of page. This object is created and
@@ -15,12 +15,15 @@ use Symfony\Component\Yaml\Yaml;
*/
class Media extends Getters
{
use GravTrait;
protected $gettersVariable = 'instances';
protected $path;
protected $instances = array();
protected $images = array();
protected $videos = array();
protected $audios = array();
protected $files = array();
/**
@@ -46,71 +49,81 @@ class Media extends Getters
// Find out the real filename, in case of we are at the metadata.
$filename = $info->getFilename();
list($basename, $ext, $meta) = $this->getFileParts($filename);
list($basename, $ext, $meta, $alternative) = $this->getFileParts($filename);
// Get medium instance creating it if it didn't exist.
$medium = $this->get("{$basename}.{$ext}", true);
if (!$medium) {
// Get medium instance if it already exists.
$medium = $this->get("{$basename}.{$ext}");
if (!$alternative) {
$medium = $medium ? $medium : $this->createMedium($info->getPathname());
if (!$medium) {
continue;
}
if ($meta) {
$medium->addMetaFile($meta);
} else {
$medium->set('size', $info->getSize());
}
} else {
$altMedium = $this->createMedium($info->getPathname());
if (!$altMedium) {
continue;
}
$altMedium->set('size', $info->getSize());
if (!$medium) {
$medium = $this->createMedium("{$path}/${basename}.${ext}");
if ($medium) {
$medium->set('size', filesize("{$path}/${basename}.${ext}"));
}
}
$medium = $medium ? $medium : $this->scaleMedium($altMedium, $alternative, 1);
$medium->addAlternative($this->parseRatio($alternative), $altMedium);
}
$this->add("{$basename}.{$ext}", $medium);
}
foreach ($this->images() as $medium) {
$alternatives = $medium->getAlternatives();
if (empty($alternatives)) {
continue;
}
// Assign meta files to the medium.
if ($meta) {
$medium->addMetaFile($meta);
$max = max(array_keys($alternatives));
for ($i=2; $i < $max; $i++) {
if (isset($alternatives[$i])) {
continue;
}
$medium->addAlternative($i, $this->scaleMedium($alternatives[$max], $max, $i));
}
}
}
/**
* Get medium by basename and extension.
* Get medium by filename.
*
* @param string $filename
* @param bool $create
* @return Medium|null
*/
public function get($filename, $create = false)
public function get($filename)
{
if ($create && !isset($this->instances[$filename])) {
$parts = explode('.', $filename);
$ext = array_pop($parts);
$basename = implode('.', $parts);
/** @var Config $config */
$config = Registry::get('Config');
// Check if medium type has been configured.
$params = $config->get("media.{$ext}");
if (!$params) {
return null;
}
$filePath = $this->path . '/' . $filename;
$params += array(
'type' => 'file',
'thumb' => 'media/thumb.png',
'mime' => 'application/octet-stream',
'name' => $filename,
'filename' => $filename,
'basename' => $basename,
'extension' => $ext,
'path' => $this->path,
'modified' => filemtime($filePath),
);
$lookup = array(
USER_DIR . 'images/',
SYSTEM_DIR . 'images/',
);
foreach ($lookup as $path) {
if (is_file($path . $params['thumb'])) {
$params['thumb'] = $path . $params['thumb'];
break;
}
}
$this->add(new Medium($params));
}
return isset($this->instances[$filename]) ? $this->instances[$filename] : null;
}
@@ -121,6 +134,7 @@ class Media extends Getters
*/
public function all()
{
ksort($this->instances, SORT_NATURAL | SORT_FLAG_CASE);
return $this->instances;
}
@@ -131,6 +145,7 @@ class Media extends Getters
*/
public function images()
{
ksort($this->images, SORT_NATURAL | SORT_FLAG_CASE);
return $this->images;
}
@@ -141,9 +156,21 @@ class Media extends Getters
*/
public function videos()
{
ksort($this->videos, SORT_NATURAL | SORT_FLAG_CASE);
return $this->videos;
}
/**
* Get a list of all audio media.
*
* @return array|Medium[]
*/
public function audios()
{
ksort($this->audios, SORT_NATURAL | SORT_FLAG_CASE);
return $this->audios;
}
/**
* Get a list of all file media.
*
@@ -151,24 +178,116 @@ class Media extends Getters
*/
public function files()
{
ksort($this->files, SORT_NATURAL | SORT_FLAG_CASE);
return $this->files;
}
/**
* Create a Medium object from a file
*
* @param string $file
*
* @return Medium|null
*/
protected function createMedium($file)
{
if (!file_exists($file)) {
return null;
}
$path = dirname($file);
$filename = basename($file);
$parts = explode('.', $filename);
$ext = array_pop($parts);
$basename = implode('.', $parts);
/** @var Config $config */
$config = self::$grav['config'];
// Check if medium type has been configured.
$params = $config->get("media.".strtolower($ext));
if (!$params) {
return null;
}
// Add default settings for undefined variables.
$params += $config->get('media.defaults');
$params += array(
'type' => 'file',
'thumb' => 'media/thumb.png',
'mime' => 'application/octet-stream',
'name' => $filename,
'filename' => $filename,
'basename' => $basename,
'extension' => $ext,
'path' => $path,
'modified' => filemtime($file),
);
$locator = self::$grav['locator'];
$lookup = $locator->findResources('image://');
foreach ($lookup as $lookupPath) {
if (is_file($lookupPath . $params['thumb'])) {
$params['thumb'] = $lookupPath . $params['thumb'];
break;
}
}
return new Medium($params);
}
protected function scaleMedium($medium, $from, $to)
{
$from = $this->parseRatio($from);
$to = $this->parseRatio($to);
if ($to > $from) {
return $medium;
}
$ratio = $to / $from;
$width = (int) ($medium->get('width') * $ratio);
$height = (int) ($medium->get('height') * $ratio);
$basename = $medium->get('basename');
$basename = str_replace('@'.$from.'x', '@'.$to.'x', $basename);
$debug = $medium->get('debug');
$medium->set('debug', false);
$file = $medium->resize($width, $height)->setPrettyName($basename)->url();
$file = preg_replace('|'. preg_quote(self::$grav['base_url_relative']) .'$|', '', GRAV_ROOT) . $file;
$medium->set('debug', $debug);
$size = filesize($file);
$medium = $this->createMedium($file);
$medium->set('size', $size);
return $medium;
}
/**
* @internal
*/
protected function add($file)
protected function add($name, $file)
{
$this->instances[$file->filename] = $file;
$this->instances[$name] = $file;
switch ($file->type) {
case 'image':
$this->images[$file->filename] = $file;
$this->images[$name] = $file;
break;
case 'video':
$this->videos[$file->filename] = $file;
$this->videos[$name] = $file;
break;
case 'audio':
$this->audios[$name] = $file;
break;
default:
$this->files[$file->filename] = $file;
$this->files[$name] = $file;
}
}
@@ -183,6 +302,13 @@ class Media extends Getters
$fileParts = explode('.', $filename);
$name = array_shift($fileParts);
$alternative = false;
if (preg_match('/(.*)@(\d+x)$/', $name, $matches)) {
$name = $matches[1];
$alternative = $matches[2];
}
$extension = null;
while (($part = array_shift($fileParts)) !== null) {
if ($part != 'meta') {
@@ -196,6 +322,15 @@ class Media extends Getters
}
$meta = implode('.', $fileParts);
return array($name, $extension, $meta);
return array($name, $extension, $meta, $alternative);
}
protected function parseRatio($ratio)
{
if (!is_numeric($ratio)) {
$ratio = (float) trim($ratio, 'x');
}
return $ratio;
}
}

View File

@@ -1,11 +1,12 @@
<?php
namespace Grav\Common\Page;
use Grav\Common\Config\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Common\Data\Blueprint;
use Grav\Common\Uri;
use Grav\Common\Data\Data;
use Grav\Common\Filesystem\File\Yaml;
use Grav\Common\Registry;
use Gregwar\Image\Image as ImageFile;
/**
@@ -34,6 +35,8 @@ use Gregwar\Image\Image as ImageFile;
*/
class Medium extends Data
{
use GravTrait;
/**
* @var string
*/
@@ -45,7 +48,25 @@ class Medium extends Data
protected $image;
protected $type = 'guess';
protected $quality = 80;
protected $quality = 85;
protected $debug_watermarked = false;
public static $valid_actions = [
// Medium functions
'format', 'lightbox', 'link', 'reset',
// Gregwar Image functions
'resize', 'forceResize', 'cropResize', 'crop', 'cropZoom',
'negate', 'brightness', 'contrast', 'grayscale', 'emboss', 'smooth', 'sharp', 'edge', 'colorize', 'sepia' ];
public static $size_param_actions = [
'resize' => [ 0, 1 ],
'forceResize' => [ 0, 1 ],
'cropResize' => [ 0, 1 ],
'crop' => [ 0, 1, 2, 3 ],
'cropResize' => [ 0, 1 ],
'zoomCrop' => [ 0, 1 ]
];
/**
* @var array
@@ -53,23 +74,44 @@ class Medium extends Data
protected $meta = array();
/**
* @var string
* @var array
*/
protected $linkTarget;
protected $alternatives = array();
/**
* @var string
*/
protected $linkAttributes;
protected $linkTarget;
/**
* @var string
*/
protected $linkSrcset;
/**
* @var string
*/
protected $linkAttributes = [];
/**
* Construct.
*
* @param array $items
* @param Blueprint $blueprint
*/
public function __construct($items = array(), Blueprint $blueprint = null)
{
parent::__construct($items, $blueprint);
$file_path = $this->get('path') . '/' . $this->get('filename');
$file_parts = pathinfo($file_path);
$this->set('thumb', $file_path);
$this->set('extension', $file_parts['extension']);
$this->set('filename', $this->get('filename'));
if ($this->get('type') == 'image') {
$filePath = $this->get('path') . '/' . $this->get('filename');
$image_info = getimagesize($filePath);
$this->set('thumb', $filePath);
$image_info = getimagesize($file_path);
$this->def('width', $image_info[0]);
$this->def('height', $image_info[1]);
$this->def('mime', $image_info['mime']);
@@ -77,6 +119,17 @@ class Medium extends Data
} else {
$this->def('mime', 'application/octet-stream');
}
$debug = self::$grav['config']->get('system.images.debug');
// try to override with page setting if possible
$page = self::$grav['page'];
if (!is_null($page)) {
if (isset($page->header()->images['debug'])) {
$debug = $page->header()->images['debug'];
}
}
$this->set('debug', $debug);
}
/**
@@ -90,39 +143,66 @@ class Medium extends Data
}
/**
* Return URL to file.
* Return PATH to file.
*
* @return string
* @return string path to file
*/
public function url()
public function path()
{
$config = Registry::get('Config');
if ($this->image) {
$output = $this->image->cacheFile($this->type, $this->quality);
$output = $this->saveImage();
$this->reset();
$output = GRAV_ROOT . '/' . $output;
} else {
$relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path'));
$output = $relPath . '/' . $this->get('filename');
$output = $this->get('path') . '/' . $this->get('filename');
}
return $config->get('system.base_url_relative') . '/'. $output;
return $output;
}
/**
* Sets image output format.
* Return URL to file.
*
* @param string $type
* @param int $quality
* @param bool $reset
* @return string
*/
public function format($type = null, $quality = 80)
public function url($reset = true)
{
if (!$this->image) {
$this->image();
if ($this->image) {
$output = '/' . $this->saveImage();
if ($reset) {
$this->reset();
}
} else {
$output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->get('path')) . '/' . $this->get('filename');
}
$this->type = $type;
$this->quality = $quality;
return self::$grav['base_url'] . $output;
}
/**
* Return srcset string for this Medium and its alternatives.
*
* @param bool $reset
* @return string
*/
public function srcset($reset = true)
{
if (empty($this->alternatives)) {
if ($reset) {
$this->reset();
}
return '';
}
$srcset = [ $this->url($reset) . ' ' . $this->get('width') . 'w' ];
foreach ($this->alternatives as $ratio => $medium) {
$srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
}
return implode(', ', $srcset);
}
/**
@@ -132,15 +212,16 @@ class Medium extends Data
* @param string $class
* @param string $type
* @param int $quality
* @param bool $reset
* @return string
*/
public function img($title = null, $class = null, $type = null, $quality = 80)
public function img($title = null, $class = null, $type = null, $quality = 80, $reset = true)
{
if (!$this->image) {
$this->image();
}
$output = $this->html($title, $class, $type, $quality);
$output = $this->html($title, $class, $type, $quality, $reset);
return $output;
}
@@ -150,77 +231,136 @@ class Medium extends Data
*
* @param string $title
* @param string $class
* @param string $type
* @param int $quality
* @param bool $reset
* @return string
*/
public function html($title = null, $class = null, $type = null, $quality = 80)
public function html($title = null, $class = null, $reset = true)
{
$data = $this->htmlRaw($reset);
$title = $title ? $title : $this->get('title');
$class = $class ? $class : '';
if ($this->image) {
$type = $type ? $type : $this->type;
$quality = $quality ? $quality : $this->quality;
$url = $this->url($type, $quality);
$this->reset();
$output = '<img src="' . $url . '" class="'. $class . '" alt="' . $title . '" />';
$attributes = $data['img_srcset'] ? ' srcset="' . $data['img_srcset'] . '" sizes="100vw"' : '';
$output = '<img src="' . $data['img_src'] . '"' . $attributes . ' class="'. $class . '" alt="' . $title . '" />';
} else {
$output = $title;
$output = $data['text'];
}
if ($this->linkTarget) {
$config = Registry::get('Config');
if (isset($data['a_href'])) {
$attributes = '';
foreach ($data['a_attributes'] as $prop => $value) {
$attributes .= " {$prop}=\"{$value}\"";
}
$output = '<a href="' . $config->get('system.base_url_relative') . '/'. $this->linkTarget
. '"' . $this->linkAttributes. ' class="'. $class . '">' . $output . '</a>';
$this->linkTarget = $this->linkAttributes = null;
$output = '<a href="' . $data['a_href'] . '"' . $attributes . ' class="'. $class . '">' . $output . '</a>';
}
return $output;
}
/**
* Return lightbox HTML for the medium.
* Return HTML array from medium.
*
* @param int $width
* @param int $height
* @return $this
* @param bool $reset
* @param string $title
*
* @return array
*/
public function lightbox($width = null, $height = null)
public function htmlRaw($reset = true, $title = '')
{
$this->linkAttributes = ' rel="lightbox"';
$output = [];
return $this->link($width, $height);
if ($this->image) {
$output['img_src'] = $this->url(false);
$output['img_srcset'] = $this->srcset($reset);
} else {
$output['text'] = $title;
}
if ($this->linkTarget) {
$output['a_href'] = $this->linkTarget;
$output['a_attributes'] = $this->linkAttributes;
$this->linkTarget = null;
$this->linkAttributes = [];
}
return $output;
}
/**
* Return link HTML for the medium.
* Sets the quality of the image
* @param Int $quality 0-100 quality
* @return Medium
*/
public function quality($quality)
{
$this->quality = $quality;
return $this;
}
/**
* Sets image output format.
*
* @param int $width
* @param int $height
* @param string $type
* @param int $quality
* @return $this
*/
public function format($type = null, $quality = 80)
{
if (!$this->image) {
$this->image();
}
$this->type = $type;
$this->quality = $quality;
return $this;
}
/**
* Enable link for the medium object.
*
* @param null $width
* @param null $height
* @return $this
*/
public function link($width = null, $height = null)
{
if ($this->image) {
$image = clone $this->image;
if ($width && $height) {
$image->cropResize($width, $height);
$this->cropResize($width, $height);
}
$this->linkTarget = $this->url(false);
$srcset = $this->srcset();
if ($srcset) {
$this->linkAttributes['data-srcset'] = $srcset;
}
$this->linkTarget = $image->cacheFile($this->type, $this->quality);
} else {
// TODO: we need to find out URI in a bit better way.
$relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path'));
$this->linkTarget = $relPath. '/' . $this->get('filename');
$this->linkTarget = self::$grav['base_url'] . preg_replace('|^' . GRAV_ROOT . '|', '', $this->get('path')) . '/' . $this->get('filename');
}
return $this;
}
/**
* Enable lightbox for the medium.
*
* @param null $width
* @param null $height
* @return Medium
*/
public function lightbox($width = null, $height = null)
{
$this->linkAttributes['rel'] = 'lightbox';
return $this->link($width, $height);
}
/**
* Reset image.
*
@@ -236,6 +376,7 @@ class Medium extends Data
}
$this->type = 'guess';
$this->quality = 80;
$this->debug_watermarked = false;
return $this;
}
@@ -257,7 +398,26 @@ class Medium extends Data
if (!$this->image) {
$this->image();
}
$result = call_user_func_array(array($this->image, $method), $args);
try {
$result = call_user_func_array(array($this->image, $method), $args);
foreach ($this->alternatives as $ratio => $medium) {
$args_copy = $args;
if (isset(self::$size_param_actions[$method])) {
foreach (self::$size_param_actions[$method] as $param) {
if (isset($args_copy[$param])) {
$args_copy[$param] = (int) $args_copy[$param] * $ratio;
}
}
}
call_user_func_array(array($medium, $method), $args_copy);
}
} catch (\BadFunctionCallException $e) {
$result = null;
}
// Returns either current object or result of the action.
return $result instanceof ImageFile ? $this : $result;
@@ -271,11 +431,13 @@ class Medium extends Data
*/
public function image($variable = 'thumb')
{
$locator = self::$grav['locator'];
// TODO: add default file
$file = $this->get($variable);
$this->image = ImageFile::open($file)
->setCacheDir(basename(IMAGES_DIR))
->setActualCacheDir(IMAGES_DIR)
->setCacheDir($locator->findResource('cache://images', false))
->setActualCacheDir($locator->findResource('cache://images', true))
->setPrettyName(basename($this->get('basename')));
$this->filter();
@@ -283,6 +445,31 @@ class Medium extends Data
return $this;
}
/**
* Save the image with cache.
*
* @return mixed|string
*/
protected function saveImage()
{
if (!$this->image) {
$this->image();
}
if ($this->get('debug') && !$this->debug_watermarked) {
$ratio = $this->get('ratio');
if (!$ratio) {
$ratio = 1;
}
$locator = self::$grav['locator'];
$overlay = $locator->findResource("system://assets/responsive-overlays/{$ratio}x.png") ?: $locator->findResource('system://assets/responsive-overlays/unknown.png');
$this->image->merge(ImageFile::open($overlay));
}
return $this->image->cacheFile($this->type, $this->quality);
}
/**
* Add meta file for the medium.
*
@@ -295,7 +482,7 @@ class Medium extends Data
$path = $this->get('path') . '/' . $this->get('filename') . '.meta.' . $type;
if ($type == 'yaml') {
$this->merge(Yaml::instance($path)->content());
$this->merge(CompiledYamlFile::instance($path)->content());
} elseif (in_array($type, array('jpg', 'jpeg', 'png', 'gif'))) {
$this->set('thumb', $path);
}
@@ -304,6 +491,28 @@ class Medium extends Data
return $this;
}
/**
* Add alternative Medium to this Medium.
*
* @param $ratio
* @param Medium $alternative
*/
public function addAlternative($ratio, Medium $alternative)
{
if (!is_numeric($ratio) || $ratio === 0) {
return;
}
$alternative->set('ratio', $ratio);
$this->alternatives[(float) $ratio] = $alternative;
}
public function getAlternatives()
{
return $this->alternatives;
}
/**
* Filter image by using user defined filter parameters.
*

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,22 @@
<?php
namespace Grav\Common\Page;
use \Grav\Common\Filesystem\Folder;
use \Grav\Common\Grav;
use \Grav\Common\Config;
use \Grav\Common\Data;
use \Grav\Common\Registry;
use \Grav\Common\Utils;
use \Grav\Common\Cache;
use \Grav\Common\Taxonomy;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Grav\Common\Utils;
use Grav\Common\Cache;
use Grav\Common\Taxonomy;
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;
/**
* GravPages is the class that is the entry point into the hierarchy of pages
*
* @author RocketTheme
* @license MIT
*/
class Pages
{
@@ -20,11 +25,6 @@ class Pages
*/
protected $grav;
/**
* @var Config
*/
protected $config;
/**
* @var array|Page[]
*/
@@ -35,10 +35,15 @@ class Pages
*/
protected $children;
/**
* @var string
*/
protected $base;
/**
* @var array|string[]
*/
protected $routes;
protected $routes = array();
/**
* @var array
@@ -46,7 +51,7 @@ class Pages
protected $sort;
/**
* @var Data\Blueprints
* @var Blueprints
*/
protected $blueprints;
@@ -55,14 +60,43 @@ class Pages
*/
protected $last_modified;
/**
* @var Types
*/
static protected $types;
/**
* Constructor
*
* @param Grav $c
*/
public function __construct(Grav $c)
{
$this->grav = $c;
$this->base = '';
}
/**
* Get or set base path for the pages.
*
* @param string $path
* @return string
*/
public function base($path = null)
{
if ($path !== null) {
$path = trim($path, '/');
$this->base = $path ? '/' . $path : null;
}
return $this->base;
}
/**
* Class initialization. Must be called before using this class.
*/
public function init()
{
$this->grav = Registry::get('Grav');
$this->config = Registry::get('Config');
$this->buildPages();
}
@@ -167,8 +201,11 @@ class Pages
public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null)
{
$items = $collection->toArray();
if (!$items) {
return [];
}
$lookup = md5(serialize($items));
$lookup = md5(json_encode($items));
if (!isset($this->sort[$lookup][$orderBy])) {
$this->buildSort($lookup, $items, $orderBy, $orderManual);
}
@@ -188,10 +225,13 @@ class Pages
*
* @param string $path
* @return Page
* @throws \Exception
*/
public function get($path)
{
if (!is_null($path) && !is_string($path)) throw new \Exception();
if (!is_null($path) && !is_string($path)) {
throw new \Exception();
}
return isset($this->instances[(string) $path]) ? $this->instances[(string) $path] : null;
}
@@ -219,11 +259,33 @@ class Pages
// Fetch page if there's a defined route to it.
$page = isset($this->routes[$url]) ? $this->get($this->routes[$url]) : null;
// If the page cannot be reached, look into site wide routes.
// If the page cannot be reached, look into site wide redirects, routes + wildcards
if (!$all && (!$page || !$page->routable())) {
$route = $this->config->get("site.routes.{$url}");
/** @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;
}
}
}
}
}
@@ -237,19 +299,22 @@ class Pages
*/
public function root()
{
return $this->instances[rtrim(PAGES_DIR, DS)];
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
return $this->instances[rtrim($locator->findResource('page://'), DS)];
}
/**
* Get a blueprint for a page type.
*
* @param string $type
* @return Data\Blueprint
* @return Blueprint
*/
public function blueprints($type)
{
if (!isset($this->blueprints)) {
$this->blueprints = new Data\Blueprints(THEMES_DIR . $this->config->get('system.pages.theme') . '/blueprints/');
$this->blueprints = new Blueprints(self::getTypes());
}
try {
@@ -259,9 +324,7 @@ class Pages
}
if (!$blueprint->initialized) {
/** @var Grav $grav */
$grav = Registry::get('Grav');
$grav->fireEvent('onCreateBlueprint', $blueprint);
$this->grav->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint]));
$blueprint->initialized = true;
}
@@ -291,25 +354,55 @@ class Pages
$list[$current->route()] = str_repeat('&nbsp; ', ($level-1)*2) . $current->title();
}
foreach ($current as $next) {
foreach ($current->children() as $next) {
$list = array_merge($list, $this->getList($next, $level + 1));
}
return $list;
}
/**
* Get available page types.
*
* @return Types
*/
public static function getTypes()
{
if (!self::$types) {
self::$types = new Types();
self::$types->scanBlueprints('theme://blueprints/');
self::$types->scanTemplates('theme://templates/');
$event = new Event();
$event->types = self::$types;
Grav::instance()->fireEvent('onGetPageTemplates', $event);
}
return self::$types;
}
/**
* Get available page types.
*
* @return array
*/
static public function types()
public static function types()
{
/** @var Config $config */
$config = Registry::get('Config');
$blueprints = new Data\Blueprints(THEMES_DIR . $config->get('system.pages.theme') . '/blueprints/');
$types = self::getTypes();
return $blueprints->types();
return $types->pageSelect();
}
/**
* Get available page types.
*
* @return array
*/
public static function modularTypes()
{
$types = self::getTypes();
return $types->modularSelect();
}
/**
@@ -317,10 +410,13 @@ class Pages
*
* @return array
*/
static public function parents()
public static function parents()
{
$grav = Grav::instance();
/** @var Pages $pages */
$pages = Registry::get('Pages');
$pages = $grav['pages'];
return $pages->getList();
}
@@ -332,18 +428,39 @@ class Pages
protected function buildPages()
{
$this->sort = array();
if ($this->config->get('system.cache.enabled')) {
/** @var Config $config */
$config = $this->grav['config'];
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$pagesDir = $locator->findResource('page://');
if ($config->get('system.cache.enabled')) {
/** @var Cache $cache */
$cache = Registry::get('Cache');
$cache = $this->grav['cache'];
/** @var Taxonomy $taxonomy */
$taxonomy = Registry::get('Taxonomy');
$last_modified = $this->config->get('system.cache.check.pages', true)
? Folder::lastModified(PAGES_DIR) : 0;
$page_cache_id = md5(USER_DIR.$last_modified);
$taxonomy = $this->grav['taxonomy'];
// how should we check for last modified? Default is by file
switch (strtolower($config->get('system.cache.check.method', 'file'))) {
case 'none':
case 'off':
$last_modified = 0;
break;
case 'folder':
$last_modified = Folder::lastModifiedFolder($pagesDir);
break;
default:
$last_modified = Folder::lastModifiedFile($pagesDir);
}
$page_cache_id = md5(USER_DIR.$last_modified.$config->checksum());
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
if (!$this->instances) {
$this->recurse();
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
$this->recurse($pagesDir);
$this->buildRoutes();
// save pages, routes, taxonomy, and sort to cache
@@ -353,10 +470,11 @@ class Pages
);
} else {
// If pages was found in cache, set the taxonomy
$this->grav['debugger']->addMessage('Page cache hit.');
$taxonomy->taxonomy($taxonomy_map);
}
} else {
$this->recurse();
$this->recurse($pagesDir);
$this->buildRoutes();
}
}
@@ -365,21 +483,27 @@ class Pages
* Recursive function to load & build page relationships.
*
* @param string $directory
* @param null $parent
* @param Page|null $parent
* @return Page
* @throws \RuntimeException
* @internal
*/
protected function recurse($directory = PAGES_DIR, &$parent = null)
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'];
$page->path($directory);
$page->parent($parent);
$page->orderDir($this->config->get('system.pages.order.dir'));
$page->orderBy($this->config->get('system.pages.order.by'));
if ($parent) {
$page->parent($parent);
}
$page->orderDir($config->get('system.pages.order.dir'));
$page->orderBy($config->get('system.pages.order.by'));
// Add into instances
if (!isset($this->instances[$page->path()])) {
@@ -391,16 +515,24 @@ class Pages
throw new \RuntimeException('Fatal error when creating page instances.');
}
// set current modified of page
$last_modified = $page->modified();
// flat for content availability
$content_exists = false;
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
$name = $file->getFilename();
$modified = $file->getMTime();
if ($file->isFile() && Utils::endsWith($name, CONTENT_EXT)) {
$page->init($file);
$content_exists = true;
if ($this->config->get('system.pages.events.page')) {
$this->grav->fireEvent('onAfterPageProcessed', $page);
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
}
} elseif ($file->isDir() && !$file->isDot()) {
@@ -418,20 +550,26 @@ class Pages
$this->children[$page->path()][$child->path()] = array('slug' => $child->slug());
// set the modified time if not already set
if (!$page->date()) {
$page->date($file->getMTime());
}
// set the last modified time on pages
$this->lastModified($file->getMTime());
if ($this->config->get('system.pages.events.page')) {
$this->grav->fireEvent('onAfterFolderProcessed', $page);
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
}
}
// Update the last modified if it's newer than already found
if ($modified > $last_modified) {
$last_modified = $modified;
}
}
// Set routability to false if no page found
if (!$content_exists) {
$page->routable(false);
}
// Override the modified and ID so that it takes the latest change into account
$page->modified($last_modified);
$page->id($last_modified.md5($page->filePath()));
// Sort based on Defaults or Page Overridden sort order
$this->children[$page->path()] = $this->sort($page);
@@ -444,7 +582,7 @@ class Pages
protected function buildRoutes()
{
/** @var $taxonomy Taxonomy */
$taxonomy = Registry::get('Taxonomy');
$taxonomy = $this->grav['taxonomy'];
// Build routes and taxonomy map.
/** @var $page Page */
@@ -465,8 +603,11 @@ class Pages
}
}
/** @var Config $config */
$config = $this->grav['config'];
// Alias and set default route to home page.
$home = trim($this->config->get('system.home.alias'), '/');
$home = trim($config->get('system.home.alias'), '/');
if ($home && isset($this->routes['/' . $home])) {
$this->routes['/'] = $this->routes['/' . $home];
$this->get($this->routes['/' . $home])->route('/');
@@ -484,12 +625,22 @@ class Pages
protected function buildSort($path, array $pages, $order_by = 'default', $manual = null)
{
$list = array();
$header_default = null;
$header_query = null;
// do this headery query work only once
if (strpos($order_by, 'header.') === 0) {
$header_query = explode('|', str_replace('header.', '', $order_by));
if (isset($header_query[1])) {
$header_default = $header_query[1];
}
}
foreach ($pages as $key => $info) {
$child = isset($this->instances[$key]) ? $this->instances[$key] : null;
if (!$child) {
throw new \RuntimeException("Page does not exist: {$key}");
throw new \RuntimeException("Page does not exist: {$key}");
}
switch ($order_by) {
@@ -503,11 +654,20 @@ class Pages
$list[$key] = $child->modified();
break;
case 'slug':
$list[$key] = $info['slug'];
$list[$key] = $child->slug();
break;
case 'basename':
$list[$key] = basename($key);
break;
case (is_string($header_query[0])):
$child_header = new Header((array)$child->header());
$header_value = $child_header->get($header_query[0]);
if ($header_value) {
$list[$key] = $header_value;
} else {
$list[$key] = $header_default ?: $key;
}
break;
case 'manual':
case 'default':
default:
@@ -515,8 +675,14 @@ class Pages
}
}
// Sort by the new list.
asort($list);
// handle special case when order_by is random
if ($order_by == 'random') {
$list = $this->arrayShuffle($list);
} else {
// else just sort the list according to specified key
asort($list);
}
// Move manually ordered items into the beginning of the list. Order of the unlisted items does not change.
if (is_array($manual) && !empty($manual)) {
@@ -544,4 +710,18 @@ class Pages
$this->sort[$path][$order_by][$key] = $info;
}
}
// Shuffles and associative array
protected function arrayShuffle($list)
{
$keys = array_keys($list);
shuffle($keys);
$new = array();
foreach ($keys as $key) {
$new[$key] = $list[$key];
}
return $new;
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Grav\Common\Page;
use Grav\Common\Filesystem\Folder;
use RocketTheme\Toolbox\ArrayTraits\ArrayAccess;
use RocketTheme\Toolbox\ArrayTraits\Constructor;
use RocketTheme\Toolbox\ArrayTraits\Countable;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\Iterator;
class Types implements \ArrayAccess, \Iterator, \Countable
{
use ArrayAccess, Constructor, Iterator, Countable, Export;
protected $items;
public function register($type, $blueprint = null)
{
if ($blueprint || empty($this->items[$type])) {
$this->items[$type] = $blueprint;
}
}
public function scanBlueprints($path)
{
$options = [
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => [
'key' => '|\.yaml$|'
],
'key' => 'SubPathName',
'value' => 'PathName',
];
$this->items = Folder::all($path, $options) + $this->items;
}
public function scanTemplates($path)
{
$options = [
'compare' => 'Filename',
'pattern' => '|\.html\.twig$|',
'filters' => [
'value' => '|\.html\.twig$|'
],
'value' => 'Filename',
'recursive' => false
];
foreach (Folder::all($path, $options) as $type) {
$this->register($type);
}
if (file_exists($path . 'modular/')) {
foreach (Folder::all($path . 'modular/', $options) as $type) {
$this->register('modular/' . $type);
}
}
}
public function pageSelect()
{
$list = [];
foreach ($this->items as $name => $file) {
if (strpos($name, '/')) {
continue;
}
$list[$name] = ucfirst(strtr($name, '_', ' '));
}
ksort($list);
return $list;
}
public function modularSelect()
{
$list = [];
foreach ($this->items as $name => $file) {
if (strpos($name, 'modular/') !== 0) {
continue;
}
$list[$name] = trim(ucfirst(strtr(basename($name), '_', ' ')));
}
ksort($list);
return $list;
}
}

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