Compare commits

...

246 Commits

Author SHA1 Message Date
Andy Miller
6baf7e0b35 Merge branch 'release/0.9.24' 2015-04-15 15:55:48 -06:00
Andy Miller
2a56f21d13 version update 2015-04-15 15:55:28 -06:00
Andy Miller
d253c3c6c5 updated changelog 2015-04-15 15:54:36 -06:00
Andy Miller
289a838ba1 disable timestamps by default 2015-04-15 11:14:04 -06:00
Andy Miller
f6f3e96106 Merge branch 'develop' of https://github.com/getgrav/grav into develop
* 'develop' of https://github.com/getgrav/grav:
  hide notice about ob_flush()
2015-04-15 09:34:37 -06:00
Andy Miller
7d22305678 hide notice about ob_flush() 2015-04-15 06:44:34 -06:00
Andy Miller
7571d1d562 Merge branch 'feature/media_querystring' into develop 2015-04-14 22:51:44 -06:00
Andy Miller
b4c06f537d added page expires 2015-04-14 22:50:45 -06:00
Andy Miller
830c723bae Revert "added page expires"
This reverts commit f02f3d507dc0959866e6bdef67cc1cf29c0f173e.
2015-04-14 22:50:26 -06:00
Andy Miller
b83ab07374 added page expires 2015-04-14 21:59:31 -06:00
Gert
8200cb9336 querystring and cache timestamp support in media 2015-04-15 03:10:08 +02:00
Andy Miller
b1a38306af Added asset timestamps 2015-04-14 15:46:11 -06:00
Andy Miller
6ec0f4782f Merge pull request #169 from TomasVotruba/patch-1
cs
2015-04-13 11:44:06 -06:00
Tomáš Votruba
d25f9bcf1b cs 2015-04-13 19:41:52 +02:00
Andy Miller
21f87ade2d Merge branch 'feature/improved_download_support' into develop 2015-04-11 18:27:09 -06:00
Andy Miller
8f54e5739f fix for any file with parameters 2015-04-10 14:28:37 -06:00
Andy Miller
21a6594573 Added a onBeforeDownload() event to provide logging/access check, etc. 2015-04-10 14:14:51 -06:00
Andy Miller
01ce80fb1a fixed direct operations on media objects 2015-04-10 13:30:36 -06:00
Andy Miller
174672c411 Support chunked downloads and non-media filetypes 2015-04-10 13:22:10 -06:00
Andy Miller
4785103081 Added download() and getMimeType() static methods 2015-04-10 13:21:53 -06:00
Andy Miller
7030422b11 New baseman option in Uri class 2015-04-10 13:21:23 -06:00
Andy Miller
2a06dc9bea customizable page types 2015-04-10 13:21:03 -06:00
Andy Miller
66927043de Merge branch 'release/0.9.23' 2015-04-09 13:01:01 -06:00
Andy Miller
7d16bafd52 Merge branch 'release/0.9.23' into develop 2015-04-09 13:01:01 -06:00
Andy Miller
ee340e2d6f version update 2015-04-09 13:00:50 -06:00
Andy Miller
66d9fd1a5e Merge branch 'feature/gpm_array_object_fixes' into develop 2015-04-09 12:51:55 -06:00
Andy Miller
7c28de6ae5 fixes for download 2015-04-09 12:41:03 -06:00
Andy Miller
c95f602ea2 fixes for changelog 2015-04-09 12:37:46 -06:00
Gert
6361280d99 remove supressed warnings 2015-04-09 20:13:10 +02:00
Andy Miller
b3a9d7cd41 fixes for broken gpm selfupgrade 2015-04-08 22:29:50 -06:00
Gert
071989c554 Merge branch 'develop' of github.com:getgrav/grav into develop
* 'develop' of github.com:getgrav/grav:
  Fix Folder::getRelativePath() so that it works with backslashes
  version update
2015-04-08 22:56:12 +02:00
Gert
f956d7113f use FilesystemIterator to solve windows path issue more elegantly 2015-04-08 22:55:27 +02:00
Matias Griese
f0472fdd76 Fix Folder::getRelativePath() so that it works with backslashes 2015-04-08 21:37:19 +03:00
Andy Miller
01899676a4 Merge branch 'release/0.9.22' into develop 2015-04-08 12:22:35 -06:00
Andy Miller
b13d572ca8 Merge branch 'release/0.9.22' 2015-04-08 12:22:34 -06:00
Andy Miller
a588e08405 version update 2015-04-08 12:22:23 -06:00
Andy Miller
6c93483220 updated changelog 2015-04-08 11:53:00 -06:00
Gert
733c13102b remove double ; 2015-04-08 11:43:40 -06:00
Gert
688d6fe17a normalize grav path 2015-04-08 11:43:28 -06:00
Gert
25ff1f230f fix mixed slashes in paths 2015-04-08 11:43:20 -06:00
Andy Miller
094b58130a Fix for issue #167 - latest version check out of order 2015-04-08 11:42:23 -06:00
Andy Miller
a18ec00962 Merge branch 'release/0.9.21' 2015-04-07 13:53:44 -06:00
Andy Miller
d9188e76ed Merge branch 'release/0.9.21' into develop 2015-04-07 13:53:44 -06:00
Andy Miller
a04a79af3e version update 2015-04-07 13:53:32 -06:00
Andy Miller
0c84392d0f sizes reference in changelog 2015-04-07 13:18:59 -06:00
Andy Miller
be12f350cb changelog updated 2015-04-07 13:05:21 -06:00
Andy Miller
01c7eadc92 Added new default_image_quality system configuration option 2015-04-07 12:55:29 -06:00
Andy Miller
4b3abc282a PSR Fixes 2015-04-07 12:42:48 -06:00
Andy Miller
81358e9984 Merge branch 'develop' of https://github.com/getgrav/grav into develop
# By Gert
# Via Gert
* 'develop' of https://github.com/getgrav/grav:
  fix alternative type not detected anymore
2015-04-07 09:11:09 -06:00
Andy Miller
8179d8eac1 updated composer packages 2015-04-07 09:10:42 -06:00
Gert
c22a2579a9 Merge branch 'develop' of github.com:getgrav/grav into develop
* 'develop' of github.com:getgrav/grav:
  update Console/GPM for refactored Common/GPM
  refactor Common/GPM namespace for more unified approach to installed vs remote packages
  add filter function to iterator
2015-04-06 23:27:19 +02:00
Gert
0a26772e35 fix alternative type not detected anymore 2015-04-06 23:26:53 +02:00
Andy Miller
364fc7afa8 Merge branch 'feature/GPM-unify' into develop 2015-04-06 10:27:16 -06:00
Gert
82bd54eb0b update Console/GPM for refactored Common/GPM 2015-04-04 14:02:28 +02:00
Gert
d92d9bafc6 refactor Common/GPM namespace for more unified approach to installed vs remote packages 2015-04-04 14:01:46 +02:00
Gert
9c541ee20b add filter function to iterator 2015-04-04 14:00:52 +02:00
Gert
105dc34b2a don't scale @1x images, use them as base 2015-04-03 11:30:35 +02:00
Andy Miller
0d8b46a157 fixed image element name 2015-04-01 17:27:51 -06:00
Gert
6142a12f48 Merge branch 'feature-multimedia' into develop
* feature-multimedia:
  reset on construct
  thumbnail selection fixes
  remove debugger line
2015-04-01 23:33:24 +02:00
Gert
1bda3eb1e3 Merge branch 'develop' of github.com:getgrav/grav into feature-multimedia
* 'develop' of github.com:getgrav/grav:
2015-04-01 23:32:40 +02:00
Gert
96b2b327b2 reset on construct 2015-04-01 16:51:24 +02:00
Gert
e014b12626 thumbnail selection fixes 2015-04-01 16:17:10 +02:00
Gert
d473f72edd remove debugger line 2015-04-01 16:14:18 +02:00
Andy Miller
7139425812 Merge pull request #161 from Gertt/feature-multimedia
Media overhaul
2015-03-31 15:00:49 -06:00
Gert
f5722d7baa Merge branch 'develop' of github.com:getgrav/grav into feature-multimedia
* 'develop' of github.com:getgrav/grav:
  switched to && operator
  Moved default image quality to defines.php
  Missing 'quality' in valid image functions
  updated version
  updated version
  updated changelog
  Fix for issue #158 - load order with debugger on
  Added support for external URL in directs
  more description for separator
  Serve images as static files
  Corrected HTML output of links with empty HTML anchors
  Changed Twig Link Regex which fixes #149 [Markdown Doesn't Handle [words](#)]

Conflicts:
	system/src/Grav/Common/Page/Medium.php
2015-03-31 22:58:24 +02:00
Gert
467eb00f0f rename factory to mediumfactory 2015-03-31 22:52:41 +02:00
Gert
0ce3646977 use style for static resize 2015-03-31 22:50:55 +02:00
Gert
53dd6c0860 multimedia refactor finishing touches 2015-03-31 21:23:14 +02:00
Andy Miller
e4a130c919 switched to && operator 2015-03-28 14:52:35 -06:00
Andy Miller
0d9ddb92d5 Moved default image quality to defines.php 2015-03-26 11:32:17 -06:00
Andy Miller
68a561d4fb Missing 'quality' in valid image functions 2015-03-26 11:24:22 -06:00
Andy Miller
5e651dd0e5 Merge branch 'release/0.9.20' 2015-03-24 17:31:39 -06:00
Andy Miller
1bb2965916 Merge tag '0.9.20' into develop
Release v0.9.20
2015-03-24 17:31:39 -06:00
Andy Miller
4ea650fc6d updated version 2015-03-24 17:31:24 -06:00
Andy Miller
501b38c4ba updated version 2015-03-24 17:30:30 -06:00
Andy Miller
ede958bd61 updated changelog 2015-03-24 17:29:44 -06:00
Andy Miller
f3d099e655 Fix for issue #158 - load order with debugger on 2015-03-19 23:29:45 -06:00
Andy Miller
db5c3ea400 Added support for external URL in directs 2015-03-16 21:10:31 -06:00
Andy Miller
504f57930a Merge pull request #151 from rzimin/patch-1
Serve images as static files
2015-03-16 18:59:27 -06:00
Andy Miller
02b1b2cf9d Merge branch 'develop' of https://github.com/getgrav/grav into develop
# By Sommerregen
# Via Andy Miller (1) and Sommerregen (1)
* 'develop' of https://github.com/getgrav/grav:
  Corrected HTML output of links with empty HTML anchors
  Changed Twig Link Regex which fixes #149 [Markdown Doesn't Handle [words](#)]
2015-03-12 17:09:15 -06:00
Andy Miller
99dbb3225b more description for separator 2015-03-12 17:08:33 -06:00
rzimin
08a8d69be2 Serve images as static files
Ensure images are served as static files when using Nginx
2015-03-12 13:34:46 +03:00
Andy Miller
a029f89ad2 Merge pull request #150 from Sommerregen/bugfix/parsedown-handle-empty-html-anchors
Changed Twig Link Regex which fixes #149 [Markdown Doesn't Handle [words](#)
2015-03-08 19:17:16 -06:00
Sommerregen
ed02fed866 Corrected HTML output of links with empty HTML anchors 2015-03-08 20:01:30 +01:00
Sommerregen
06de6c8e17 Changed Twig Link Regex which fixes #149 [Markdown Doesn't Handle [words](#)] 2015-03-08 19:24:48 +01:00
Gert
9ddfcd2154 Merge branch 'develop' of github.com:getgrav/grav into feature-multimedia
* 'develop' of github.com:getgrav/grav:
  updated changelog
  Added async and defer loading of JavaScript files
  Updated web.config  - Assets accesible
  fixed date
  version update
  removed some unused vars
  Removed some duplicate code via a trait
  Broke out iterators into their own class files
  only use files for last modified, so children don't affect the modified time of their parent page
  prevent a page from taken it's parent into account when detecting modified time
  Fixed `nth()` and added `first()` `last()` and `reverse()` methods
  Revert "Replace constant with streams. (need for multisite)."
  Replace constant with streams. (need for multisite).
2015-03-02 22:52:18 +01:00
Andy Miller
217e5e5a2c Merge branch 'develop' of https://github.com/getgrav/grav into develop
# By Vivalldi
# Via Andy Miller (1) and Vivalldi (1)
* 'develop' of https://github.com/getgrav/grav:
  Updated web.config  - Assets accesible
2015-03-02 11:09:54 -07:00
Andy Miller
272bf357d4 updated changelog 2015-03-02 11:06:14 -07:00
Andy Miller
9a5d14aa13 Added async and defer loading of JavaScript files 2015-03-02 11:03:21 -07:00
Andy Miller
2b70b2ad4f Merge pull request #148 from Vivalldi/patch-1
Updated web.config  - Assets accesible
2015-03-02 10:33:10 -07:00
Vivalldi
9323385b25 Updated web.config - Assets accesible
The system/assets folder is accessible except for .txt .md .html .yaml .php .twig .sh .bat files.
2015-03-02 12:30:46 -05:00
Andy Miller
fe90204772 Merge branch 'release/0.9.19' 2015-03-01 15:30:15 -07:00
Andy Miller
1b66e3d2a1 fixed date 2015-03-01 14:48:13 -07:00
Andy Miller
a2e1b9e100 Merge branch 'release/0.9.19' 2015-02-28 17:53:09 -07:00
Andy Miller
74ab81b524 Merge branch 'release/0.9.19' into develop 2015-02-28 17:53:09 -07:00
Andy Miller
01be2df935 version update 2015-02-28 17:52:55 -07:00
Andy Miller
62e5ea3bbd removed some unused vars 2015-02-28 14:14:20 -07:00
Andy Miller
b8c274b7b8 Removed some duplicate code via a trait 2015-02-28 14:13:59 -07:00
Andy Miller
e154b13b6e Broke out iterators into their own class files 2015-02-28 13:56:53 -07:00
Andy Miller
0f312a3c43 Merge pull request #146 from Gertt/develop
fix for last modified time detection of page
2015-02-27 16:17:48 -07:00
Gert
d8823a6b3a only use files for last modified, so children don't affect the modified time of their parent page 2015-02-27 23:29:23 +01:00
Gert
73d5f9da90 prevent a page from taken it's parent into account when detecting modified time 2015-02-27 22:16:59 +01:00
Andy Miller
7707b042e5 Fixed nth() and added first() last() and reverse() methods 2015-02-26 19:28:34 -07:00
Gert
ff95a116c2 Merge branch 'develop' of github.com:getgrav/grav into feature-multimedia
* 'develop' of github.com:getgrav/grav: (21 commits)
  Added initial named-assets implementation
  version update
  Update README.md
  Added optional deep option. defaults to false
  Added more flexible param separator option
  PSR camelCase fix
  missed a logical operator
  Fix camelCase method
  removed function in loop
  removed some commented out code
  removed unused vars
  Removed unused `use` statements
  rename fixme to todo
  remove fixme
  Object type hints
  strict boolean compare
  PSR fixes
  Strict boolean compares
  Some insight fixes
  Added new random string static and twig function
  ...
2015-02-24 21:40:59 +01:00
Andy Miller
2fe6dda365 Added initial named-assets implementation 2015-02-23 16:30:52 -07:00
Gert
f30a3c137e define new types 2015-02-22 00:01:55 +01:00
Gert
a5d31e7187 allow duplicated actions and follow medium on action 2015-02-22 00:01:44 +01:00
Gert
73c42313fa media overhaul 2015-02-22 00:00:52 +01:00
Andy Miller
686ba8a3f6 Merge branch 'release/0.9.18' into develop 2015-02-19 16:59:41 -07:00
Andy Miller
3397d5d2b7 Merge branch 'release/0.9.18' 2015-02-19 16:59:40 -07:00
Andy Miller
9ab3524fc5 version update 2015-02-19 16:59:20 -07:00
Andy Miller
3f10fa1b4c Update README.md 2015-02-18 22:21:38 -07:00
Andy Miller
6811fbea3d Added optional deep option. defaults to false 2015-02-18 22:04:02 -07:00
Andy Miller
3e245ef686 Merge pull request #144 from Sommerregen/bugfix/get-nested-default-plugin-configurations
Fix to merge nested page options with default plugin configurations
2015-02-18 21:59:49 -07:00
Andy Miller
28cff4e1da Added more flexible param separator option 2015-02-18 15:51:27 -07:00
Andy Miller
2bf67e482d PSR camelCase fix 2015-02-18 13:17:16 -07:00
Andy Miller
3976e4ce23 missed a logical operator 2015-02-18 13:12:57 -07:00
Andy Miller
a1ab94ffdd Fix camelCase method 2015-02-18 12:54:01 -07:00
Andy Miller
d6bed5441d removed function in loop 2015-02-18 12:51:58 -07:00
Andy Miller
e505c409ac removed some commented out code 2015-02-18 12:50:33 -07:00
Andy Miller
409742c078 removed unused vars 2015-02-18 12:48:01 -07:00
Andy Miller
6bd76028ce Removed unused use statements 2015-02-18 12:47:45 -07:00
Andy Miller
87dc53912b rename fixme to todo 2015-02-18 12:37:53 -07:00
Andy Miller
83d606c34a remove fixme 2015-02-18 12:37:39 -07:00
Andy Miller
1f906326e7 Object type hints 2015-02-18 12:37:19 -07:00
Andy Miller
ec67bf4c5b strict boolean compare 2015-02-18 12:37:00 -07:00
Andy Miller
127fe7fa2a PSR fixes 2015-02-18 12:36:30 -07:00
Andy Miller
e0de6f8b5f Strict boolean compares 2015-02-18 12:36:19 -07:00
Andy Miller
e81e35b7c2 Some insight fixes 2015-02-18 12:24:35 -07:00
Andy Miller
9bc365fe9e Added new random string static and twig function 2015-02-17 15:44:34 -07:00
Gert
21772c5481 Merge branch 'develop' of github.com:getgrav/grav into feature-multimedia
* 'develop' of github.com:getgrav/grav: (31 commits)
  Some performance optimizations for folder/file checking routines
  now checks for symlink availability before giving that option
  updated bundled composer package
  Backup pages folder if it exits in demo copy
  Fix rcopy reference
  Fix for issue #140 - plugin/theme install status not working on windows
  added a space after prompt
  Look for `_demo` content in packages and prompt to install it
  Use rcopy from Utils class
  Set package_type for unknown packages based on best guess
  Added recursive folder copy
  Don't critically fail, only log when plugin has config but is missing
  added repo URL to output
  text addition
  some corner case fixes
  Added symlink + git clone installation methods
  beginning of dependency work
  move timezone setting to after Config's `init()` method
  added timezone override support
  added a twig processTemplate() method
  ...

Conflicts:
	system/src/Grav/Common/Markdown/ParsedownGravTrait.php
	system/src/Grav/Common/Page/Media.php
	system/src/Grav/Common/Page/Medium.php
2015-02-17 20:41:49 +01:00
Andy Miller
7b32cbe2e1 Some performance optimizations for folder/file checking routines 2015-02-17 11:22:35 -07:00
Andy Miller
04a2f618bd now checks for symlink availability before giving that option 2015-02-16 18:48:52 -07:00
Andy Miller
73a104000a updated bundled composer package 2015-02-16 18:28:52 -07:00
Andy Miller
b8cb788324 Backup pages folder if it exits in demo copy 2015-02-16 14:46:05 -07:00
Andy Miller
690bb8a8be Fix rcopy reference 2015-02-16 14:45:21 -07:00
Andy Miller
26b0b02de2 Fix for issue #140 - plugin/theme install status not working on windows 2015-02-16 10:14:14 -07:00
Andy Miller
e5348ec8f1 added a space after prompt 2015-02-15 16:16:27 -07:00
Andy Miller
5e22eee1a8 Look for _demo content in packages and prompt to install it 2015-02-15 16:10:00 -07:00
Andy Miller
534bb57d3f Use rcopy from Utils class 2015-02-15 16:09:16 -07:00
Andy Miller
f8c203c033 Set package_type for unknown packages based on best guess 2015-02-15 16:08:53 -07:00
Andy Miller
300155e82e Added recursive folder copy 2015-02-15 16:08:24 -07:00
Andy Miller
3117a214d4 Merge branch 'feature/gpm_dependency' into develop 2015-02-15 15:30:25 -07:00
Andy Miller
9959868022 Don't critically fail, only log when plugin has config but is missing 2015-02-15 15:24:07 -07:00
Andy Miller
9248c5b709 added repo URL to output 2015-02-15 14:21:17 -07:00
Andy Miller
85fb5bccf3 text addition 2015-02-15 14:07:40 -07:00
Andy Miller
bd595ca9f6 some corner case fixes 2015-02-15 14:04:50 -07:00
Andy Miller
ace8823bd6 Added symlink + git clone installation methods 2015-02-15 13:37:51 -07:00
Sommerregen
5051b15145 Fix to merge nested page options with default plugin configurations 2015-02-15 21:20:20 +01:00
Andy Miller
1c1f2c268a beginning of dependency work 2015-02-14 22:33:50 -07:00
Andy Miller
545c76088c move timezone setting to after Config's init() method 2015-02-14 13:07:45 -07:00
Andy Miller
b418a7fd1e added timezone override support 2015-02-14 10:38:01 -07:00
Andy Miller
2938848df1 added a twig processTemplate() method 2015-02-13 11:35:21 -07:00
Andy Miller
17172f8920 added a contains() static method 2015-02-13 11:35:04 -07:00
Andy Miller
aeb237633e Merge pull request #139 from Zifius/patch-1
Update formatting to have blocks nesting less confusing
2015-02-10 17:01:31 -07:00
Andy Miller
d8a29dd639 Moved parsedown config into trait 2015-02-09 18:56:33 -07:00
Andy Miller
5c139e4b3c added a new markdown twig filter 2015-02-09 18:56:12 -07:00
Andy Miller
d69a0a9b06 minor naming updates 2015-02-09 16:35:02 -07:00
Andy Miller
07ce9c83ac changed getGrav() to static 2015-02-08 11:42:08 -07:00
Andy Miller
1d3559f0db Added a new filter to convert relative to absolute URLs 2015-02-07 12:21:40 -07:00
Andy Miller
be92a1da97 Merge pull request #141 from Gertt/patch-1
fix page ids not picking up folder-only pages
2015-02-07 09:56:54 -07:00
Gertt
97b430a888 Update Page.php 2015-02-07 17:55:46 +01:00
Gertt
b696e0d790 fix page ids not picking up folder-only pages 2015-02-07 17:47:58 +01:00
Gert
5b08a8605e various bugfixes, thumbnail support for files 2015-02-07 00:05:40 +01:00
Andy Miller
ab358dd820 Merge branch 'release/0.9.17' 2015-02-05 20:59:46 -07:00
Andy Miller
573b3227c7 Merge branch 'release/0.9.17' into develop 2015-02-05 20:59:46 -07:00
Andy Miller
50785c2434 version update 2015-02-05 20:59:23 -07:00
Andy Miller
fb9705809d HHVM fixes due to issues with traits and static variables #138 2015-02-05 15:07:31 -07:00
Alexander Turiak
1aeac01284 Update formatting to have blocks nesting less confusing 2015-02-05 12:43:49 +02:00
Gert
0cc35b56e5 Merge branch 'develop' of github.com:getgrav/grav into feature-multimedia
* 'develop' of github.com:getgrav/grav:
  Utilize new summary.delimiter setting rather than constant
  PSR fixes
  fix for markdown adding HTML tags into inline JS/CSS
  moved summary delimiter into site config
  fix for twig set capturing
  Moved to camels for plugins+themes as optional class naming type
  Added summary option
2015-02-04 22:39:47 +01:00
Gert
1423375312 enable markdown for any medium object and further cleanup 2015-02-04 22:39:34 +01:00
Gert
640ba16f8b fix alternatives detect 2015-02-04 22:38:24 +01:00
Andy Miller
d4e17442b2 Merge branch 'develop' of https://github.com/getgrav/grav into develop
# By Sommerregen
# Via Andy Miller (1) and Sommerregen (1)
* 'develop' of https://github.com/getgrav/grav:
  Added summary option
2015-02-02 17:27:04 -07:00
Andy Miller
eb4eafd915 Utilize new summary.delimiter setting rather than constant 2015-02-02 17:25:51 -07:00
Andy Miller
dc65475723 PSR fixes 2015-02-02 17:25:24 -07:00
Andy Miller
3f33e97f0c fix for markdown adding HTML tags into inline JS/CSS 2015-02-02 17:25:14 -07:00
Andy Miller
02508933d7 moved summary delimiter into site config 2015-02-02 17:24:41 -07:00
Andy Miller
89ebf2b011 Merge pull request #136 from Sommerregen/feature/added-summary-option
Added summary option
2015-02-02 17:23:00 -07:00
Andy Miller
c747c4baf7 fix for twig set capturing 2015-02-02 16:27:36 -07:00
Andy Miller
a3c848e4e2 Moved to camels for plugins+themes as optional class naming type 2015-02-02 13:08:56 -07:00
Gert
345d61f04f Merge branch 'develop' of github.com:getgrav/grav into feature-multimedia
* 'develop' of github.com:getgrav/grav:
  per-page debug setting not working as intended. Changing approaches...
  fix for using just defaults
  version update
  Added a page override support to media debug
  Some performance optimizations
  some cleanup refactoring
  PSR fixes
  Added 'enabled' option for summary and fixed query support in assets
  Added merge config and disable function in "Plugin.php"
2015-02-02 20:58:29 +01:00
Andy Miller
f0585ddb4e per-page debug setting not working as intended. Changing approaches... 2015-02-02 09:59:54 -07:00
Sommerregen
a6790cace3 Added summary option 2015-02-01 18:15:24 +01:00
Andy Miller
f244fc93c8 fix for using just defaults 2015-01-30 19:19:49 -07:00
Andy Miller
846a0baed8 Merge branch 'release/0.9.16' 2015-01-30 18:13:37 -07:00
Andy Miller
26ebb8fa6d Merge branch 'release/0.9.16' into develop 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
Gert
6f06e0c424 fix var name 2015-01-29 05:04:38 +01:00
Gert
8dfb9d08c4 refactor medium and basic implementation of video 2015-01-29 05:01:31 +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
621f38d856 Merge branch 'release/0.9.14' into develop 2015-01-23 13:08:47 -07:00
95 changed files with 3297 additions and 1309 deletions

View File

@@ -1,3 +1,141 @@
# v0.9.24
## 04/15/2015
1. [](#new)
* Added support for chunked downloads of Assets
* Added new `onBeforeDownload()` event
* Added new `download()` and `getMimeType()` methods to Utils class
* Added configuration option for supported page types
* Added assets and media timestamp options (off by default)
* Added page expires configuration option
2. [](#bugfix)
* Fixed issue with Nginx/Gzip and `ob_flush()` throwing error
* Fixed assets actions on 'direct media' URLs
* Fix for 'direct assets` with any parameters
# v0.9.23
## 04/09/2015
1. [](#bugfix)
* Fix for broken GPM `selfupgrade` (Grav 0.9.21 and 0.9.22 will need to manually upgrade to this version)
# v0.9.22
## 04/08/2015
1. [](#bugfix)
* Fix to normalize GRAV_ROOT path for Windows
* Fix to normalize Media image paths for Windows
* Fix for GPM `selfupgrade` when you are on latest version
# v0.9.21
## 04/07/2015
1. [](#new)
* Major Media functionality enhancements: SVG, Animated GIF, Video support!
* Added ability to configure default image quality in system configuration
* Added `sizes` attributes for custom retina image breakpoints
2. [](#improved)
* Don't scale @1x retina images
* Add filter to Iterator class
* Updated various composer packages
* Various PSR fixes
# v0.9.20
## 03/24/2015
1. [](#new)
* Added `addAsyncJs()` and `addDeferJs()` to Assets manager
* Added support for extranal URL redirects
2. [](#improved)
* Fix unpredictable asset ordering when set from plugin/system
* Updated `nginx.conf` to ensure system assets are accessible
* Ensure images are served as static files in Nginx
* Updated vendor libraries to latest versions
* Updated included composer.phar to latest version
3. [](#bugfix)
* Fixed issue with markdown links to `#` breaking HTML
# v0.9.19
## 02/28/2015
1. [](#new)
* Added named assets capability and bundled jQuery into Grav core
* Added `first()` and `last()` to `Iterator` class
2. [](#improved)
* Improved page modification routine to skip _dot files_
* Only use files to calculate page modification dates
* Broke out Folder iterators into their own classes
* Various Sensiolabs Insight fixes
3. [](#bugfix)
* Fixed `Iterator.nth()` method
# v0.9.18
## 02/19/2015
1. [](#new)
* Added ability for GPM `install` to automatically install `_demo` content if found (w/backup)
* Added ability for themes and plugins to have dependencies required to install via GPM
* Added ability to override the system timezone rather than relying on server setting only
* Added new Twig filter `random_string` for generating random id values
* Added new Twig filter `markdown` for on-the-fly markdown processing
* Added new Twig filter `absoluteUrl` to convert relative to absolute URLs
* Added new `processTemplate()` method to Twig object for on-the-fly processing of twig template
* Added `rcopy()` and `contains()` helper methods in Utils
2. [](#improved)
* Provided new `param_sep` variable to better support Apache on Windows
* Moved parsedown configuration into the trait
* Added optional **deep-copy** option to `mergeConfig()` for plugins
* Updated bundled `composer.phar` package
* Various Sensiolabs Insight fixes - Silver level now!
* Various PSR Fixes
3. [](#bugfix)
* Fix for windows platforms not displaying installed themes/plugins via GPM
* Fix page IDs not picking up folder-only pages
# v0.9.17
## 02/05/2015
1. [](#new)
* Added **full HHVM support!** Get your speed on with Facebook's crazy fast PHP JIT compiler
2. [](#improved)
* More flexible page summary control
* Support **CamelCase** plugin and theme class names. Replaces dashes and underscores
* Moved summary delimiter into `site.yaml` so it can be configurable
* Various PSR fixes
3. [](#bugfix)
* Fix for `mergeConfig()` not falling back to defaults
* Fix for `addInlineCss()` and `addInlineJs()` Assets not working between Twig tags
* Fix for Markdown adding HTML tags into inline CSS and JS
# 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

View File

@@ -1,5 +1,6 @@
# ![](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)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad/mini.png)](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/getgrav/grav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principals to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself.

Binary file not shown.

View File

@@ -8,15 +8,15 @@
"require": {
"php": ">=5.4.0",
"twig/twig": "~1.16",
"erusev/parsedown-extra": "~0.6",
"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",
"monolog/monolog": "~1.0",
"gregwar/image": "~2.0",
"ircmaxell/password-compat": "1.0.*",
"mrclay/minify": "dev-master",
"donatj/phpuseragentparser": "dev-master",

View File

@@ -2,12 +2,13 @@
namespace Grav;
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
exit(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
throw new \RuntimeException(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
}
// Ensure vendor libraries exist
$autoload = __DIR__ . '/vendor/autoload.php';
if (!is_file($autoload)) {
exit('Please run: <i>bin/grav install</i>');
throw new \RuntimeException("Please run: <i>bin/grav install</i>");
}
use Grav\Common\Grav;
@@ -15,19 +16,19 @@ use Grav\Common\Grav;
// Register the auto-loader.
$loader = require_once $autoload;
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
// Set timezone to default, falls back to system if php.ini not set
date_default_timezone_set(@date_default_timezone_get());
// Get the Grav instance
$grav = Grav::instance(
array(
'loader' => $loader
)
);
// Process the page
try {
$grav->process();
} catch (\Exception $e) {
$grav->fireEvent('onFatalException');
throw $e;

View File

@@ -25,28 +25,32 @@ http {
index index.php;
if (!-e $request_filename){ rewrite ^(.*)$ /index.php last; }
}
location /images/ {
# Serve images as static
}
location /user {
rewrite ^/user/accounts/(.*)$ /error redirect;
rewrite ^/user/config/(.*)$ /error redirect;
rewrite ^/user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
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/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
location /system {
rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
location /vendor {
rewrite ^/vendor/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
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
@@ -60,7 +64,6 @@ http {
include fastcgi_params;
}
}
}

File diff suppressed because one or more lines are too long

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

View File

@@ -20,10 +20,15 @@ png:
thumb: media/thumb-png.png
mime: image/png
gif:
type: image
type: animated
thumb: media/thumb-gif.png
mime: image/gif
svg:
type: vector
thumb: media/thumb-gif.png
mime: image/svg+xml
mp4:
type: video
thumb: media/thumb-mp4.png
@@ -52,19 +57,19 @@ mp3:
ogg:
type: audio
thumb: media/thumb-ogg.png
mine: audio/ogg
mime: audio/ogg
wma:
type: audio
thumb: media/thumb-wma.png
mine: audio/wma
mime: audio/wma
m4a:
type: audio
thumb: media/thumb-m4a.png
mine: audio/m4a
mime: audio/m4a
wav:
type: audio
thumb: media/thumb-wav.png
mine: audio/wav
mime: audio/wav
txt:
type: file

View File

@@ -6,9 +6,12 @@ taxonomies: [category,tag] # Arbitrary list of taxonomy types
blog:
route: '/blog' # Route to blog
metadata:
description: 'My Grav Site' # Site description
description: 'My Grav Site' # Site description
summary:
enabled: true # enable or disable summary of page
format: short # long = summary delimiter will be ignored; short = use the first occurence of delimter or size
size: 300 # Maximum length of summary (characters)
delimiter: === # The summary delimiter
routes:
/something/else: '/blog/sample-3' # Alias for /blog/sample-3
/another/one/here: '/blog/sample-3' # Another alias for /blog/sample-3

View File

@@ -8,6 +8,7 @@ schemes:
type: ReadOnlyStream
paths:
- user://images
- system://images
page:
type: ReadOnlyStream

View File

@@ -1,4 +1,6 @@
absolute_urls: false # Absolute or relative URLs for `base_url`
timezone: '' # Valid values: http://php.net/manual/en/timezones.php
param_sep: ':' # Parameter separator, use ';' for Apache on windows
home:
alias: '/home' # Default path for home, ie /
@@ -28,6 +30,8 @@ pages:
special_chars: # List of special characters to automatically convert to entities
'>': 'gt'
'<': 'lt'
types: 'txt|xml|html|json|rss|atom' # Pipe separated list of valid page types
expires: 604800 # Page expires time in seconds (default 7 days)
cache:
enabled: true # Set to true to enable caching
@@ -38,6 +42,7 @@ cache:
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
gzip: false # GZip compress the page output
twig:
cache: true # Set to true to enable twig caching
debug: false # Enable Twig debug
@@ -53,6 +58,9 @@ assets: # Configuration for Assets Manager (JS, C
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_minify: true # Minify the JS during pipelining
enable_asset_timestamp: false # Enable asset timetsamps
collections:
jquery: system://assets/jquery/jquery-2.1.3.min.js
errors:
display: true # Display full backtrace-style error page
@@ -63,3 +71,10 @@ debugger:
twig: true # Enable debugging of Twig templates
shutdown:
close_connection: true # Close the connection before calling onShutdown(). false for debugging
images:
default_image_quality: 85 # Default image quality to use when resampling images (85%)
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
media:
enable_media_timestamp: false # Enable media timetsamps

View File

@@ -2,12 +2,12 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '0.9.14');
define('GRAV_VERSION', '0.9.24');
define('DS', '/');
// Directories and Paths
if (!defined('GRAV_ROOT')) {
define('GRAV_ROOT', getcwd());
define('GRAV_ROOT', str_replace(DIRECTORY_SEPARATOR, DS, getcwd()));
}
define('ROOT_DIR', GRAV_ROOT . '/');
define('USER_PATH', 'user/');
@@ -40,6 +40,3 @@ define('RAW_CONTENT', 1);
define('TWIG_CONTENT', 2);
define('TWIG_CONTENT_LIST', 3);
define('TWIG_TEMPLATES', 4);
// Misc Defines
define('SUMMARY_DELIMITER', '===');

View File

@@ -71,6 +71,7 @@ class Assets
// Some configuration variables
protected $config;
protected $base_url;
protected $timestamp = '';
// Default values for pipeline settings
protected $css_minify = true;
@@ -82,7 +83,6 @@ class Assets
protected $css_no_pipeline = array();
protected $js_no_pipeline = array();
public function __construct(array $options = array())
{
// Forward config options
@@ -120,7 +120,7 @@ class Assets
}
// Set custom pipeline fetch command
if (isset($config['fetch_command']) and ($config['fetch_command'] instanceof Closure)) {
if (isset($config['fetch_command']) && ($config['fetch_command'] instanceof Closure)) {
$this->fetch_command = $config['fetch_command'];
}
@@ -143,17 +143,23 @@ class Assets
}
// Set collections
if (isset($config['collections']) and is_array($config['collections'])) {
if (isset($config['collections']) && is_array($config['collections'])) {
$this->collections = $config['collections'];
}
// Autoload assets
if (isset($config['autoload']) and is_array($config['autoload'])) {
if (isset($config['autoload']) && is_array($config['autoload'])) {
foreach ($config['autoload'] as $asset) {
$this->add($asset);
}
}
// Set timestamp
if (isset($config['enable_asset_timestamp']) && $config['enable_asset_timestamp'] === true) {
$this->timestamp = '?' . self::getGrav()['cache']->getKey();
}
return $this;
}
@@ -163,12 +169,17 @@ class Assets
public function init()
{
/** @var Config $config */
$config = self::$grav['config'];
$base_url = self::$grav['base_url'];
$config = self::getGrav()['config'];
$base_url = self::getGrav()['base_url'];
$asset_config = (array)$config->get('system.assets');
$this->config($asset_config);
$this->base_url = $base_url . '/';
// Register any preconfigured collections
foreach ($config->get('system.assets.collections') as $name => $collection) {
$this->registerCollection($name, (array)$collection);
}
}
/**
@@ -193,13 +204,15 @@ class Assets
} 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
$info = pathinfo($asset);
if (isset($info['extension'])) {
$ext = strtolower($info['extension']);
if ($ext === 'css') {
if (strlen($extension) > 0) {
$extension = strtolower($extension);
if ($extension === 'css') {
$this->addCss($asset, $priority, $pipeline);
} elseif ($ext === 'js') {
} elseif ($extension === 'js') {
$this->addJs($asset, $priority, $pipeline);
}
}
@@ -226,7 +239,9 @@ class Assets
foreach ($asset as $a) {
$this->addCss($a, $priority, $pipeline);
}
return $this;
} elseif (isset($this->collections[$asset])) {
$this->add($this->collections[$asset], $priority, $pipeline);
return $this;
}
@@ -235,7 +250,7 @@ class Assets
}
$key = md5($asset);
if ($asset && !array_key_exists($key, $this->css)) {
if ($asset) {
$this->css[$key] = [
'asset' => $asset,
'priority' => $priority,
@@ -256,16 +271,19 @@ class Assets
* @param mixed $asset
* @param int $priority the priority, bigger comes first
* @param bool $pipeline false if this should not be pipelined
* @param string $loading how the asset is loaded (async/defer)
*
* @return $this
*/
public function addJs($asset, $priority = 10, $pipeline = true)
public function addJs($asset, $priority = 10, $pipeline = true, $loading = '')
{
if (is_array($asset)) {
foreach ($asset as $a) {
$this->addJs($a, $priority, $pipeline);
}
return $this;
} elseif (isset($this->collections[$asset])) {
$this->add($this->collections[$asset], $priority, $pipeline);
return $this;
}
@@ -274,18 +292,47 @@ class Assets
}
$key = md5($asset);
if ($asset && !array_key_exists($key, $this->js)) {
if ($asset) {
$this->js[$key] = [
'asset' => $asset,
'priority' => $priority,
'order' => count($this->js),
'pipeline' => $pipeline
'pipeline' => $pipeline,
'loading' => $loading
];
}
return $this;
}
/**
* Convenience wrapper for async loading of JavaScript
*
* @param $asset
* @param int $priority
* @param bool $pipeline
*
* @return \Grav\Common\Assets
*/
public function addAsyncJs($asset, $priority = 10, $pipeline = true)
{
return $this->addJs($asset, $priority, $pipeline, 'async');
}
/**
* Convenience wrapper for deferred loading of JavaScript
*
* @param $asset
* @param int $priority
* @param bool $pipeline
*
* @return \Grav\Common\Assets
*/
public function addDeferJs($asset, $priority = 10, $pipeline = true)
{
return $this->addJs($asset, $priority, $pipeline, 'defer');
}
/**
* Add an inline CSS asset.
*
@@ -299,6 +346,9 @@ class Assets
*/
public function addInlineCss($asset, $priority = 10)
{
if (is_a($asset, 'Twig_Markup')) {
$asset = strip_tags((string)$asset);
}
$key = md5($asset);
if (is_string($asset) && !array_key_exists($key, $this->inline_css)) {
$this->inline_css[$key] = [
@@ -324,6 +374,9 @@ class Assets
*/
public function addInlineJs($asset, $priority = 10)
{
if (is_a($asset, 'Twig_Markup')) {
$asset = strip_tags((string)$asset);
}
$key = md5($asset);
if (is_string($asset) && !array_key_exists($key, $this->inline_js)) {
$this->inline_js[$key] = [
@@ -350,7 +403,7 @@ class Assets
}
// Sort array by priorities (larger priority first)
if (self::$grav) {
if (self::getGrav()) {
usort($this->css, function ($a, $b) {
if ($a['priority'] == $b['priority']) {
return $b['order'] - $a['order'];
@@ -375,11 +428,11 @@ class Assets
$output .= '<link href="' . $this->pipeline(CSS_ASSET) . '"' . $attributes . ' />' . "\n";
foreach ($this->css_no_pipeline as $file) {
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' />' . "\n";
}
} else {
foreach ($this->css as $file) {
$output .= '<link href="' . $file['asset'] . '"' . $attributes . ' />' . "\n";
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' />' . "\n";
}
}
@@ -433,11 +486,11 @@ class Assets
if ($this->js_pipeline) {
$output .= '<script src="' . $this->pipeline(JS_ASSET) . '"' . $attributes . ' ></script>' . "\n";
foreach ($this->js_no_pipeline as $file) {
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ></script>' . "\n";
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading']. '></script>' . "\n";
}
} else {
foreach ($this->js as $file) {
$output .= '<script src="' . $file['asset'] . '"' . $attributes . ' ></script>' . "\n";
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading'].'></script>' . "\n";
}
}
@@ -463,7 +516,7 @@ class Assets
protected function pipeline($css = true)
{
/** @var Cache $cache */
$cache = self::$grav['cache'];
$cache = self::getGrav()['cache'];
$key = '?' . $cache->getKey();
if ($css) {
@@ -521,17 +574,68 @@ class Assets
return $relative_path . $key;
}
/**
* Return the array of all the registered CSS assets
*
* @return array
*/
public function getCss()
{
return $this->css;
}
/**
* Return the array of all the registered JS assets
*
* @return array
*/
public function getJs()
{
return $this->js;
}
/**
* Return the array of all the registered collections
*
* @return array
*/
public function getCollections()
{
return $this->collections;
}
/**
* Determines if an asset exists as a collection, CSS or JS reference
*
* @param $asset
*
* @return bool
*/
public function exists($asset)
{
if (isset($this->collections[$asset]) ||
isset($this->css[$asset]) ||
isset($this->js[$asset])) {
return true;
} else {
return false;
}
}
/**
* Add/replace collection.
*
* @param string $collectionName
* @param array $assets
* @param bool $overwrite
*
* @return $this
*/
public function registerCollection($collectionName, Array $assets)
public function registerCollection($collectionName, Array $assets, $overwrite = false)
{
$this->collections[$collectionName] = $assets;
if ($overwrite || !isset($this->collections[$collectionName])) {
$this->collections[$collectionName] = $assets;
}
return $this;
}
@@ -570,26 +674,6 @@ class Assets
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).
*
@@ -643,9 +727,9 @@ class Assets
$info = pathinfo($asset);
if (isset($info['extension'])) {
$ext = strtolower($info['extension']);
if ($ext === 'css' and !in_array($asset, $this->css)) {
if ($ext === 'css' && !in_array($asset, $this->css)) {
$this->css[] = $asset;
} elseif ($ext === 'js' and !in_array($asset, $this->js)) {
} elseif ($ext === 'js' && !in_array($asset, $this->js)) {
$this->js[] = $asset;
}
}
@@ -665,8 +749,8 @@ class Assets
*/
protected function isRemoteLink($link)
{
return ('http://' === substr($link, 0, 7) or 'https://' === substr($link, 0, 8)
or '//' === substr($link, 0, 2));
return ('http://' === substr($link, 0, 7) || 'https://' === substr($link, 0, 8)
|| '//' === substr($link, 0, 2));
}
/**
@@ -679,7 +763,7 @@ class Assets
protected function buildLocalLink($asset)
{
try {
$asset = self::$grav['locator']->findResource($asset, false);
$asset = self::getGrav()['locator']->findResource($asset, false);
} catch (\Exception $e) {
}

View File

@@ -188,8 +188,7 @@ class Cache extends Getters
public function save($id, $data, $lifetime = null)
{
if ($this->enabled) {
if ($lifetime == null) {
if ($lifetime === null) {
$lifetime = $this->getLifetime();
}
$this->driver->save($id, $data, $lifetime);

View File

@@ -3,7 +3,6 @@ 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;

View File

@@ -3,10 +3,7 @@ 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;
@@ -21,6 +18,12 @@ class Config extends Data
{
protected $grav;
protected $streams = [
'system' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['system'],
]
],
'user' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
@@ -60,7 +63,8 @@ class Config extends Data
'cache' => [
'type' => 'Stream',
'prefixes' => [
'' => ['cache']
'' => ['cache'],
'images' => ['images']
]
],
'log' => [

View File

@@ -11,7 +11,7 @@ use RocketTheme\Toolbox\ArrayTraits\Export;
*/
class Blueprint
{
use Export;
use Export, DataMutatorTrait;
public $name;
@@ -46,68 +46,6 @@ class Blueprint
$this->filter = array_flip($filter);
}
/**
* Get value by using dot notation for nested arrays/objects.
*
* @example $value = $data->get('this.is.my.nested.variable');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
*
* @return mixed Value.
*/
public function get($name, $default = null, $separator = '.')
{
$path = explode($separator, $name);
$current = $this->items;
foreach ($path as $field) {
if (is_object($current) && isset($current->{$field})) {
$current = $current->{$field};
} elseif (is_array($current) && isset($current[$field])) {
$current = $current[$field];
} else {
return $default;
}
}
return $current;
}
/**
* Sey value by using dot notation for nested arrays/objects.
*
* @example $value = $data->set('this.is.my.nested.variable', true);
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value New value.
* @param string $separator Separator, defaults to '.'
*/
public function set($name, $value, $separator = '.')
{
$path = explode($separator, $name);
$current = &$this->items;
foreach ($path as $field) {
if (is_object($current)) {
// Handle objects.
if (!isset($current->{$field})) {
$current->{$field} = array();
}
$current = &$current->{$field};
} else {
// Handle arrays and scalars.
if (!is_array($current)) {
$current = array($field => array());
} elseif (!isset($current[$field])) {
$current[$field] = array();
}
$current = &$current[$field];
}
}
$current = $value;
}
/**
* Return all form fields.
*
@@ -146,10 +84,9 @@ class Blueprint
*
* @param array $data1
* @param array $data2
* @param string $name
* @return array
*/
public function mergeData(array $data1, array $data2, $name = null)
public function mergeData(array $data1, array $data2)
{
// Initialize data
$this->fields();
@@ -213,7 +150,7 @@ class Blueprint
$bref = array_merge($bref, array($key => $head[$key]));
}
}
} while(count($head_stack));
} while (count($head_stack));
$this->items = $blueprints;
}
@@ -478,14 +415,15 @@ class Blueprint
* @throws \RuntimeException
* @internal
*/
protected function checkRequired(array $data, array $fields) {
protected function checkRequired(array $data, array $fields)
{
foreach ($fields as $name => $field) {
if (!is_string($field)) {
continue;
}
$field = $this->rules[$field];
if (isset($field['validate']['required'])
&& $field['validate']['required'] == true
&& $field['validate']['required'] === true
&& empty($data[$name])) {
throw new \RuntimeException("Missing required field: {$field['name']}");
}

View File

@@ -47,7 +47,6 @@ class Blueprints
$file = CompiledYamlFile::instance($filename);
$blueprints = $file->content();
} else {
// throw new \RuntimeException("Blueprints for '{$type}' cannot be found! {$this->search}{$type}");
$blueprints = [];
}

View File

@@ -15,7 +15,7 @@ use RocketTheme\Toolbox\File\FileInterface;
*/
class Data implements DataInterface
{
use ArrayAccessWithGetters, Countable, Export;
use ArrayAccessWithGetters, Countable, Export, DataMutatorTrait;
protected $gettersVariable = 'items';
protected $items;
@@ -56,67 +56,6 @@ class Data implements DataInterface
return $this->get($name, $default, $separator);
}
/**
* Get value by using dot notation for nested arrays/objects.
*
* @example $value = $data->get('this.is.my.nested.variable');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @return mixed Value.
*/
public function get($name, $default = null, $separator = '.')
{
$path = explode($separator, $name);
$current = $this->items;
foreach ($path as $field) {
if (is_object($current) && isset($current->{$field})) {
$current = $current->{$field};
} elseif (is_array($current) && isset($current[$field])) {
$current = $current[$field];
} else {
return $default;
}
}
return $current;
}
/**
* Sey value by using dot notation for nested arrays/objects.
*
* @example $value = $data->set('this.is.my.nested.variable', true);
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value New value.
* @param string $separator Separator, defaults to '.'
*/
public function set($name, $value, $separator = '.')
{
$path = explode($separator, $name);
$current = &$this->items;
foreach ($path as $field) {
if (is_object($current)) {
// Handle objects.
if (!isset($current->{$field})) {
$current->{$field} = array();
}
$current = &$current->{$field};
} else {
// Handle arrays and scalars.
if (!is_array($current)) {
$current = array($field => array());
} elseif (!isset($current[$field])) {
$current[$field] = array();
}
$current = &$current[$field];
}
}
$current = $value;
}
/**
* Set default value by using dot notation for nested arrays/objects.
*

View File

@@ -0,0 +1,68 @@
<?php
namespace Grav\Common\Data;
trait DataMutatorTrait
{
/**
* Get value by using dot notation for nested arrays/objects.
*
* @example $value = $data->get('this.is.my.nested.variable');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @return mixed Value.
*/
public function get($name, $default = null, $separator = '.')
{
$path = explode($separator, $name);
$current = $this->items;
foreach ($path as $field) {
if (is_object($current) && isset($current->{$field})) {
$current = $current->{$field};
} elseif (is_array($current) && isset($current[$field])) {
$current = $current[$field];
} else {
return $default;
}
}
return $current;
}
/**
* Sey value by using dot notation for nested arrays/objects.
*
* @example $value = $data->set('this.is.my.nested.variable', true);
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value New value.
* @param string $separator Separator, defaults to '.'
*/
public function set($name, $value, $separator = '.')
{
$path = explode($separator, $name);
$current = &$this->items;
foreach ($path as $field) {
if (is_object($current)) {
// Handle objects.
if (!isset($current->{$field})) {
$current->{$field} = array();
}
$current = &$current->{$field};
} else {
// Handle arrays and scalars.
if (!is_array($current)) {
$current = array($field => array());
} elseif (!isset($current[$field])) {
$current[$field] = array();
}
$current = &$current[$field];
}
}
$current = $value;
}
}

View File

@@ -375,7 +375,7 @@ class Validation
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeDatetime_local($value, array $params, array $field)
public static function typeDatetimeLocal($value, array $params, array $field)
{
return self::typeDatetime($value, $params, $field);
}
@@ -517,7 +517,7 @@ class Validation
public static function validateRequired($value, $params)
{
return (bool) $params != true || !empty($value);
return (bool) $params !== true || !empty($value);
}
public static function validatePattern($value, $params)

View File

@@ -1,10 +1,8 @@
<?php
namespace Grav\Common;
use DebugBar\Bridge\Twig\TraceableTwigEnvironment;
use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
//use \Tracy\Debugger as TracyDebugger;
/**
* Class Debugger
@@ -48,9 +46,11 @@ class Debugger
public function addAssets()
{
if ($this->enabled()) {
$assets = $this->grav['assets'];
// Add jquery library
$assets->add('jquery', 101);
$this->renderer = $this->debugbar->getJavascriptRenderer();
$this->renderer->setIncludeVendors(false);

View File

@@ -4,9 +4,6 @@ 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;
/**
@@ -23,7 +20,7 @@ class Errors extends \Whoops\Run
}
if (!$handler instanceof HandlerInterface) {
throw new InvalidArgumentException(
throw new \InvalidArgumentException(
"Argument to " . __METHOD__ . " must be a callable, or instance of"
. "Whoops\\Handler\\HandlerInterface"
);

View File

@@ -21,9 +21,7 @@ class SimplePageHandler extends Handler
*/
public function handle()
{
$exception = $this->getException();
$inspector = $this->getInspector();
$run = $this->getRun();
$helper = new TemplateHelper();
$templateFile = $this->getResource("layout.html.php");
@@ -46,6 +44,11 @@ class SimplePageHandler extends Handler
return Handler::QUIT;
}
/**
* @param $resource
*
* @return string
*/
protected function getResource($resource)
{
// If the resource was found before, we can speed things up
@@ -67,7 +70,7 @@ class SimplePageHandler extends Handler
}
// If we got this far, nothing was found.
throw new RuntimeException(
throw new \RuntimeException(
"Could not find resource '$resource' in any resource paths."
. "(searched: " . join(", ", $this->searchPaths). ")"
);
@@ -76,7 +79,7 @@ class SimplePageHandler extends Handler
public function addResourcePath($path)
{
if (!is_dir($path)) {
throw new InvalidArgumentException(
throw new \InvalidArgumentException(
"'$path' is not a valid directory"
);
}

View File

@@ -19,12 +19,13 @@ abstract class Folder
{
$last_modified = 0;
$directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$filterItr = new RecursiveFolderFilterIterator($dirItr);
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
$dir_modified = $file->getMTime();
foreach ($itr as $dir) {
$dir_modified = $dir->getMTime();
if ($dir_modified > $last_modified) {
$last_modified = $dir_modified;
}
@@ -33,6 +34,34 @@ abstract class Folder
return $last_modified;
}
/**
* 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, \RecursiveDirectoryIterator::SKIP_DOTS);
$filterItr = new RecursiveFileFilterIterator($dirItr);
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
/** @var \RecursiveDirectoryIterator $file */
foreach ($itr as $file) {
if ($file->isDir()) {
continue;
}
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
}
}
return $last_modified;
}
/**
* Get relative path between target and base path. If path isn't relative, return full path.
*
@@ -43,8 +72,8 @@ abstract class Folder
public static function getRelativePath($path, $base = GRAV_ROOT)
{
if ($base) {
$base = preg_replace('![\\|/]+!', '/', $base);
$path = preg_replace('![\\|/]+!', '/', $path);
$base = preg_replace('![\\\/]+!', '/', $base);
$path = preg_replace('![\\\/]+!', '/', $path);
if (strpos($path, $base) === 0) {
$path = ltrim(substr($path, strlen($base)), '/');
}
@@ -68,30 +97,7 @@ abstract class Folder
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.
@@ -295,20 +301,3 @@ abstract class Folder
return @rmdir($folder);
}
}
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,13 @@
<?php
namespace Grav\Common\Filesystem;
class RecursiveFileFilterIterator extends \RecursiveFilterIterator
{
public static $FILTERS = ['.DS_Store'];
public function accept()
{
// Ensure any filtered file names are skipped
return !in_array($this->current()->getFilename(), self::$FILTERS, true);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Grav\Common\Filesystem;
class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
{
public function accept()
{
// only accept directories
return $this->current()->isDir();
}
}

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
<?php
namespace Grav\Common\GPM\Common;
use Grav\Common\Iterator;
class CachedCollection extends Iterator {
protected static $cache;
public function __construct($items)
{
// local cache to speed things up
if (!isset(self::$cache[get_called_class().__METHOD__])) {
self::$cache[get_called_class().__METHOD__] = $items;
}
foreach (self::$cache[get_called_class().__METHOD__] as $name => $item) {
$this->append([$name => $item]);
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Grav\Common\GPM\Common;
use Grav\Common\Data\Data;
class Package {
protected $data;
public function __construct(Data $package, $type = null) {
$this->data = $package;
if ($type) {
$this->data->set('package_type', $type);
}
}
public function getData() {
return $this->data;
}
public function __get($key) {
return $this->data->get($key);
}
public function __isset($key) {
return isset($this->data->$key);
}
public function __toString() {
return $this->toJson();
}
public function toJson() {
return $this->data->toJson();
}
public function toArray() {
return $this->data->toArray();
}
}

View File

@@ -1,7 +1,9 @@
<?php
namespace Grav\Common\GPM;
use Grav\Common\Inflector;
use Grav\Common\Iterator;
use Grav\Common\Utils;
class GPM extends Iterator
{
@@ -28,6 +30,8 @@ class GPM extends Iterator
*/
protected $cache;
protected $install_paths = ['plugins' => 'user/plugins/%name%', 'themes' => 'user/themes/%name%', 'skeletons' => 'user/'];
/**
* 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
@@ -347,7 +351,20 @@ class GPM extends Iterator
$packages = ['total' => 0, 'not_found' => []];
foreach ($searches as $search) {
$repository = '';
// if this is an object, get the search data from the key
if (is_object($search)) {
$search = (array) $search;
$key = key($search);
$repository = $search[$key];
$search = $key;
}
if ($found = $this->findPackage($search)) {
// set override respository if provided
if ($repository) {
$found->override_repository = $repository;
}
if (!isset($packages[$found->package_type])) {
$packages[$found->package_type] = [];
}
@@ -355,7 +372,20 @@ class GPM extends Iterator
$packages[$found->package_type][$found->slug] = $found;
$packages['total']++;
} else {
$packages['not_found'][] = $search;
// make a best guess at the type based on the repo URL
if (Utils::contains($repository, '-theme')) {
$type = 'themes';
} else {
$type = 'plugins';
}
$not_found = new \stdClass();
$not_found->name = Inflector::camelize($search);
$not_found->slug = $search;
$not_found->package_type = $type;
$not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]);
$not_found->override_repository = $repository;
$packages['not_found'][$search] = $not_found;
}
}

View File

@@ -61,15 +61,12 @@ class Installer
$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'])
) {
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']
) {
if (self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] ||
self::lastErrorCode() == self::EXISTS && !$options['overwrite']) {
return false;
}
@@ -112,7 +109,7 @@ class Installer
}
public static function nonSophisticatedInstall($zip, $install_path, $tmp)
public static function nonSophisticatedInstall(\ZipArchive $zip, $install_path, $tmp)
{
$container = $zip->getNameIndex(0); // TODO: better way of determining if zip has container folder
if (file_exists($install_path)) {
@@ -124,7 +121,7 @@ class Installer
return true;
}
public static function sophisticatedInstall($zip, $install_path, $tmp)
public static function sophisticatedInstall(\ZipArchive $zip, $install_path, $tmp)
{
for ($i = 0, $l = $zip->numFiles; $i < $l; $i++) {
$filename = $zip->getNameIndex($i);

View File

@@ -0,0 +1,16 @@
<?php
namespace Grav\Common\GPM\Local;
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
use Grav\Common\GPM\Local\Package;
abstract class AbstractPackageCollection extends BaseCollection {
public function __construct($items)
{
foreach ($items as $name => $data) {
$this->items[$name] = new Package($data, $this->type);
}
}
}

View File

@@ -2,39 +2,24 @@
namespace Grav\Common\GPM\Local;
use Grav\Common\Data\Data;
use Grav\Common\GPM\Common\Package as BasePackage;
/**
* Class Package
* @package Grav\Common\GPM\Local
*/
class Package
class Package extends BasePackage
{
/**
* @var Data
*/
protected $data;
/**
* @var \Grav\Common\Data\Blueprint
*/
protected $blueprints;
protected $settings;
/**
* @param Data $package
* @param bool $package_type
*/
public function __construct(Data $package, $package_type = false)
public function __construct(Data $package, $package_type = null)
{
$this->data = $package;
$this->blueprints = $this->data->blueprints();
$data = new Data($package->blueprints()->toArray());
parent::__construct($data, $package_type);
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));
}
$this->settings = $package->toArray();
$html_description = \Parsedown::instance()->line($this->description);
$this->data->set('slug', $this->name);
$this->data->set('description_html', $html_description);
$this->data->set('description_plain', strip_tags($html_description));
$this->data->set('symlink', is_link(USER_DIR . $package_type . DS . $this->name));
}
/**
@@ -42,47 +27,6 @@ class Package
*/
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();
return $this->settings['enabled'];
}
}

View File

@@ -1,28 +1,17 @@
<?php
namespace Grav\Common\GPM\Local;
use Grav\Common\Iterator;
use Grav\Common\GPM\Common\CachedCollection;
class Packages extends Iterator
class Packages extends CachedCollection
{
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()
];
}
$items = [
'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]);
parent::__construct($items);
}
}

View File

@@ -5,22 +5,18 @@ namespace Grav\Common\GPM\Local;
* Class Plugins
* @package Grav\Common\GPM\Local
*/
class Plugins extends Collection
class Plugins extends AbstractPackageCollection
{
/**
* @var string
*/
private $type = 'plugins';
protected $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);
}
parent::__construct(self::getGrav()['plugins']->all());
}
}

View File

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

View File

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

View File

@@ -0,0 +1,55 @@
<?php
namespace Grav\Common\GPM\Remote;
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
use Grav\Common\GPM\Response;
use \Doctrine\Common\Cache\FilesystemCache;
class AbstractPackageCollection extends BaseCollection
{
/**
* The cached data previously fetched
* @var string
*/
protected $raw;
/**
* The lifetime to store the entry in seconds
* @var integer
*/
private $lifetime = 86400;
protected $repository;
protected $cache;
public function __construct($repository = null, $refresh = false, $callback = null)
{
if ($repository === null) {
throw new \RuntimeException("A repository is required to indicate the origin of the remote collection");
}
$cache_dir = self::getGrav()['locator']->findResource('cache://gpm', true, true);
$this->cache = new FilesystemCache($cache_dir);
$this->repository = $repository;
$this->raw = $this->cache->fetch(md5($this->repository));
$this->fetch($refresh, $callback);
foreach (json_decode($this->raw, true) as $slug => $data) {
$this->items[$slug] = new Package($data, $this->type);
}
}
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

@@ -1,70 +0,0 @@
<?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

@@ -1,9 +1,11 @@
<?php
namespace Grav\Common\GPM\Remote;
class Grav extends Collection
use \Doctrine\Common\Cache\FilesystemCache;
class Grav extends AbstractPackageCollection
{
private $repository = 'http://getgrav.org/downloads/grav.json';
protected $repository = 'http://getgrav.org/downloads/grav.json';
private $data;
private $version;
@@ -15,15 +17,17 @@ class Grav extends Collection
*/
public function __construct($refresh = false, $callback = null)
{
parent::__construct($this->repository);
$cache_dir = self::getGrav()['locator']->findResource('cache://gpm', true, true);
$this->cache = new FilesystemCache($cache_dir);
$this->raw = $this->cache->fetch(md5($this->repository));
$this->fetch($refresh, $callback);
$this->data = json_decode($this->raw);
$this->version = @$this->data->version ?: '-';
$this->date = @$this->data->date ?: '-';
$this->data = json_decode($this->raw, true);
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
foreach ($this->data->assets as $slug => $data) {
foreach ($this->data['assets'] as $slug => $data) {
$this->items[$slug] = new Package($data);
}
}
@@ -34,7 +38,7 @@ class Grav extends Collection
*/
public function getAssets()
{
return $this->data->assets;
return $this->data['assets'];
}
/**
@@ -46,11 +50,11 @@ class Grav extends Collection
public function getChangelog($diff = null)
{
if (!$diff) {
return $this->data->changelog;
return $this->data['changelog'];
}
$diffLog = [];
foreach ($this->data->changelog as $version => $changelog) {
foreach ($this->data['changelog'] as $version => $changelog) {
preg_match("/[\d\.]+/", $version, $cleanVersion);
if (!$cleanVersion || version_compare($diff, $cleanVersion[0], ">=")) { continue; }

View File

@@ -1,32 +1,12 @@
<?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;
}
}
use Grav\Common\Data\Data;
use Grav\Common\GPM\Common\Package as BasePackage;
public function getData() {
return $this->data;
class Package extends BasePackage {
public function __construct($package, $package_type = null) {
$data = new Data($package);
parent::__construct($data, $package_type);
}
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

@@ -1,28 +1,17 @@
<?php
namespace Grav\Common\GPM\Remote;
use Grav\Common\Iterator;
use Grav\Common\GPM\Common\CachedCollection;
class Packages extends Iterator
class Packages extends CachedCollection
{
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)
];
}
$items = [
'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]);
parent::__construct($items);
}
}

View File

@@ -1,21 +1,24 @@
<?php
namespace Grav\Common\GPM\Remote;
class Plugins extends Collection
/**
* Class Plugins
* @package Grav\Common\GPM\Remote
*/
class Plugins extends AbstractPackageCollection
{
private $repository = 'http://getgrav.org/downloads/plugins.json';
private $type = 'plugins';
private $data;
/**
* @var string
*/
protected $type = 'plugins';
protected $repository = 'http://getgrav.org/downloads/plugins.json';
/**
* Local Plugins Constructor
*/
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);
}
parent::__construct($this->repository, $refresh, $callback);
}
}

View File

@@ -1,21 +1,24 @@
<?php
namespace Grav\Common\GPM\Remote;
class Themes extends Collection
/**
* Class Themes
* @package Grav\Common\GPM\Remote
*/
class Themes extends AbstractPackageCollection
{
private $repository = 'http://getgrav.org/downloads/themes.json';
private $type = 'themes';
private $data;
/**
* @var string
*/
protected $type = 'themes';
protected $repository = 'http://getgrav.org/downloads/themes.json';
/**
* Local Themes Constructor
*/
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);
}
parent::__construct($this->repository, $refresh, $callback);
}
}

View File

@@ -78,7 +78,6 @@ class Response
$method = 'get' . ucfirst(strtolower(self::$method));
self::$callback = $callback;
return static::$method($uri, $options, $callback);
}

View File

@@ -1,9 +1,6 @@
<?php
namespace Grav\Common\GPM;
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\Installer;
class Upgrader
{
/**

View File

@@ -2,6 +2,7 @@
namespace Grav\Common;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Pages;
use Grav\Common\Service\ConfigServiceProvider;
use Grav\Common\Service\ErrorServiceProvider;
@@ -10,7 +11,6 @@ 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
@@ -99,34 +99,34 @@ class Grav extends Container
/** @var Pages $pages */
$pages = $c['pages'];
// If base URI is set, we want to remove it from the URL.
$path = '/' . ltrim(Folder::getRelativePath($c['uri']->route(), $pages->base()), '/');
/** @var Uri $uri */
$uri = $c['uri'];
$path = $uri->path();
$page = $pages->dispatch($path);
if (!$page || !$page->routable()) {
$path_parts = pathinfo($path);
$page = $c['pages']->dispatch($path_parts['dirname'], true);
if ($page) {
$media = $page->media()->all();
// special case where a media file is requested
if (!$page) {
$path_parts = pathinfo($path);
$parsed_url = parse_url(urldecode($uri->basename()));
$page = $c['pages']->dispatch($path_parts['dirname']);
if ($page) {
$media = $page->media()->all();
$media_file = urldecode($path_parts['basename']);
if (isset($media[$media_file])) {
$medium = $media[$media_file];
$media_file = $parsed_url['path'];
// 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));
}
// if this is a media object, try actions first
if (isset($media[$media_file])) {
$medium = $media[$media_file];
foreach ($uri->query(null, true) as $action => $params) {
if (in_array($action, ImageMedium::$magic_actions)) {
call_user_func_array(array(&$medium, $action), explode(',', $params));
}
header('Content-type: '. $mime = $medium->get('mime'));
echo file_get_contents($medium->path());
die;
}
Utils::download($medium->path(), false);
} else {
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), true);
}
}
@@ -174,18 +174,23 @@ class Grav extends Container
ob_start('ob_gzhandler');
}
/** @var Debugger $debugger */
$debugger = $this['debugger'];
// Initialize configuration.
$debugger->startTimer('_config', 'Configuration');
$this['config']->init();
$this['uri']->init();
$this['errors']->resetHandlers();
$debugger->init();
$this['config']->debug();
$debugger->stopTimer('_config');
// Initialize the timezone
if ($this['config']->get('system.timezone')) {
date_default_timezone_set($this['config']->get('system.timezone'));
}
$debugger->startTimer('streams', 'Streams');
$this['streams'];
$debugger->stopTimer('streams');
@@ -252,7 +257,13 @@ class Grav extends Container
$this['session']->close();
}
header("Location: " . rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/'), true, $code);
if ($this['uri']->isExternal($route)) {
$url = $route;
} else {
$url = rtrim($uri->rootUrl(), '/') .'/'. trim($route, '/');
}
header("Location: {$url}", true, $code);
exit();
}
@@ -287,10 +298,11 @@ class Grav extends Container
$extension = $this['uri']->extension();
header('Content-type: ' . $this->mime($extension));
header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + $this['config']->get('system.pages.expires')));
// Set debugger data in headers
if (!($extension == null || $extension == 'html')) {
if (!($extension === null || $extension == 'html')) {
$this['debugger']->enabled(false);
// $this['debugger']->sendDataInHeaders();
}
// Set HTTP response code
@@ -337,7 +349,7 @@ class Grav extends Container
header("Connection: close\r\n");
ob_end_flush(); // regular buffer
ob_flush();
@ob_flush();
flush();
if (function_exists('fastcgi_finish_request')) {

View File

@@ -11,8 +11,11 @@ trait GravTrait
/**
* @return Grav
*/
public function getGrav()
public static function getGrav()
{
if (!self::$grav) {
self::$grav = Grav::instance();
}
return self::$grav;
}

View File

@@ -84,10 +84,43 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
*/
public function nth($key)
{
$items = array_values($this->items);
$items = array_keys($this->items);
return (isset($items[$key])) ? $this->offsetGet($items[$key]) : false;
}
/**
* Get the first item
*
* @return mixed
*/
public function first()
{
$items = array_keys($this->items);
return $this->offsetGet(array_shift($items));
}
/**
* Get the last item
*
* @return mixed
*/
public function last()
{
$items = array_keys($this->items);
return $this->offsetGet(array_pop($items));
}
/**
* Reverse the Iterator
*
* @return $this
*/
public function reverse()
{
$this->items = array_reverse($this->items);
return $this;
}
/**
* @param mixed $needle Searched value.
* @return string|bool Key if found, otherwise false.
@@ -164,4 +197,23 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
return $this;
}
/**
* Filter elements from the list
* @param callable|null $callback A function the receives ($value, $key) and must return a boolean to indicate filter status
* @return $this
*/
public function filter(callable $callback = null)
{
foreach ($this->items as $key => $value) {
if (
($callback && !call_user_func($callback, $value, $key)) ||
(!$callback && !(bool) $value)
) {
unset($this->items[$key]);
}
}
return $this;
}
}

View File

@@ -4,7 +4,7 @@ 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\Page\Medium\Medium;
use Grav\Common\Uri;
/**
@@ -14,11 +14,12 @@ trait ParsedownGravTrait
{
use GravTrait;
protected $page;
protected $pages;
protected $base_url;
protected $pages_dir;
protected $special_chars;
protected $twig_link_regex = '/\!*\[(?:.*)\]\(([{{|{%|{#].*[#}|%}|}}])\)/';
protected $twig_link_regex = '/\!*\[(?:.*)\]\((\{([\{%#])\s*(.*?)\s*(?:\2|\})\})\)/';
/**
* Initialiazation function to setup key variables needed by the MarkdownGravLinkTrait
@@ -28,10 +29,29 @@ trait ParsedownGravTrait
protected function init($page)
{
$this->page = $page;
$this->pages = self::getGrav()['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->base_url = rtrim(self::getGrav()['base_url'] . self::getGrav()['pages']->base(), '/');
$this->pages_dir = self::getGrav()['locator']->findResource('page://');
$this->special_chars = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
$defaults = self::getGrav()['config']->get('system.pages.markdown');
$this->setBreaksEnabled($defaults['auto_line_breaks']);
$this->setUrlsLinked($defaults['auto_url_links']);
$this->setMarkupEscaped($defaults['escape_markup']);
$this->setSpecialChars($defaults['special_chars']);
}
/**
* Make the element function publicly accessible, Medium uses this to render from Twig
*
* @param array $Element
* @return string markup
*/
public function elementToHtml(array $Element)
{
return $this->element($Element);
}
/**
@@ -63,7 +83,7 @@ trait ParsedownGravTrait
protected function inlineSpecialCharacter($Excerpt)
{
if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) {
if ($Excerpt['text'][0] === '&' && ! preg_match('/^&#?\w+;/', $Excerpt['text'])) {
return array(
'markup' => '&amp;',
'extent' => 1,
@@ -90,81 +110,73 @@ trait ParsedownGravTrait
$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'] ?: '';
$class = isset($excerpt['element']['attributes']['class']) ? $excerpt['element']['attributes']['class'] : '';
//get the url and parse it
$url = parse_url(htmlspecialchars_decode($excerpt['element']['attributes']['src']));
//get back to current page if possible
$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 media objects for this page
$media = $this->page->media();
// get the local path to page media if possible
if (strpos($url['path'], $this->page->url()) !== false) {
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 (isset($media->images()[$url['path']])) {
if ($media && isset($media->all()[$url['path']])) {
// get the medium object
$medium = $media->images()[$url['path']];
$medium = $media->all()[$url['path']];
// if there is a query, then parse it and build action calls
if (isset($url['query'])) {
parse_str($url['query'], $actions);
$actions = array_reduce(explode('&', $url['query']), function ($carry, $item) {
$parts = explode('=', $item, 2);
$value = isset($parts[1]) ? $parts[1] : null;
$carry[] = [ 'method' => $parts[0], 'params' => $value ];
return $carry;
}, []);
}
// 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));
}
foreach ($actions as $action) {
$medium = call_user_func_array(array($medium, $action['method']), explode(',', $action['params']));
}
// Get the URL for regular images, or an array of bits needed to put together
// the lightbox HTML
if (!isset($actions['lightbox'])) {
$src = $medium->url();
} else {
$src = $medium->lightboxRaw();
if (isset($url['fragment'])) {
$medium->urlHash($url['fragment']);
}
// set the src element with the new generated url
if (!isset($actions['lightbox']) && !is_array($src)) {
$excerpt['element']['attributes']['src'] = $src;
} else {
// Create the custom lightbox element
$element = array(
'name' => 'a',
'attributes' => array('rel' => $src['a_rel'], 'href' => $src['a_url']),
'handler' => 'element',
'text' => array(
'name' => 'img',
'attributes' => array('src' => $src['img_url'], 'alt' => $alt, 'title' => $title)
),
);
$excerpt['element'] = $medium->parseDownElement($title, $alt, $class);
// 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'] = $this->convertUrl(Uri::build_url($url));
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url);
}
}
}
@@ -187,14 +199,12 @@ trait ParsedownGravTrait
// 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'])) {
if (!isset($url['scheme']) && (count($url) > 0)) {
// convert the URl is required
$excerpt['element']['attributes']['href'] = $this->convertUrl(Uri::build_url($url));
$excerpt['element']['attributes']['href'] = $this->convertUrl(Uri::buildUrl($url));
}
}
@@ -210,17 +220,17 @@ trait ParsedownGravTrait
{
// if absolute and starts with a base_url move on
if ($this->base_url != '' && strpos($markdown_url, $this->base_url) === 0) {
$new_url = $markdown_url;
return $markdown_url;
// if its absolute and starts with /
} elseif (strpos($markdown_url, '/') === 0) {
$new_url = $this->base_url . $markdown_url;
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);
// If this is a 'real' filepath clean it up
if (file_exists($this->page->path() . '/' . parse_url($markdown_url, PHP_URL_PATH))) {
$relative_path = $this->base_url . preg_replace('/\/([\d]+.)/', '/', str_replace($this->pages_dir, '', $this->page->path()));
$markdown_url = preg_replace('/^([\d]+.)/', '', preg_replace('/\/([\d]+.)/', '/', trim(preg_replace('/[^\/]+(\.md$)/', '', $markdown_url), '/')));
// 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

View File

@@ -5,6 +5,8 @@ use Grav\Common\Getters;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Grav\Common\GravTrait;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Medium\MediumFactory;
/**
* Media is a holder object that contains references to the media of page. This object is created and
@@ -38,88 +40,93 @@ class Media extends Getters
$this->path = $path;
$iterator = new \DirectoryIterator($path);
$iterator = new \FilesystemIterator($path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$media = [];
/** @var \DirectoryIterator $info */
foreach ($iterator as $info) {
foreach ($iterator as $path => $info) {
// Ignore folders and Markdown files.
if ($info->isDot() || !$info->isFile() || $info->getExtension() == 'md') {
if (!$info->isFile() || $info->getExtension() == 'md') {
continue;
}
// Find out the real filename, in case of we are at the metadata.
$filename = $info->getFilename();
list($basename, $ext, $meta) = $this->getFileParts($filename);
// Find out what type we're dealing with
list($basename, $ext, $type, $extra) = $this->getFileParts($info->getFilename());
$media["{$basename}.{$ext}"] = isset($media["{$basename}.{$ext}"]) ? $media["{$basename}.{$ext}"] : [];
if ($type === 'alternative') {
$media["{$basename}.{$ext}"][$type] = isset($media["{$basename}.{$ext}"][$type]) ? $media["{$basename}.{$ext}"][$type] : [];
$media["{$basename}.{$ext}"][$type][$extra] = $path;
} else {
$media["{$basename}.{$ext}"][$type] = $path;
}
}
foreach ($media as $name => $types) {
// First prepare the alternatives in case there is no base medium
if (!empty($types['alternative'])) {
foreach ($types['alternative'] as $ratio => &$file) {
$file = MediumFactory::fromFile($file);
}
}
// Create the base medium
if (!empty($types['base'])) {
$medium = MediumFactory::fromFile($types['base']);
} else if (!empty($types['alternative'])) {
$altMedium = reset($types['alternative']);
$ratio = key($types['alternative']);
$medium = MediumFactory::scaledFromMedium($altMedium, $ratio, 1);
}
// Get medium instance creating it if it didn't exist.
$medium = $this->get("{$basename}.{$ext}", true);
if (!$medium) {
continue;
}
//set file size
$medium->set('size', $info->getSize());
// Assign meta files to the medium.
if ($meta) {
$medium->addMetaFile($meta);
if (!empty($types['meta'])) {
$medium->addMetaFile($types['meta']);
}
if (!empty($types['thumb'])) {
// We will not turn it into medium yet because user might never request the thumbnail
// not wasting any resources on that, maybe we should do this for medium in general?
$medium->set('thumbnails.page', $types['thumb']);
}
// Build missing alternatives
if (!empty($types['alternative'])) {
$alternatives = $types['alternative'];
$max = max(array_keys($alternatives));
for ($i=2; $i < $max; $i++) {
if (isset($alternatives[$i])) {
continue;
}
$types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max], $max, $i);
}
foreach ($types['alternative'] as $ratio => $altMedium) {
$medium->addAlternative($ratio, $altMedium);
}
}
$this->add($name, $medium);
}
}
/**
* 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 = self::$grav['config'];
// Check if medium type has been configured.
$params = $config->get("media.".strtolower($ext));
if (!$params) {
return null;
}
$filePath = $this->path . '/' . $filename;
// 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' => $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;
}
@@ -181,21 +188,21 @@ class Media extends Getters
/**
* @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[$file->filename] = $file;
$this->audios[$name] = $file;
break;
default:
$this->files[$file->filename] = $file;
$this->files[$name] = $file;
}
}
@@ -210,19 +217,35 @@ class Media extends Getters
$fileParts = explode('.', $filename);
$name = array_shift($fileParts);
$extension = null;
while (($part = array_shift($fileParts)) !== null) {
if ($part != 'meta') {
if (isset($extension)) {
$name .= '.' . $extension;
$type = 'base';
$extra = null;
if (preg_match('/(.*)@(\d+)x\.(.*)$/', $filename, $matches)) {
$name = $matches[1];
$extension = $matches[3];
$extra = (int) $matches[2];
$type = 'alternative';
if ($extra === 1) {
$type = 'base';
$extra = null;
}
} else {
$extension = null;
while (($part = array_shift($fileParts)) !== null) {
if ($part != 'meta' && $part != 'thumb') {
if (isset($extension)) {
$name .= '.' . $extension;
}
$extension = $part;
} else {
$type = $part;
$extra = '.' . $part . '.' . implode('.', $fileParts);
break;
}
$extension = $part;
} else {
break;
}
}
$meta = implode('.', $fileParts);
return array($name, $extension, $meta);
return array($name, $extension, $type, $extra);
}
}

View File

@@ -1,382 +0,0 @@
<?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\Data\Data;
use Gregwar\Image\Image as ImageFile;
/**
* The Image medium holds information related to an individual image. These are then stored in the Media object.
*
* @author RocketTheme
* @license MIT
*
* @property string $file_name
* @property string $type
* @property string $name Alias of file_name
* @property string $description
* @property string $url
* @property string $path
* @property string $thumb
* @property int $width
* @property int $height
* @property string $mime
* @property int $modified
*
* Medium can have up to 3 files:
* - video.mov Medium file itself.
* - video.mov.meta.yaml Metadata for the medium.
* - video.mov.thumb.jpg Thumbnail image for the medium.
*
*/
class Medium extends Data
{
use GravTrait;
/**
* @var string
*/
protected $path;
/**
* @var ImageFile
*/
protected $image;
protected $type = 'guess';
protected $quality = 85;
public static $valid_actions = ['resize', 'forceResize', 'cropResize', 'crop', 'cropZoom',
'negate', 'brightness', 'contrast', 'grayscale', 'emboss', 'smooth', 'sharp', 'edge', 'colorize', 'sepia' ];
/**
* @var array
*/
protected $meta = array();
/**
* @var string
*/
protected $linkTarget;
/**
* @var string
*/
protected $linkAttributes;
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') {
$image_info = getimagesize($file_path);
$this->def('width', $image_info[0]);
$this->def('height', $image_info[1]);
$this->def('mime', $image_info['mime']);
$this->reset();
} else {
$this->def('mime', 'application/octet-stream');
}
}
/**
* Return string representation of the object (html or url).
*
* @return string
*/
public function __toString()
{
return $this->linkImage ? $this->html() : $this->url();
}
/**
* Return PATH to file.
*
* @return string path to file
*/
public function path()
{
/** @var Config $config */
$config = self::$grav['config'];
if ($this->image) {
$output = $this->image->cacheFile($this->type, $this->quality);
$this->reset();
$output = ROOT_DIR . $output;
} else {
$output = $this->get('path') . '/' . $this->get('filename');
}
return $output;
}
/**
* Sets the quality of the image
* @param Int $quality 0-100 quality
* @return Medium
*/
public function quality($quality)
{
$this->quality = $quality;
return $this;
}
/**
* Return URL to file.
*
* @return string
*/
public function url()
{
if ($this->image) {
$output = $this->image->cacheFile($this->type, $this->quality);
$this->reset();
} else {
$relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path'));
$output = $relPath . '/' . $this->get('filename');
}
return self::$grav['base_url'] . '/'. $output;
}
/**
* Sets image output format.
*
* @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;
}
/**
* Returns <img> tag from the medium.
*
* @param string $title
* @param string $class
* @param string $type
* @param int $quality
* @return string
*/
public function img($title = null, $class = null, $type = null, $quality = 80)
{
if (!$this->image) {
$this->image();
}
$output = $this->html($title, $class, $type, $quality);
return $output;
}
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $class
* @param string $type
* @param int $quality
* @return string
*/
public function html($title = null, $class = null, $type = null, $quality = 80)
{
$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 . '" />';
} else {
$output = $title;
}
if ($this->linkTarget) {
/** @var Config $config */
$config = self::$grav['config'];
$output = '<a href="' . self::$grav['base_url'] . '/'. $this->linkTarget
. '"' . $this->linkAttributes. ' class="'. $class . '">' . $output . '</a>';
$this->linkTarget = $this->linkAttributes = null;
}
return $output;
}
/**
* Return lightbox HTML for the medium.
*
* @param int $width
* @param int $height
* @return $this
*/
public function lightbox($width = null, $height = null)
{
$this->linkAttributes = ' rel="lightbox"';
return $this->link($width, $height);
}
public function lightboxRaw($width = null, $height = null)
{
/** @var Config $config */
$config = self::$grav['config'];
$url = $this->url();
$this->link($width, $height);
$lightbox_url = self::$grav['base_url'] . '/'. $this->linkTarget;
return array('a_url' => $lightbox_url, 'a_rel' => 'lightbox', 'img_url' => $url);
}
/**
* Return link HTML for the medium.
*
* @param int $width
* @param int $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->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');
}
return $this;
}
/**
* Reset image.
*
* @return $this
*/
public function reset()
{
$this->image = null;
if ($this->get('type') == 'image') {
$this->image();
$this->filter();
}
$this->type = 'guess';
$this->quality = 80;
return $this;
}
/**
* Forward the call to the image processing method.
*
* @param string $method
* @param mixed $args
* @return $this|mixed
*/
public function __call($method, $args)
{
if ($method == 'cropZoom') {
$method = 'zoomCrop';
}
// Always initialize image.
if (!$this->image) {
$this->image();
}
try {
$result = call_user_func_array(array($this->image, $method), $args);
} catch (\BadFunctionCallException $e) {
$result = null;
}
// Returns either current object or result of the action.
return $result instanceof ImageFile ? $this : $result;
}
/**
* Gets medium image, resets image manipulation operations.
*
* @param string $variable
* @return $this
*/
public function image($variable = 'thumb')
{
// TODO: add default file
$file = $this->get($variable);
$this->image = ImageFile::open($file)
->setCacheDir(basename(IMAGES_DIR))
->setActualCacheDir(IMAGES_DIR)
->setPrettyName(basename($this->get('basename')));
$this->filter();
return $this;
}
/**
* Add meta file for the medium.
*
* @param $type
* @return $this
*/
public function addMetaFile($type)
{
$this->meta[$type] = $type;
$path = $this->get('path') . '/' . $this->get('filename') . '.meta.' . $type;
if ($type == 'yaml') {
$this->merge(CompiledYamlFile::instance($path)->content());
} elseif (in_array($type, array('jpg', 'jpeg', 'png', 'gif'))) {
$this->set('thumb', $path);
}
$this->reset();
return $this;
}
/**
* Filter image by using user defined filter parameters.
*
* @param string $filter Filter to be used.
*/
public function filter($filter = 'image.filters.default')
{
$filters = (array) $this->get($filter, array());
foreach ($filters as $params) {
$params = (array) $params;
$method = array_shift($params);
$this->__call($method, $params);
}
}
}

View File

@@ -0,0 +1,397 @@
<?php
namespace Grav\Common\Page\Medium;
use Grav\Common\Data\Blueprint;
use Gregwar\Image\Image as ImageFile;
class ImageMedium extends Medium
{
/**
* @var array
*/
protected $thumbnailTypes = [ 'page', 'media', 'default' ];
/**
* @var ImageFile
*/
protected $image;
/**
* @var string
*/
protected $format = 'guess';
/**
* @var int
*/
protected $quality;
/**
* @var int
*/
protected $default_quality;
/**
* @var boolean
*/
protected $debug_watermarked = false;
/**
* @var array
*/
public static $magic_actions = [
'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop',
'negate', 'brightness', 'contrast', 'grayscale', 'emboss',
'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive'
];
/**
* @var array
*/
public static $magic_resize_actions = [
'resize' => [ 0, 1 ],
'forceResize' => [ 0, 1 ],
'cropResize' => [ 0, 1 ],
'crop' => [ 0, 1, 2, 3 ],
'cropResize' => [ 0, 1 ],
'zoomCrop' => [ 0, 1 ]
];
/**
* Construct.
*
* @param array $items
* @param Blueprint $blueprint
*/
public function __construct($items = [], Blueprint $blueprint = null)
{
parent::__construct($items, $blueprint);
$image_info = getimagesize($this->get('filepath'));
$this->def('width', $image_info[0]);
$this->def('height', $image_info[1]);
$this->def('mime', $image_info['mime']);
$this->def('debug', self::$grav['config']->get('system.images.debug'));
$this->set('thumbnails.media', $this->get('filepath'));
$this->default_quality = self::$grav['config']->get('system.images.default_image_quality', 85);
$this->reset();
}
/**
* Add meta file for the medium.
*
* @param $filepath
* @return $this
*/
public function addMetaFile($filepath)
{
parent::addMetaFile($filepath);
// Apply filters in meta file
$this->reset();
return $this;
}
/**
* Return PATH to image.
*
* @param bool $reset
* @return string path to image
*/
public function path($reset = true)
{
$output = $this->saveImage();
if ($reset) {
$this->reset();
}
return $output;
}
/**
* Return URL to image.
*
* @param bool $reset
* @return string
*/
public function url($reset = true)
{
$output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->saveImage());
if ($reset) {
$this->reset();
}
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
}
/**
* 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);
}
/**
* Parsedown element for source display mode
*
* @param array $attributes
* @param boolean $reset
* @return array
*/
public function sourceParsedownElement(array $attributes, $reset = true)
{
empty($attributes['src']) && $attributes['src'] = $this->url(false);
$srcset = $this->srcset($reset);
if ($srcset) {
empty($attributes['srcset']) && $attributes['srcset'] = $srcset;
empty($attributes['sizes']) && $attributes['sizes'] = $this->sizes();
}
return [ 'name' => 'img', 'attributes' => $attributes ];
}
/**
* Reset image.
*
* @return $this
*/
public function reset()
{
parent::reset();
if ($this->image) {
$this->image();
$this->filter();
}
$this->format = 'guess';
$this->quality = $this->default_quality;
$this->debug_watermarked = false;
return $this;
}
/**
* Turn the current Medium into a Link
*
* @param boolean $reset
* @param array $attributes
* @return Link
*/
public function link($reset = true, array $attributes = [])
{
$attributes['href'] = $this->url(false);
$srcset = $this->srcset(false);
if ($srcset) {
$attributes['data-srcset'] = $srcset;
}
return parent::link($reset, $attributes);
}
/**
* Turn the current Medium inta a Link with lightbox enabled
*
* @param int $width
* @param int $height
* @param boolean $reset
* @return Link
*/
public function lightbox($width = null, $height = null, $reset = true)
{
if ($this->mode !== 'source') {
$this->display('source');
}
if ($width && $height) {
$this->cropResize($width, $height);
}
return parent::lightbox($width, $height, $reset);
}
/**
* Sets the quality of the image
*
* @param int $quality 0-100 quality
* @return Medium
*/
public function quality($quality)
{
if (!$this->image) {
$this->image();
}
$this->quality = $quality;
return $this;
}
/**
* Sets image output format.
*
* @param string $format
* @return $this
*/
public function format($format)
{
if (!$this->image) {
$this->image();
}
$this->format = $format;
return $this;
}
/**
* Set or get sizes parameter for srcset media action
*
* @param string $sizes
* @return $this
*/
public function sizes($sizes = null)
{
if ($sizes) {
$this->attributes['sizes'] = $sizes;
return $this;
}
return empty($this->attributes['sizes']) ? '100vw' : $this->attributes['sizes'];
}
/**
* Forward the call to the image processing method.
*
* @param string $method
* @param mixed $args
* @return $this|mixed
*/
public function __call($method, $args)
{
if ($method == 'cropZoom') {
$method = 'zoomCrop';
}
if (!in_array($method, self::$magic_actions)) {
return parent::__call($method, $args);
}
// Always initialize image.
if (!$this->image) {
$this->image();
}
try {
$result = call_user_func_array([$this->image, $method], $args);
foreach ($this->alternatives as $ratio => $medium) {
$args_copy = $args;
// regular image: resize 400x400 -> 200x200
// --> @2x: resize 800x800->400x400
if (isset(self::$magic_resize_actions[$method])) {
foreach (self::$magic_resize_actions[$method] as $param) {
if (isset($args_copy[$param])) {
$args_copy[$param] = (int) $args_copy[$param] * $ratio;
}
}
}
call_user_func_array([$medium, $method], $args_copy);
}
} catch (\BadFunctionCallException $e) {
}
return $this;
}
/**
* Gets medium image, resets image manipulation operations.
*
* @return $this
*/
protected function image()
{
$locator = self::$grav['locator'];
$file = $this->get('filepath');
$cacheDir = $locator->findResource('cache://images', true);
$this->image = ImageFile::open($file)
->setCacheDir($cacheDir)
->setActualCacheDir($cacheDir)
->setPrettyName(basename($this->get('basename')));
$this->filter();
return $this;
}
/**
* Save the image with cache.
*
* @return mixed|string
*/
protected function saveImage()
{
if (!$this->image) {
return parent::path(false);
}
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));
}
$result = $this->image->cacheFile($this->format, $this->quality);
return $result;
}
/**
* Filter image by using user defined filter parameters.
*
* @param string $filter Filter to be used.
*/
public function filter($filter = 'image.filters.default')
{
$filters = (array) $this->get($filter, []);
foreach ($filters as $params) {
$params = (array) $params;
$method = array_shift($params);
$this->__call($method, $params);
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Grav\Common\Page\Medium;
use Grav\Common\GravTrait;
class Link implements RenderableInterface
{
use GravTrait;
use ParsedownHtmlTrait;
/**
* @var array
*/
protected $attributes = [];
protected $source;
/**
* Construct.
* @param array $attributes
* @param Medium $medium
*/
public function __construct(array $attributes, Medium $medium)
{
$this->attributes = $attributes;
$this->source = $medium->reset()->thumbnail('auto')->display('thumbnail');
$this->source->linked = true;
}
/**
* Get an element (is array) that can be rendered by the Parsedown engine
*
* @param string $title
* @param string $alt
* @param string $class
* @param boolean $reset
* @return array
*/
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true)
{
$innerElement = $this->source->parsedownElement($title, $alt, $class, $reset);
return [
'name' => 'a',
'attributes' => $this->attributes,
'handler' => is_string($innerElement) ? 'line' : 'element',
'text' => $innerElement
];
}
/**
* Forward the call to the source element
*
* @param string $method
* @param mixed $args
* @return $this|mixed
*/
public function __call($method, $args)
{
$this->source = call_user_func_array(array($this->source, $method), $args);
// Don't start nesting links, if user has multiple link calls in his
// actions, we will drop the previous links.
return $this->source instanceof LinkMedium ? $this->source : $this;
}
}

View File

@@ -0,0 +1,415 @@
<?php
namespace Grav\Common\Page\Medium;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\GravTrait;
use Grav\Common\Data\Data;
use Grav\Common\Data\Blueprint;
/**
* The Medium is a general class for multimedia objects in Grav pages, specific implementations will derive from
*
* @author Grav
* @license MIT
*
*/
class Medium extends Data implements RenderableInterface
{
use GravTrait;
use ParsedownHtmlTrait;
/**
* @var string
*/
protected $mode = 'source';
/**
* @var Medium
*/
protected $_thumbnail = null;
/**
* @var array
*/
protected $thumbnailTypes = [ 'page', 'default' ];
protected $thumbnailType = null;
/**
* @var Medium[]
*/
protected $alternatives = [];
/**
* @var array
*/
protected $attributes = [];
/**
* @var array
*/
protected $styleAttributes = [];
/**
* Construct.
*
* @param array $items
* @param Blueprint $blueprint
*/
public function __construct($items = [], Blueprint $blueprint = null)
{
parent::__construct($items, $blueprint);
if (self::getGrav()['config']->get('media.enable_media_timestamp', true)) {
$this->querystring('&' . self::getGrav()['cache']->getKey());
}
$this->def('mime', 'application/octet-stream');
$this->reset();
}
/**
* Add meta file for the medium.
*
* @param $filepath
*/
public function addMetaFile($filepath)
{
$this->merge(CompiledYamlFile::instance($filepath)->content());
}
/**
* 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;
}
/**
* Return string representation of the object (html).
*
* @return string
*/
public function __toString()
{
return $this->html();
}
/**
* Return PATH to file.
*
* @param bool $reset
* @return string path to file
*/
public function path($reset = true)
{
if ($reset) {
$this->reset();
}
return $this->get('filepath');
}
/**
* Return URL to file.
*
* @param bool $reset
* @return string
*/
public function url($reset = true)
{
$output = preg_replace('|^' . GRAV_ROOT . '|', '', $this->get('filepath'));
if ($reset) {
$this->reset();
}
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
}
/**
* Get/set querystring for the file's url
*
* @param string $hash
* @param boolean $withHash
* @return string
*/
public function querystring($querystring = null, $withQuestionmark = true)
{
if ($querystring) {
$this->set('querystring', ltrim($querystring, '?&'));
foreach ($this->alternatives as $alt) {
$alt->querystring($querystring, $withQuestionmark);
}
}
$querystring = $this->get('querystring', '');
if ($withQuestionmark && !empty($querystring)) {
return '?' . $querystring;
} else {
return $querystring;
}
}
/**
* Get/set hash for the file's url
*
* @param string $hash
* @param boolean $withHash
* @return string
*/
public function urlHash($hash = null, $withHash = true)
{
if ($hash) {
$this->set('urlHash', ltrim($hash, '#'));
}
$hash = $this->get('urlHash', '');
if ($withHash && !empty($hash)) {
return '#' . $hash;
} else {
return $hash;
}
}
/**
* Get an element (is array) that can be rendered by the Parsedown engine
*
* @param string $title
* @param string $alt
* @param string $class
* @param boolean $reset
* @return array
*/
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true)
{
$attributes = $this->attributes;
$style = '';
foreach ($this->styleAttributes as $key => $value) {
$style .= $key . ': ' . $value . ';';
}
$attributes['style'] = $style;
!empty($title) && empty($attributes['title']) && $attributes['title'] = $title;
!empty($alt) && empty($attributes['alt']) && $attributes['alt'] = $alt;
!empty($class) && empty($attributes['class']) && $attributes['class'] = $class;
switch ($this->mode) {
case 'text':
$element = $this->textParsedownElement($attributes, false);
break;
case 'thumbnail':
$element = $this->getThumbnail()->sourceParsedownElement($attributes, false);
break;
case 'source':
$element = $this->sourceParsedownElement($attributes, false);
break;
}
if ($reset) {
$this->reset();
}
$this->display('source');
return $element;
}
/**
* Parsedown element for source display mode
*
* @param array $attributes
* @param boolean $reset
* @return array
*/
protected function sourceParsedownElement(array $attributes, $reset = true)
{
return $this->textParsedownElement($attributes, $reset);
}
/**
* Parsedown element for text display mode
*
* @param array $attributes
* @param boolean $reset
* @return array
*/
protected function textParsedownElement(array $attributes, $reset = true)
{
$text = empty($attributes['title']) ? empty($attributes['alt']) ? $this->get('filename') : $attributes['alt'] : $attributes['title'];
$element = [
'name' => 'p',
'attributes' => $attributes,
'text' => $text
];
if ($reset) {
$this->reset();
}
return $element;
}
/**
* Reset medium.
*
* @return $this
*/
public function reset()
{
$this->attributes = [];
return $this;
}
/**
* Switch display mode.
*
* @param string $mode
*
* @return $this
*/
public function display($mode = 'source')
{
if ($this->mode === $mode) {
return $this;
}
$this->mode = $mode;
return $mode === 'thumbnail' ? $this->getThumbnail()->reset() : $this->reset();
}
/**
* Switch thumbnail.
*
* @param string $type
*
* @return $this
*/
public function thumbnail($type = 'auto')
{
if ($type !== 'auto' && !in_array($type, $this->thumbnailTypes)) {
return $this;
}
if ($this->thumbnailType !== $type) {
$this->_thumbnail = null;
}
$this->thumbnailType = $type;
return $this;
}
/**
* Turn the current Medium into a Link
*
* @param boolean $reset
* @param array $attributes
* @return Link
*/
public function link($reset = true, array $attributes = [])
{
if ($this->mode !== 'source') {
$this->display('source');
}
foreach ($this->attributes as $key => $value) {
empty($attributes['data-' . $key]) && $attributes['data-' . $key] = $value;
}
empty($attributes['href']) && $attributes['href'] = $this->url();
return new Link($attributes, $this);
}
/**
* Turn the current Medium inta a Link with lightbox enabled
*
* @param int $width
* @param int $height
* @param boolean $reset
* @return Link
*/
public function lightbox($width = null, $height = null, $reset = true)
{
$attributes = ['rel' => 'lightbox'];
if ($width && $height) {
$attributes['data-width'] = $width;
$attributes['data-height'] = $height;
}
return $this->link($reset, $attributes);
}
/**
* Allow any action to be called on this medium from twig or markdown
*
* @param string $method
* @param mixed $args
* @return $this
*/
public function __call($method, $args)
{
$qs = $method;
if (count($args) > 1 || (count($args) == 1 && !empty($args[0]))) {
$qs .= '=' . implode(',', array_map(function ($a) { return urlencode($a); }, $args));
}
if (!empty($qs)) {
$this->querystring($this->querystring(null, false) . '&' . $qs);
}
self::$grav['debugger']->addMessage($this->querystring());
return $this;
}
/**
* Get the thumbnail Medium object
*
* @return ThumbnailImageMedium
*/
protected function getThumbnail()
{
if (!$this->_thumbnail) {
$types = $this->thumbnailTypes;
if ($this->thumbnailType !== 'auto') {
array_unshift($types, $this->thumbnailType);
}
foreach ($types as $type) {
$thumb = $this->get('thumbnails.' . $type, false);
if ($thumb) {
$thumb = $thumb instanceof ThumbnailMedium ? $thumb : MediumFactory::fromFile($thumb, ['type' => 'thumbnail']);
$thumb->parent = $this;
}
if ($thumb) {
$this->_thumbnail = $thumb;
break;
}
}
}
return $this->_thumbnail;
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Grav\Common\Page\Medium;
use Grav\Common\GravTrait;
use Grav\Common\Data\Blueprint;
/**
* MediumFactory can be used to more easily create various Medium objects from files or arrays, it should
* contain most logic for instantiating a Medium object.
*
* @author Grav
* @license MIT
*
*/
class MediumFactory
{
use GravTrait;
/**
* Create Medium from a file
*
* @param string $file
* @param array $params
* @return Medium
*/
public static function fromFile($file, array $params = [])
{
if (!file_exists($file)) {
return null;
}
$path = dirname($file);
$filename = basename($file);
$parts = explode('.', $filename);
$ext = array_pop($parts);
$basename = implode('.', $parts);
$config = self::getGrav()['config'];
$media_params = $config->get("media.".strtolower($ext));
if (!$media_params) {
return null;
}
$params += $media_params;
// Add default settings for undefined variables.
$params += $config->get('media.defaults');
$params += [
'type' => 'file',
'thumb' => 'media/thumb.png',
'mime' => 'application/octet-stream',
'filepath' => $file,
'filename' => $filename,
'basename' => $basename,
'extension' => $ext,
'path' => $path,
'modified' => filemtime($file),
'thumbnails' => []
];
$locator = self::getGrav()['locator'];
$lookup = $locator->findResources('image://');
foreach ($lookup as $lookupPath) {
if (is_file($lookupPath . '/' . $params['thumb'])) {
$params['thumbnails']['default'] = $lookupPath . '/' . $params['thumb'];
break;
}
}
return static::fromArray($params);
}
/**
* Create Medium from array of parameters
*
* @param array $items
* @param Blueprint|null $blueprint
* @return Medium
*/
public static function fromArray(array $items = [], Blueprint $blueprint = null)
{
$type = isset($items['type']) ? $items['type'] : null;
switch ($type) {
case 'image':
return new ImageMedium($items, $blueprint);
break;
case 'thumbnail':
return new ThumbnailImageMedium($items, $blueprint);
break;
case 'animated':
case 'vector':
return new StaticImageMedium($items, $blueprint);
break;
case 'video':
return new VideoMedium($items, $blueprint);
break;
default:
return new Medium($items, $blueprint);
break;
}
}
/**
* Create a new ImageMedium by scaling another ImageMedium object.
*
* @param ImageMedium $medium
* @param int $from
* @param int $to
* @return Medium
*/
public static function scaledFromMedium($medium, $from, $to)
{
if (! $medium instanceof ImageMedium) {
return $medium;
}
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)->path();
$medium->set('debug', $debug);
$size = filesize($file);
$medium = self::fromFile($file);
$medium->set('size', $size);
return $medium;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Grav\Common\Page\Medium;
use Grav\Common\Markdown\Parsedown;
trait ParsedownHtmlTrait
{
/**
* @var \Grav\Common\Markdown\Parsedown
*/
protected $parsedown = null;
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $class
* @param bool $reset
* @return string
*/
public function html($title = null, $alt = null, $class = null, $reset = true)
{
$element = $this->parsedownElement($title, $alt, $class, $reset);
if (!$this->parsedown) {
$this->parsedown = new Parsedown(null);
}
return $this->parsedown->elementToHtml($element);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Grav\Common\Page\Medium;
/**
* Renderable Medium objects can be rendered to HTML markup and Parsedown objects
*
* @author Grav
* @license MIT
*
*/
interface RenderableInterface
{
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param bool $reset
* @return string
*/
public function html($title = null, $alt = null, $class = null, $reset = true);
/**
* Return Parsedown Element from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param bool $reset
* @return string
*/
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true);
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Grav\Common\Page\Medium;
/**
* The Image medium holds information related to an individual image. These are then stored in the Media object.
*
* @author Grav
* @license MIT
*
*/
class StaticImageMedium extends Medium
{
use StaticResizeTrait;
/**
* Parsedown element for source display mode
*
* @param array $attributes
* @param boolean $reset
* @return array
*/
protected function sourceParsedownElement(array $attributes, $reset = true)
{
empty($attributes['src']) && $attributes['src'] = $this->url($reset);
return [ 'name' => 'image', 'attributes' => $attributes ];
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Grav\Common\Page\Medium;
trait StaticResizeTrait
{
/**
* Resize media by setting attributes
*
* @param int $width
* @param int $height
* @return Medium
*/
public function resize($width = null, $height = null)
{
$this->styleAttributes['width'] = $width . 'px';
$this->styleAttributes['height'] = $height . 'px';
return $this;
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Grav\Common\Page\Medium;
class ThumbnailImageMedium extends ImageMedium
{
/**
* @var Medium
*/
public $parent = null;
/**
* @var boolean
*/
public $linked = false;
/**
* Return srcset string for this Medium and its alternatives.
*
* @param bool $reset
* @return string
*/
public function srcset($reset = true)
{
return '';
}
/**
* Get an element (is array) that can be rendered by the Parsedown engine
*
* @param string $title
* @param string $alt
* @param string $class
* @param boolean $reset
* @return array
*/
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true)
{
return $this->bubble('parsedownElement', [$title, $alt, $class, $reset]);
}
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param bool $reset
* @return string
*/
public function html($title = null, $alt = null, $class = null, $reset = true)
{
return $this->bubble('html', [$title, $alt, $class, $reset]);
}
/**
* Switch display mode.
*
* @param string $mode
*
* @return $this
*/
public function display($mode = 'source')
{
return $this->bubble('display', [$mode], false);
}
/**
* Switch thumbnail.
*
* @param string $type
*
* @return $this
*/
public function thumbnail($type = 'auto')
{
$this->bubble('thumbnail', [$type], false);
return $this->bubble('getThumbnail', [], false);
}
/**
* Turn the current Medium into a Link
*
* @param boolean $reset
* @param array $attributes
* @return Link
*/
public function link($reset = true, array $attributes = [])
{
return $this->bubble('link', [$reset, $attributes], false);
}
/**
* Turn the current Medium inta a Link with lightbox enabled
*
* @param int $width
* @param int $height
* @param boolean $reset
* @return Link
*/
public function lightbox($width = null, $height = null, $reset = true)
{
return $this->bubble('lightbox', [$width, $height, $reset], false);
}
/**
* Bubble a function call up to either the superclass function or the parent Medium instance
*
* @param string $method
* @param array $arguments
* @param boolean $testLinked
* @return Medium
*/
protected function bubble($method, array $arguments = [], $testLinked = true)
{
if (!$testLinked || $this->linked) {
return $this->parent ? call_user_func_array(array($this->parent, $method), $arguments) : $this;
} else {
return call_user_func_array(array($this, 'parent::' . $method), $arguments);
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Grav\Common\Page\Medium;
use Grav\Common\Config\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Data;
use Gregwar\Image\Image as ImageFile;
class VideoMedium extends Medium
{
use StaticResizeTrait;
/**
* Parsedown element for source display mode
*
* @param array $attributes
* @param boolean $reset
* @return array
*/
protected function sourceParsedownElement(array $attributes, $reset = true)
{
$location = $this->url($reset);
return [
'name' => 'video',
'text' => '<source src="' . $location . '">Your browser does not support the video tag.',
'attributes' => $attributes
];
}
/**
* Reset medium.
*
* @return $this
*/
public function reset()
{
parent::reset();
$this->attributes['controls'] = true;
return $this;
}
}

View File

@@ -90,13 +90,11 @@ class Page
/**
* Page Object Constructor
*
* @param array $array An array of existing page objects
*/
public function __construct($array = array())
public function __construct()
{
/** @var Config $config */
$config = self::$grav['config'];
$config = self::getGrav()['config'];
$this->routable = true;
$this->taxonomy = array();
@@ -110,7 +108,7 @@ class Page
* @param \SplFileInfo $file The file information for the .md file that the page represents
* @return void
*/
public function init($file)
public function init(\SplFileInfo $file)
{
$this->filePath($file->getPathName());
$this->modified($file->getMTime());
@@ -123,20 +121,20 @@ class Page
$this->modularTwig($this->slug[0] == '_');
// Handle publishing dates if no explict published option set
if (self::$grav['config']->get('system.pages.publish_dates') && !isset($this->header->published)) {
if (self::getGrav()['config']->get('system.pages.publish_dates') && !isset($this->header->published)) {
// unpublish if required, if not clear cache right before page should be unpublished
if ($this->unpublishDate()) {
if ($this->unpublishDate() < time()) {
$this->published(false);
} else {
$this->published();
self::$grav['cache']->setLifeTime($this->unpublishDate());
self::getGrav()['cache']->setLifeTime($this->unpublishDate());
}
}
// publish if required, if not clear cache right before page is published
if ($this->publishDate() != $this->modified() && $this->publishDate() > time()) {
$this->published(false);
self::$grav['cache']->setLifeTime($this->publishDate());
self::getGrav()['cache']->setLifeTime($this->publishDate());
}
}
$this->published();
@@ -280,6 +278,17 @@ class Page
return $this->header;
}
/**
* Modify a header value directly
*
* @param $key
* @param $value
*/
public function modifyHeader($key, $value)
{
$this->header->$key = $value;
}
/**
* Get the summary.
*
@@ -288,30 +297,40 @@ class Page
*/
public function summary($size = null)
{
/** @var Config $config */
$config = self::getGrav()['config'];
$content = $this->content();
// Return summary based on settings in site config file
if (!$config->get('site.summary.enabled', true)) {
return $content;
}
// Get summary size from site config's file
if (is_null($size)) {
$size = $config->get('site.summary.size', null);
}
// Return calculated summary based on summary divider's position
if (!$size && isset($this->summary_size)) {
$format = $config->get('site.summary.format', 'short');
// Return entire page content on wrong/ unknown format
if (!in_array($format, array('short', 'long'))) {
return $content;
} elseif (($format === 'short') && isset($this->summary_size)) {
return substr($content, 0, $this->summary_size);
}
// Return calculated summary based on setting in site config file
/** @var Config $config */
$config = self::$grav['config'];
if (!$size && $config->get('site.summary.size')) {
$size = $config->get('site.summary.size');
}
// If the size is zero, return the entire page content
if ($size === 0) {
return $content;
// Return calculated summary based on defaults
if (!$size) {
} elseif (!is_numeric($size) || ($size < 0)) {
$size = 300;
}
return Utils::truncateHTML($content, $size);
}
/**
* Gets and Sets the content based on content portion of the .md file
*
@@ -336,13 +355,12 @@ class Page
// If no content, process it
if ($this->content === null) {
// Get media
$this->media();
// Load cached content
/** @var Cache $cache */
$cache = self::$grav['cache'];
$cache = self::getGrav()['cache'];
$cache_id = md5('page'.$this->id());
$this->content = $cache->fetch($cache_id);
@@ -353,10 +371,9 @@ class Page
$twig_already_processed = false;
// if no cached-content run everything
if ($this->content == false) {
if ($this->content === false) {
$this->content = $this->raw_content;
self::$grav->fireEvent('onPageContentRaw', new Event(['page' => $this]));
self::getGrav()->fireEvent('onPageContentRaw', new Event(['page' => $this]));
if ($twig_first) {
if ($process_twig) {
@@ -393,10 +410,11 @@ class Page
}
// Handle summary divider
$divider_pos = strpos($this->content, '<p>'.SUMMARY_DELIMITER.'</p>');
$delimiter = self::getGrav()['config']->get('site.summary.delimiter', '===');
$divider_pos = strpos($this->content, "<p>{$delimiter}</p>");
if ($divider_pos !== false) {
$this->summary_size = $divider_pos;
$this->content = str_replace('<p>'.SUMMARY_DELIMITER.'</p>', '', $this->content);
$this->content = str_replace("<p>{$delimiter}</p>", '', $this->content);
}
}
@@ -410,7 +428,7 @@ class Page
protected function processMarkdown()
{
/** @var Config $config */
$config = self::$grav['config'];
$config = self::getGrav()['config'];
$defaults = (array) $config->get('system.pages.markdown');
if (isset($this->header()->markdown)) {
@@ -419,7 +437,7 @@ class Page
// pages.markdown_extra is deprecated, but still check it...
if (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null) {
$defaults['extra'] = $this->markdown_extra;
$defaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra');
}
// Initialize the preferred variant of Parsedown
@@ -429,11 +447,6 @@ class Page
$parsedown = new Parsedown($this);
}
$parsedown->setBreaksEnabled($defaults['auto_line_breaks']);
$parsedown->setUrlsLinked($defaults['auto_url_links']);
$parsedown->setMarkupEscaped($defaults['escape_markup']);
$parsedown->setSpecialChars($defaults['special_chars']);
$this->content = $parsedown->text($this->content);
}
@@ -443,7 +456,7 @@ class Page
*/
private function processTwig()
{
$twig = self::$grav['twig'];
$twig = self::getGrav()['twig'];
$this->content = $twig->processPage($this, $this->content);
}
@@ -452,10 +465,10 @@ class Page
*/
private function cachePageContent()
{
$cache = self::$grav['cache'];
$cache = self::getGrav()['cache'];
$cache_id = md5('page'.$this->id());
self::$grav->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
self::getGrav()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
$cache->save($cache_id, $this->content);
}
@@ -630,7 +643,7 @@ class Page
public function blueprints()
{
/** @var Pages $pages */
$pages = self::$grav['pages'];
$pages = self::getGrav()['pages'];
return $pages->blueprints($this->template());
}
@@ -709,7 +722,7 @@ class Page
public function media($var = null)
{
/** @var Cache $cache */
$cache = self::$grav['cache'];
$cache = self::getGrav()['cache'];
if ($var) {
$this->media = $var;
@@ -927,7 +940,6 @@ class Page
// if not metadata yet, process it.
if (null === $this->metadata) {
$header_tag_http_equivs = ['content-type', 'default-style', 'refresh'];
$this->metadata = array();
$page_header = $this->header;
@@ -938,7 +950,7 @@ class Page
// Safety check to ensure we have a header
if ($page_header) {
// Merge any site.metadata settings in with page metadata
$defaults = (array) self::$grav['config']->get('site.metadata');
$defaults = (array) self::getGrav()['config']->get('site.metadata');
if (isset($page_header->metadata)) {
$page_header->metadata = array_merge($defaults, $page_header->metadata);
@@ -948,7 +960,6 @@ class Page
// Build an array of meta objects..
foreach ((array)$page_header->metadata as $key => $value) {
// If this is a property type metadata: "og", "twitter", "facebook" etc
if (is_array($value)) {
foreach ($value as $property => $prop_value) {
@@ -1041,10 +1052,10 @@ class Page
public function url($include_host = false)
{
/** @var Pages $pages */
$pages = self::$grav['pages'];
$pages = self::getGrav()['pages'];
/** @var Uri $uri */
$uri = self::$grav['uri'];
$uri = self::getGrav()['uri'];
$rootUrl = $uri->rootUrl($include_host) . $pages->base();
$url = $rootUrl.'/'.trim($this->route(), '/');
@@ -1116,7 +1127,7 @@ class Page
// Path to the page.
$this->path = dirname(dirname($var));
}
return $this->name ? $this->path . '/' . $this->folder . '/' . $this->name : null;
return $this->path . '/' . $this->folder . '/' . ($this->name ?: '');
}
/**
@@ -1243,7 +1254,7 @@ class Page
}
if (empty($this->max_count)) {
/** @var Config $config */
$config = self::$grav['config'];
$config = self::getGrav()['config'];
$this->max_count = (int) $config->get('system.pages.list.count');
}
return $this->max_count;
@@ -1318,7 +1329,7 @@ class Page
}
/** @var Pages $pages */
$pages = self::$grav['pages'];
$pages = self::getGrav()['pages'];
return $pages->get($this->parent);
}
@@ -1331,7 +1342,7 @@ class Page
public function children()
{
/** @var Pages $pages */
$pages = self::$grav['pages'];
$pages = self::getGrav()['pages'];
return $pages->children($this->path());
}
@@ -1398,7 +1409,7 @@ class Page
public function active()
{
/** @var Uri $uri */
$uri = self::$grav['uri'];
$uri = self::getGrav()['uri'];
if ($this->url() == $uri->url()) {
return true;
}
@@ -1414,8 +1425,8 @@ class Page
public function activeChild()
{
/** @var Uri $uri */
$uri = self::$grav['uri'];
$config = self::$grav['config'];
$uri = self::getGrav()['uri'];
$config = self::getGrav()['config'];
// Special check when item is home
if ($this->home()) {
@@ -1450,7 +1461,7 @@ class Page
*/
public function root()
{
if (!$this->parent && !$this->name and !$this->visible) {
if (!$this->parent && !$this->name && !$this->visible) {
return true;
} else {
return false;
@@ -1469,7 +1480,7 @@ class Page
public function find($url, $all = false)
{
/** @var Pages $pages */
$pages = self::$grav['pages'];
$pages = self::getGrav()['pages'];
return $pages->dispatch($url, $all);
}
@@ -1501,9 +1512,9 @@ class Page
// TODO: MOVE THIS INTO SOMEWHERE ELSE?
/** @var Uri $uri */
$uri = self::$grav['uri'];
$uri = self::getGrav()['uri'];
/** @var Config $config */
$config = self::$grav['config'];
$config = self::getGrav()['config'];
foreach ((array) $config->get('site.taxonomies') as $taxonomy) {
if ($uri->param($taxonomy)) {
@@ -1539,7 +1550,7 @@ class Page
}
/** @var Grav $grav */
$grav = self::$grav['grav'];
$grav = self::getGrav()['grav'];
// New Custom event to handle things like pagination.
$grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection]));
@@ -1619,7 +1630,7 @@ class Page
// @taxonomy: { category: [ blog, featured ], level: 1 }
/** @var Taxonomy $taxonomy_map */
$taxonomy_map = self::$grav['taxonomy'];
$taxonomy_map = self::getGrav()['taxonomy'];
if (!empty($parts)) {
$params = [implode('.', $parts) => $params];
@@ -1695,7 +1706,7 @@ class Page
// Do reordering.
if ($reorder && $this->order() != $this->_original->order()) {
/** @var Pages $pages */
$pages = self::$grav['pages'];
$pages = self::getGrav()['pages'];
$parent = $this->parent();

View File

@@ -523,20 +523,27 @@ class Pages
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
if ($file->isDot()) {
continue;
}
$name = $file->getFilename();
$modified = $file->getMTime();
if ($file->isFile() && Utils::endsWith($name, CONTENT_EXT)) {
$page->init($file);
$content_exists = true;
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
if ($file->isFile()) {
// Update the last modified if it's newer than already found
if ($file->getBasename() !== '.DS_Store' && ($modified = $file->getMTime()) > $last_modified) {
$last_modified = $modified;
}
} elseif ($file->isDir() && !$file->isDot()) {
if (Utils::endsWith($name, CONTENT_EXT)) {
$page->init($file);
$content_exists = true;
if ($config->get('system.pages.events.page')) {
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
}
}
} elseif ($file->isDir()) {
if (!$page->path()) {
$page->path($file->getPath());
}
@@ -554,11 +561,6 @@ class Pages
$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
@@ -587,7 +589,6 @@ class Pages
// Build routes and taxonomy map.
/** @var $page Page */
foreach ($this->instances as $page) {
$parent = $page->parent();
if ($parent) {
@@ -637,7 +638,6 @@ class Pages
}
foreach ($pages as $key => $info) {
$child = isset($this->instances[$key]) ? $this->instances[$key] : null;
if (!$child) {
throw new \RuntimeException("Page does not exist: {$key}");

View File

@@ -1,6 +1,8 @@
<?php
namespace Grav\Common;
use Grav\Common\Data\Data;
use Grav\Common\Page\Page;
use Grav\Common\Config\Config;
use RocketTheme\Toolbox\Event\EventDispatcher;
use RocketTheme\Toolbox\Event\EventSubscriberInterface;
@@ -24,6 +26,10 @@ class Plugin implements EventSubscriberInterface
protected $config;
protected $active = true;
/**
* @var \Grav\Common\string
*/
protected $name;
/**
* By default assign all methods as listeners using the default priority.
@@ -47,13 +53,15 @@ class Plugin implements EventSubscriberInterface
/**
* Constructor.
*
* @param Grav $grav
* @param Config $config
* @param string $name
* @param Grav $grav
* @param Config $config
*/
public function __construct(Grav $grav, Config $config)
public function __construct($name, Grav $grav, Config $config)
{
$this->grav = $grav;
$this->config = $config;
$this->name = $name;
}
public function isAdmin()
@@ -84,4 +92,70 @@ class Plugin implements EventSubscriberInterface
}
}
}
/**
* @param array $events
*/
protected function disable(array $events)
{
/** @var EventDispatcher $dispatcher */
$dispatcher = $this->grav['events'];
foreach ($events as $eventName => $params) {
if (is_string($params)) {
$dispatcher->removeListener($eventName, array($this, $params));
} elseif (is_string($params[0])) {
$dispatcher->removeListener($eventName, array($this, $params[0]));
} else {
foreach ($params as $listener) {
$dispatcher->removeListener($eventName, array($this, $listener[0]));
}
}
}
}
/**
* Merge global and page configurations.
*
* @param Page $page The page to merge the configurations with the
* plugin settings.
*
* @param bool $deep Should you use deep or shallow merging
*
* @return \Grav\Common\Data\Data
*/
protected function mergeConfig(Page $page, $deep = false)
{
$class_name = $this->name;
$class_name_merged = $class_name . '.merged';
$defaults = $this->config->get('plugins.' . $class_name, array());
$header = array();
if (isset($page->header()->$class_name_merged)) {
$merged = $page->header()->$class_name_merged;
if (count($merged) > 0) {
return $merged;
} else {
return new Data($defaults);
}
}
// Get default plugin configurations and retrieve page header configuration
if (isset($page->header()->$class_name)) {
if ($deep) {
$header = array_replace_recursive($defaults, $page->header()->$class_name);
} else {
$header = array_merge($defaults, $page->header()->$class_name);
}
} else {
$header = $defaults;
}
// Create new config object and set it on the page object so it's cached for next time
$config = new Data($header);
$page->modifyHeader($class_name_merged, $config);
// Return configurations as a new data config class
return $config;
}
}

View File

@@ -47,14 +47,15 @@ class Plugins extends Iterator
$filePath = $this->grav['locator']('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT);
if (!is_file($filePath)) {
throw new \RuntimeException(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin));
$this->grav['log']->addWarning(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin));
continue;
}
require_once $filePath;
$pluginClassFormat = [
'Grav\\Plugin\\'.ucfirst($plugin).'Plugin',
'Grav\\Plugin\\'.str_replace(['_','-'], '', $plugin).'Plugin'
'Grav\\Plugin\\'.Inflector::camelize($plugin).'Plugin'
];
$pluginClassName = false;
@@ -69,7 +70,7 @@ class Plugins extends Iterator
throw new \RuntimeException(sprintf("Plugin '%s' class not found! Try reinstalling this plugin.", $plugin));
}
$instance = new $pluginClassName($this->grav, $config);
$instance = new $pluginClassName($plugin, $this->grav, $config);
if ($instance instanceof EventSubscriberInterface) {
$events->addSubscriber($instance);
}
@@ -93,7 +94,8 @@ class Plugins extends Iterator
public static function all()
{
$list = array();
$iterator = new \DirectoryIterator('plugins://');
$locator = Grav::instance()['locator'];
$iterator = new \DirectoryIterator($locator->findResource('plugins://', false));
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {

View File

@@ -2,9 +2,6 @@
namespace Grav\Common\Service;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Uri;
use Grav\Common\Filesystem\Folder;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use RocketTheme\Toolbox\Blueprints\Blueprints;

View File

@@ -7,7 +7,6 @@ use Pimple\ServiceProviderInterface;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Handler\PlainTextHandler;
use Whoops\Run;
class ErrorServiceProvider implements ServiceProviderInterface
{
@@ -30,7 +29,7 @@ class ErrorServiceProvider implements ServiceProviderInterface
$errors->pushHandler($json_page, 'json');
$logger = $container['log'];
$errors->pushHandler(function ($exception, $inspector, $run) use($logger) {
$errors->pushHandler(function (\Exception $exception, $inspector, $run) use ($logger) {
$logger->addCritical($exception->getMessage(). ' - Trace: '. $exception->getTraceAsString());
}, 'log');

View File

@@ -5,7 +5,6 @@ use Pimple\Container;
use Pimple\ServiceProviderInterface;
use \Monolog\Logger;
use \Monolog\Handler\StreamHandler;
use \Monolog\Handler\RotatingFileHandler;
class LoggerServiceProvider implements ServiceProviderInterface
{
@@ -13,9 +12,7 @@ class LoggerServiceProvider implements ServiceProviderInterface
{
$log = new Logger('grav');
$log_file = LOG_DIR.'grav.log';
$log_days = 14;
// $log->pushHandler(new RotatingFileHandler($log_file, $log_days, Logger::WARNING));
$log->pushHandler(new StreamHandler($log_file, Logger::WARNING));
$container['log'] = $log;

View File

@@ -18,6 +18,6 @@ class Theme extends Plugin
{
$this->name = $name;
parent::__construct($grav, $config);
parent::__construct($name, $grav, $config);
}
}

View File

@@ -59,7 +59,8 @@ class Themes extends Iterator
public function all()
{
$list = array();
$iterator = new \DirectoryIterator('themes://');
$locator = Grav::instance()['locator'];
$iterator = new \DirectoryIterator($locator->findResource('themes://', false));
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
@@ -145,10 +146,19 @@ class Themes extends Iterator
$class = include $file;
if (!is_object($class)) {
$className = '\\Grav\\Theme\\' . ucfirst($name);
if (class_exists($className)) {
$class = new $className($grav, $config, $name);
$themeClassFormat = [
'Grav\\Theme\\'.ucfirst($name),
'Grav\\Theme\\'.Inflector::camelize($name)
];
$themeClassName = false;
foreach ($themeClassFormat as $themeClass) {
if (class_exists($themeClass)) {
$themeClassName = $themeClass;
$class = new $themeClassName($grav, $config, $name);
break;
}
}
}
} elseif (!$locator('theme://') && !defined('GRAV_CLI')) {

View File

@@ -219,6 +219,34 @@ class Twig
}
/**
* Process a Twig template directly by using a template name
* and optional array of variables
*
* @param string $template template to render with
* @param array $vars Optional variables
* @return string
*/
public function processTemplate($template, $vars = array())
{
// override the twig header vars for local resolution
$this->grav->fireEvent('onTwigTemplateVariables');
$vars += $this->twig_vars;
try {
$output = $this->twig->render($template, $vars);
} catch (\Twig_Error_Loader $e) {
throw new \RuntimeException($e->getRawMessage(), 404, $e);
}
return $output;
}
/**
* Process a Twig template directly by using a Twig string
* and optional array of variables
*
* @param string $string string to render.
* @param array $vars Optional variables
* @return string

View File

@@ -1,6 +1,8 @@
<?php
namespace Grav\Common;
use Grav\Common\Markdown\Parsedown;
use Grav\Common\Markdown\ParsedownExtra;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
@@ -49,6 +51,8 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFilter('ksort', [$this,'ksortFilter']),
new \Twig_SimpleFilter('contains', [$this, 'containsFilter']),
new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFilter']),
new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']),
new \Twig_SimpleFilter('markdown', [$this, 'markdownFilter'])
];
}
@@ -64,7 +68,8 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('gist', [$this, 'gistFunc'])
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
];
}
@@ -90,7 +95,8 @@ class TwigExtension extends \Twig_Extension
public function safeEmailFilter($str)
{
$email = '';
for ($i = 0; $i < strlen($str); $i++) {
$str_len = strlen($str);
for ($i = 0; $i < $str_len; $i++) {
$email .= "&#" . ord($str[$i]);
}
return $email;
@@ -164,8 +170,8 @@ class TwigExtension extends \Twig_Extension
* {{ 'send_email'|camelize }} => SendEmail
* {{ 'CamelCased'|underscorize }} => camel_cased
* {{ 'Something Text'|hyphenize }} => something-text
* {{ 'something text to read'|humanize }} => "Something text to read"
* {{ '181'|monthize}} => 6
* {{ 'something_text_to_read'|humanize }} => "Something text to read"
* {{ '181'|monthize }} => 6
* {{ '10'|ordinalize }} => 10th
*
* @param string $action
@@ -316,6 +322,31 @@ class TwigExtension extends \Twig_Extension
return "$difference $periods[$j] {$tense}";
}
public function absoluteUrlFilter($string)
{
$url = $this->grav['uri']->base();
$string = preg_replace('/((?:href|src) *= *[\'"](?!(http|ftp)))/i', "$1$url", $string);
return $string;
}
public function markdownFilter($string)
{
$page = $this->grav['page'];
$defaults = $this->grav['config']->get('system.pages.markdown');
// Initialize the preferred variant of Parsedown
if ($defaults['extra']) {
$parsedown = new ParsedownExtra($page);
} else {
$parsedown = new Parsedown($page);
}
$string = $parsedown->text($string);
return $string;
}
/**
* Repeat given string x times.
*
@@ -404,4 +435,16 @@ class TwigExtension extends \Twig_Extension
{
return '<script src="https://gist.github.com/'.$id.'.js"></script>';
}
/**
* Generate a random string
*
* @param int $count
*
* @return string
*/
public function randomStringFunc($count = 5)
{
return Utils::generateRandomString($count);
}
}

View File

@@ -11,6 +11,7 @@ class Uri
{
public $url;
protected $basename;
protected $base;
protected $root;
protected $bits;
@@ -65,8 +66,6 @@ class Uri
$this->root = $base . $root_path;
$this->url = $base . $uri;
$this->init();
}
/**
@@ -74,29 +73,24 @@ class Uri
*/
public function init()
{
$config = Grav::instance()['config'];
// get any params and remove them
$uri = str_replace($this->root, '', $this->url);
$this->params = array();
if (strpos($uri, ':')) {
$bits = explode('/', $uri);
$path = array();
foreach ($bits as $bit) {
if (strpos($bit, ':') !== false) {
$param = explode(':', $bit);
if (count($param) == 2) {
$this->params[$param[0]] = str_replace('%7C', '/', filter_var($param[1], FILTER_SANITIZE_STRING));
}
} else {
$path[] = $bit;
}
}
$uri = implode('/', $path);
}
// reset params
$this->params = [];
// process params
$uri = $this->processParams($uri, $config->get('system.param_sep'));
// remove the extension if there is one set
$parts = pathinfo($uri);
if (preg_match("/\.(txt|xml|html|json|rss|atom)$/", $parts['basename'])) {
// set the original basename
$this->basename = $parts['basename'];
if (preg_match("/\.(".$config->get('system.pages.types').")$/", $parts['basename'])) {
$uri = rtrim($parts['dirname'], '/').'/'.$parts['filename'];
$this->extension = $parts['extension'];
}
@@ -120,6 +114,34 @@ class Uri
}
}
/**
* Process any params based in this URL, supports any valid delimiter
*
* @param $uri
* @param string $delimiter
*
* @return string
*/
private function processParams($uri, $delimiter = ':')
{
if (strpos($uri, $delimiter) !== false) {
$bits = explode('/', $uri);
$path = array();
foreach ($bits as $bit) {
if (strpos($bit, $delimiter) !== false) {
$param = explode($delimiter, $bit);
if (count($param) == 2) {
$this->params[$param[0]] = str_replace(urlencode($delimiter), '/', filter_var($param[1], FILTER_SANITIZE_STRING));
}
} else {
$path[] = $bit;
}
}
$uri = implode('/', $path);
}
return $uri;
}
/**
* Return URI path.
*
@@ -174,15 +196,17 @@ class Uri
*/
public function params($id = null)
{
$config = Grav::instance()['config'];
$params = null;
if ($id === null) {
$output = array();
foreach ($this->params as $key => $value) {
$output[] = $key . ':' . $value;
$output[] = $key . $config->get('system.param_sep') . $value;
$params = '/'.implode('/', $output);
}
} elseif (isset($this->params[$id])) {
$params = "/{$id}:".$this->params[$id];
$params = "/{$id}". $config->get('system.param_sep') . $this->params[$id];
}
return $params;
@@ -232,6 +256,8 @@ class Uri
/**
* Return the Extension of the URI
*
* @param null $default
*
* @return String The extension of the URI
*/
public function extension($default = null)
@@ -262,6 +288,17 @@ class Uri
return $this->host();
}
/**
* Return the basename of the URI
*
* @return String The basename of the URI
*/
public function basename()
{
return $this->basename;
}
/**
* Return the base of the URI
*
@@ -358,6 +395,20 @@ class Uri
return $ipaddress;
}
/**
* Is this an external URL? if it starts with `http` then yes, else false
*
* @param string $url the URL in question
* @return boolean is eternal state
*/
public function isExternal($url)
{
if (Utils::startsWith($url, 'http')) {
return true;
} else {
return false;
}
}
/**
* The opposite of built-in PHP method parse_url()
@@ -365,7 +416,7 @@ class Uri
* @param $parsed_url
* @return string
*/
public static function build_url($parsed_url)
public static function buildUrl($parsed_url)
{
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';

View File

@@ -30,7 +30,6 @@ abstract class Authentication
public static function verify($password, $hash)
{
// Always accept plaintext passwords (needs an update).
// FIXME: not safe to do this...
if ($password && $password == $hash) {
return 2;
}

View File

@@ -26,9 +26,9 @@ class User extends Data
*/
public static function load($username)
{
$locator = self::$grav['locator'];
$locator = self::getGrav()['locator'];
// FIXME: validate directory name
// TODO: validate directory name
$blueprints = new Blueprints('blueprints://user');
$blueprint = $blueprints->get('account');
$file_path = $locator->findResource('account://' . $username . YAML_EXT);

View File

@@ -1,6 +1,8 @@
<?php
namespace Grav\Common;
use RocketTheme\Toolbox\Event\Event;
/**
* Misc utilities.
*
@@ -8,6 +10,8 @@ namespace Grav\Common;
*/
abstract class Utils
{
use GravTrait;
/**
* @param string $haystack
* @param string $needle
@@ -28,6 +32,16 @@ abstract class Utils
return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
}
/**
* @param string $haystack
* @param string $needle
* @return bool
*/
public static function contains($haystack, $needle)
{
return $needle === '' || strpos($haystack, $needle) !== false;
}
/**
* Merge two objects into one.
*
@@ -41,12 +55,13 @@ abstract class Utils
}
/**
* Recurseive remove a directory - DANGEROUS! USE WITH CARE!!!!
* Recursive remove a directory - DANGEROUS! USE WITH CARE!!!!
*
* @param $dir
* @return bool
*/
public static function rrmdir($dir) {
public static function rrmdir($dir)
{
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
@@ -55,15 +70,59 @@ abstract class Utils
/** @var \DirectoryIterator $fileinfo */
foreach ($files as $fileinfo) {
if ($fileinfo->isDir()) {
if (false === rmdir($fileinfo->getRealPath())) return false;
if (false === rmdir($fileinfo->getRealPath())) {
return false;
}
} else {
if (false === unlink($fileinfo->getRealPath())) return false;
if (false === unlink($fileinfo->getRealPath())) {
return false;
}
}
}
return rmdir($dir);
}
/**
* Recursive copy of one directory to another
*
* @param $src
* @param $dest
*
* @return bool
*/
public static function rcopy($src, $dest)
{
// If the src is not a directory do a simple file copy
if (!is_dir($src)) {
copy($src, $dest);
return true;
}
// If the destination directory does not exist create it
if (!is_dir($dest)) {
if (!mkdir($dest)) {
// If the destination directory could not be created stop processing
return false;
}
}
// Open the source directory to read in files
$i = new \DirectoryIterator($src);
/** @var \DirectoryIterator $f */
foreach ($i as $f) {
if ($f->isFile()) {
copy($f->getRealPath(), "$dest/" . $f->getFilename());
} else {
if (!$f->isDot() && $f->isDir()) {
static::rcopy($f->getRealPath(), "$dest/$f");
}
}
}
return true;
}
/**
* Truncate HTML by text length.
*
@@ -74,7 +133,8 @@ abstract class Utils
* @param bool $considerHtml
* @return string
*/
public static function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true) {
public static function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true)
{
$open_tags = array();
if ($considerHtml) {
// if the plain text is shorter than the maximum length, return the whole text
@@ -163,4 +223,177 @@ abstract class Utils
}
return $truncate;
}
/**
* Generate a random string of a given length
*
* @param int $length
*
* @return string
*/
public static function generateRandomString($length = 5)
{
return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, $length);
}
/**
* Provides the ability to download a file to the browser
*
* @param $file the full path to the file to be downloaded
* @param bool $force_download as opposed to letting browser choose if to download or render
*/
public static function download($file, $force_download = true)
{
if (file_exists($file)) {
// fire download event
self::getGrav()->fireEvent('onBeforeDownload', new Event(['file' => $file]));
$file_parts = pathinfo($file);
$filesize = filesize($file);
$range = false;
set_time_limit(0);
ignore_user_abort(false);
ini_set('output_buffering', 0);
ini_set('zlib.output_compression', 0);
if ($force_download) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.$file_parts['basename']);
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
} else {
header("Content-Type: " . Utils::getMimeType($file_parts['extension']));
}
header('Content-Length: ' . $filesize);
// 8kb chunks for now
$chunk = 8 * 1024;
$fh = fopen($file, "rb");
if ($fh === false) {
return;
}
// Repeat reading until EOF
while (!feof($fh)) {
echo fread($fh, $chunk);
ob_flush(); // flush output
flush();
}
exit;
}
}
/**
* Return the mimetype based on filename
*
* @param $extension Extension of file (eg .txt)
*
* @return string
*/
public static function getMimeType($extension)
{
$extension = strtolower($extension);
switch($extension)
{
case "js":
return "application/x-javascript";
case "json":
return "application/json";
case "jpg":
case "jpeg":
case "jpe":
return "image/jpg";
case "png":
case "gif":
case "bmp":
case "tiff":
return "image/" . $extension;
case "css":
return "text/css";
case "xml":
return "application/xml";
case "doc":
case "docx":
return "application/msword";
case "xls":
case "xlt":
case "xlm":
case "xld":
case "xla":
case "xlc":
case "xlw":
case "xll":
return "application/vnd.ms-excel";
case "ppt":
case "pps":
return "application/vnd.ms-powerpoint";
case "rtf":
return "application/rtf";
case "pdf":
return "application/pdf";
case "html":
case "htm":
case "php":
return "text/html";
case "txt":
return "text/plain";
case "mpeg":
case "mpg":
case "mpe":
return "video/mpeg";
case "mp3":
return "audio/mpeg3";
case "wav":
return "audio/wav";
case "aiff":
case "aif":
return "audio/aiff";
case "avi":
return "video/msvideo";
case "wmv":
return "video/x-ms-wmv";
case "mov":
return "video/quicktime";
case "zip":
return "application/zip";
case "tar":
return "application/x-tar";
case "swf":
return "application/x-shockwave-flash";
default:
return "application/octet-stream";
}
}
}

View File

@@ -6,9 +6,7 @@ use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
/**
* Class BackupCommand

View File

@@ -4,9 +4,7 @@ namespace Grav\Console\Cli;
use Grav\Common\Filesystem\Folder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**

View File

@@ -1,16 +1,12 @@
<?php
namespace Grav\Console\Cli;
use Grav\Console\ConsoleTrait;
use Grav\Common\Cache;
use Grav\Common\Filesystem\Folder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
/**
* Class ClearCacheCommand

View File

@@ -2,13 +2,11 @@
namespace Grav\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Yaml;
/**
* Class NewProjectCommand

View File

@@ -2,6 +2,7 @@
namespace Grav\Console\Cli;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Utils;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
@@ -188,7 +189,7 @@ class SandboxCommand extends Command
$to = $this->destination . $target;
$this->output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
$this->rcopy($from, $to);
Utils::rcopy($from, $to);
}
}
@@ -268,7 +269,7 @@ class SandboxCommand extends Command
if (count($pages_files) == 0) {
$destination = $this->source . '/user/pages';
$this->rcopy($destination, $pages_dir);
Utils::rcopy($destination, $pages_dir);
$this->output->writeln(' <cyan>' . $destination . '</cyan> <comment>-></comment> Created');
}
@@ -326,42 +327,4 @@ class SandboxCommand extends Command
exit;
}
}
/**
* @param $src
* @param $dest
*
* @return bool
*/
private function rcopy($src, $dest)
{
// If the src is not a directory do a simple file copy
if (!is_dir($src)) {
copy($src, $dest);
return true;
}
// If the destination directory does not exist create it
if (!is_dir($dest)) {
if (!mkdir($dest)) {
// If the destination directory could not be created stop processing
return false;
}
}
// Open the source directory to read in files
$i = new \DirectoryIterator($src);
/** @var \DirectoryIterator $f */
foreach ($i as $f) {
if ($f->isFile()) {
copy($f->getRealPath(), "$dest/" . $f->getFilename());
} else {
if (!$f->isDot() && $f->isDir()) {
$this->rcopy($f->getRealPath(), "$dest/$f");
}
}
}
return true;
}
}

View File

@@ -35,8 +35,8 @@ trait ConsoleTrait
*/
public function setupConsole(InputInterface $input, OutputInterface $output)
{
if (self::$grav) {
self::$grav['config']->set('system.cache.driver', 'default');
if (self::getGrav()) {
self::getGrav()['config']->set('system.cache.driver', 'default');
}
$this->argv = $_SERVER['argv'][0];

View File

@@ -78,12 +78,12 @@ class InfoCommand extends Command
$this->output->writeln('');
$packageURL = '';
if (isset($foundPackage->author->url)) {
$packageURL = '<' . $foundPackage->author->url . '>';
if (isset($foundPackage->author['url'])) {
$packageURL = '<' . $foundPackage->author['url'] . '>';
}
$this->output->writeln("<green>" . str_pad("Author",
12) . ":</green> " . $foundPackage->author->name . ' <' . $foundPackage->author->email . '> ' . $packageURL);
12) . ":</green> " . $foundPackage->author['name'] . ' <' . $foundPackage->author['email'] . '> ' . $packageURL);
foreach (array(
'version',

View File

@@ -5,6 +5,8 @@ use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Common\Inflector;
use Grav\Common\Utils;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -12,6 +14,10 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Yaml\Yaml;
define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/(.*)/');
/**
* Class InstallCommand
@@ -42,6 +48,9 @@ class InstallCommand extends Command
*/
protected $tmp;
protected $local_config;
/**
*
*/
@@ -93,6 +102,11 @@ class InstallCommand extends Command
$packages = array_map('strtolower', $this->input->getArgument('package'));
$this->data = $this->gpm->findPackages($packages);
$local_config_file = exec('eval echo ~/.grav/config');
if (file_exists($local_config_file)) {
$this->local_config = Yaml::parse($local_config_file);
}
if (
!Installer::isGravInstance($this->destination) ||
!Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
@@ -111,7 +125,7 @@ class InstallCommand extends Command
if (count($this->data['not_found'])) {
$this->output->writeln("These packages were not found on Grav: <red>" . implode('</red>, <red>',
$this->data['not_found']) . "</red>");
array_keys($this->data['not_found'])) . "</red>");
}
unset($this->data['not_found']);
@@ -119,29 +133,29 @@ class InstallCommand extends Command
foreach ($this->data as $data) {
foreach ($data as $package) {
$version = isset($package->available) ? $package->available : $package->version;
$this->output->writeln("Preparing to install <cyan>" . $package->name . "</cyan> [v" . $version . "]");
$this->output->write(" |- Downloading package... 0%");
$this->file = $this->downloadPackage($package);
$this->output->write(" |- Checking destination... ");
$checks = $this->checkDestination($package);
if (!$checks) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
//Check for dependencies
if (isset($package->dependencies)) {
$this->output->writeln("Package <cyan>" . $package->name . "</cyan> has ". count($package->dependencies) . " required dependencies that must be installed first...");
$this->output->writeln('');
} else {
$this->output->write(" |- Installing package... ");
$installation = $this->installPackage($package);
if (!$installation) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$dependency_data = $this->gpm->findPackages($package->dependencies);
if (!$dependency_data['total']) {
$this->output->writeln("No dependencies found...");
$this->output->writeln('');
} else {
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
unset($dependency_data['total']);
foreach($dependency_data as $type => $dep_data) {
foreach($dep_data as $name => $dep_package) {
$this->processPackage($dep_package);
}
}
}
}
$this->processPackage($package);
}
}
@@ -149,6 +163,255 @@ class InstallCommand extends Command
$this->clearCache();
}
/**
* @param $package
*/
private function processPackage($package)
{
$install_options = ['GPM'];
// if no name, not found in GPM
if (!isset($package->version)) {
unset($install_options[0]);
}
// if local config found symlink is a valid option
if (isset($this->local_config) && $this->getSymlinkSource($package)) {
$install_options[] = 'Symlink';
}
// if override set, can install via git
if (isset($package->override_repository)) {
$install_options[] = 'Git';
}
// reindex list
$install_options = array_values($install_options);
if (count($install_options) == 0) {
// no valid install options - error and return
$this->output->writeln("<red>not valid installation methods found!</red>");
return;
} elseif (count($install_options) == 1) {
// only one option, use it...
$method = $install_options[0];
} else {
$helper = $this->getHelper('question');
$question = new ChoiceQuestion(
'Please select installation method for <cyan>' . $package->name . '</cyan> (<magenta>'.$install_options[0].' is default</magenta>)', array_values($install_options), 0
);
$question->setErrorMessage('Method %s is invalid');
$method = $helper->ask($this->input, $this->output, $question);
}
$this->output->writeln('');
$method_name = 'process'.$method;
$this->$method_name($package);
$this->installDemoContent($package);
}
/**
* @param $package
*/
private function installDemoContent($package)
{
$demo_dir = $this->destination . DS . $package->install_path . DS . '_demo';
$dest_dir = $this->destination . DS . 'user';
$pages_dir = $dest_dir . DS . 'pages';
if (file_exists($demo_dir)) {
// Demo content exists, prompt to install it.
$this->output->writeln("<white>Attention: </white><cyan>".$package->name . "</cyan> contains demo content");
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Do you wish to install this demo content? [y|N] ', false);
if (!$helper->ask($this->input, $this->output, $question)) {
$this->output->writeln(" '- <red>Skipped!</red> ");
$this->output->writeln('');
return;
}
// if pages folder exists in demo
if (file_exists($demo_dir . DS . 'pages')) {
$pages_backup = 'pages.' . date('m-d-Y-H-i-s');
$question = new ConfirmationQuestion('This will backup your current `user/pages` folder to `user/'. $pages_backup. '`, continue? [y|N]', false);
if (!$helper->ask($this->input, $this->output, $question)) {
$this->output->writeln(" '- <red>Skipped!</red> ");
$this->output->writeln('');
return;
}
// backup current pages folder
if (file_exists($dest_dir)) {
if (rename($pages_dir, $dest_dir . DS . $pages_backup)) {
$this->output->writeln(" |- Backing up pages... <green>ok</green>");
} else {
$this->output->writeln(" |- Backing up pages... <red>failed</red>");
}
}
}
// Confirmation received, copy over the data
$this->output->writeln(" |- Installing demo content... <green>ok</green> ");
Utils::rcopy($demo_dir, $dest_dir);
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
}
/**
* @param $package
*
* @return array
*/
private function getGitRegexMatches($package)
{
if (isset($package->override_repository)) {
$repository = $package->override_repository;
} elseif (isset($package->repository)) {
$repository = $package->repository;
} else {
return false;
}
preg_match(GIT_REGEX, $repository, $matches);
return $matches;
}
/**
* @param $package
*
* @return bool|string
*/
private function getSymlinkSource($package)
{
$matches = $this->getGitRegexMatches($package);
foreach ($this->local_config as $path) {
if (Utils::endsWith($matches[2], '.git')) {
$repo_dir = preg_replace('/\.git$/', '', $matches[2]);
} else {
$repo_dir = $matches[2];
}
$from = rtrim($path, '/') . '/' . $repo_dir;
if (file_exists($from)) {
return $from;
}
}
return false;
}
/**
* @param $package
*/
private function processSymlink($package)
{
exec('cd ' . $this->destination);
$to = $this->destination . DS . $package->install_path;
$from = $this->getSymlinkSource($package);
$this->output->writeln("Preparing to Symlink <cyan>" . $package->name . "</cyan>");
$this->output->write(" |- Checking source... ");
if (file_exists($from)) {
$this->output->writeln("<green>ok</green>");
$this->output->write(" |- Checking destination... ");
$checks = $this->checkDestination($package);
if (!$checks) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
if (file_exists($to)) {
$this->output->writeln(" '- <red>Symlink cannot overwrite an existing package, please remove first</red>");
$this->output->writeln('');
} else {
symlink($from, $to);
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Symlinking package... <green>ok</green> ");
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
}
return;
}
$this->output->writeln("<red>not found!</red>");
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
}
/**
* @param $package
*/
private function processGit($package)
{
$matches = $this->getGitRegexMatches($package);
$to = $this->destination . DS . $package->install_path;
$this->output->writeln("Preparing to Git clone <cyan>" . $package->name . "</cyan> from " . $matches[0]);
$this->output->write(" |- Checking destination... ");
$checks = $this->checkDestination($package);
if (!$checks) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
$cmd = 'cd ' . $this->destination . ' && git clone ' . $matches[0] . ' ' . $package->install_path;
exec($cmd);
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Cloning package... <green>ok</green> ");
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
}
/**
* @param $package
*/
private function processGPM($package)
{
$version = isset($package->available) ? $package->available : $package->version;
$this->output->writeln("Preparing to install <cyan>" . $package->name . "</cyan> [v" . $version . "]");
$this->output->write(" |- Downloading package... 0%");
$this->file = $this->downloadPackage($package);
$this->output->write(" |- Checking destination... ");
$checks = $this->checkDestination($package);
if (!$checks) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
$this->output->write(" |- Installing package... ");
$installation = $this->installPackage($package);
if (!$installation) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
}
}
/**
* @param $package
*
@@ -218,6 +481,8 @@ class InstallCommand extends Command
$this->output->writeln(" | '- <red>You decided to not delete the symlink automatically.</red>");
return false;
} else {
unlink($this->destination . DS . $package->install_path);
}
}

View File

@@ -84,9 +84,10 @@ class SelfupgradeCommand extends Command
$this->setupConsole($input, $output);
$this->upgrader = new Upgrader($this->input->getOption('force'));
$update = $this->upgrader->getAssets()['grav-update'];
$local = $this->upgrader->getLocalVersion();
$remote = $this->upgrader->getRemoteVersion();
$update = $this->upgrader->getAssets()->{'grav-update'};
$release = strftime('%c', strtotime($this->upgrader->getReleaseDate()));
if (!$this->upgrader->isUpgradable()) {
@@ -110,10 +111,10 @@ class SelfupgradeCommand extends Command
$this->output->writeln("");
foreach ($changelog as $version => $log) {
$title = $version . ' [' . $log->date . ']';
$title = $version . ' [' . $log['date'] . ']';
$content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) {
return "\n" . ucfirst($match[1]) . ":";
}, $log->content);
}, $log['content']);
$this->output->writeln($title);
$this->output->writeln(str_repeat('-', strlen($title)));
@@ -138,7 +139,7 @@ class SelfupgradeCommand extends Command
$this->output->writeln("");
$this->output->writeln("Preparing to upgrade to v<cyan>$remote</cyan>..");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update->size) . "]... 0%");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update['size']) . "]... 0%");
$this->file = $this->download($update);
$this->output->write(" |- Installing upgrade... ");
@@ -164,17 +165,17 @@ class SelfupgradeCommand extends Command
private function download($package)
{
$this->tmp = CACHE_DIR . DS . 'tmp/Grav-' . uniqid();
$output = Response::get($package->download, [], [$this, 'progress']);
$output = Response::get($package['download'], [], [$this, 'progress']);
Folder::mkdir($this->tmp);
$this->output->write("\x0D");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package->size) . "]... 100%");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package['size']) . "]... 100%");
$this->output->writeln('');
file_put_contents($this->tmp . DS . $package->name, $output);
file_put_contents($this->tmp . DS . $package['name'], $output);
return $this->tmp . DS . $package->name;
return $this->tmp . DS . $package['name'];
}
/**

View File

@@ -140,7 +140,7 @@ class UninstallCommand extends Command
*/
private function uninstallPackage($package)
{
$path = self::$grav['locator']->findResource($package->package_type . '://' . $package->slug);
$path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug);
Installer::uninstall($path);
$errorCode = Installer::lastErrorCode();
@@ -167,7 +167,7 @@ class UninstallCommand extends Command
private function checkDestination($package)
{
$path = self::$grav['locator']->findResource($package->package_type . '://' . $package->slug);
$path = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug);
$questionHelper = $this->getHelper('question');
$skipPrompt = $this->input->getOption('all-yes');

View File

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