mirror of
https://github.com/getgrav/grav.git
synced 2025-12-07 16:29:58 +01:00
Compare commits
335 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
645285ca5c | ||
|
|
c7fd01a644 | ||
|
|
61c0c31992 | ||
|
|
e2ee02a71d | ||
|
|
4e283322ea | ||
|
|
b5b452e585 | ||
|
|
138abdcab1 | ||
|
|
bf661c4355 | ||
|
|
085ca323e2 | ||
|
|
c22b28f312 | ||
|
|
9b5ef4c263 | ||
|
|
3cffe74965 | ||
|
|
759ba5143f | ||
|
|
9ebff2287c | ||
|
|
e492fbde21 | ||
|
|
7255556819 | ||
|
|
c718b8f32a | ||
|
|
07b66dd5d0 | ||
|
|
2e4686fada | ||
|
|
e2544feeaf | ||
|
|
970bf77492 | ||
|
|
0145f454b7 | ||
|
|
e984d9b68f | ||
|
|
1cd6773ded | ||
|
|
0445aa707b | ||
|
|
cc96d160a4 | ||
|
|
4331ab374e | ||
|
|
61005360a5 | ||
|
|
335c44385a | ||
|
|
88e9ad3df2 | ||
|
|
2b19414598 | ||
|
|
b00cd00259 | ||
|
|
6097431021 | ||
|
|
55a9356681 | ||
|
|
8aee946682 | ||
|
|
523d3a331a | ||
|
|
397107b611 | ||
|
|
342af3deba | ||
|
|
dc92498cd0 | ||
|
|
364209a27d | ||
|
|
46d741a2ed | ||
|
|
b5be9ee3f0 | ||
|
|
e0f17a48d5 | ||
|
|
fa27856bc0 | ||
|
|
befaf5d387 | ||
|
|
9571e992d9 | ||
|
|
e40bed5be2 | ||
|
|
442249c3a1 | ||
|
|
8b8d8bcc5b | ||
|
|
d2152cb48e | ||
|
|
81fc0d47ac | ||
|
|
8450f77443 | ||
|
|
0ccc34d860 | ||
|
|
5b6452d89e | ||
|
|
d61d260ef1 | ||
|
|
1125b51f27 | ||
|
|
bf552e22f1 | ||
|
|
b740142668 | ||
|
|
d23f829559 | ||
|
|
85bf215dc6 | ||
|
|
9db04abd1c | ||
|
|
156f645576 | ||
|
|
d3b654bdb0 | ||
|
|
7ed078ce31 | ||
|
|
3c6df48b8b | ||
|
|
833cd497bb | ||
|
|
625f3d3a34 | ||
|
|
380157f9cc | ||
|
|
74c005d39c | ||
|
|
7b2716dab1 | ||
|
|
906c090bd4 | ||
|
|
27ad9a24eb | ||
|
|
83fdecbdd1 | ||
|
|
6c1a76b901 | ||
|
|
4a5847784a | ||
|
|
896fb8138b | ||
|
|
678c445799 | ||
|
|
36428e4735 | ||
|
|
10da784d53 | ||
|
|
c64cdb5dad | ||
|
|
52b68a0a1b | ||
|
|
ee1742af1f | ||
|
|
6315283a3a | ||
|
|
d8b3f215a2 | ||
|
|
3838de1d97 | ||
|
|
f6ddba52d8 | ||
|
|
f7b35c3b79 | ||
|
|
8dd65b709d | ||
|
|
3064fe8ad9 | ||
|
|
287a329a4d | ||
|
|
923b2469f9 | ||
|
|
55bb4cf2fa | ||
|
|
5105be338a | ||
|
|
20e36c8a00 | ||
|
|
9dd4f690a8 | ||
|
|
9a15b5ebdc | ||
|
|
5c003d38be | ||
|
|
95ab80b8f9 | ||
|
|
079468c609 | ||
|
|
7c98ca7134 | ||
|
|
fbf9c345b9 | ||
|
|
491e73eade | ||
|
|
e73773672b | ||
|
|
98d022ee49 | ||
|
|
abca8ce433 | ||
|
|
2dcd4aeaad | ||
|
|
58d4e3384e | ||
|
|
76aed8a119 | ||
|
|
37b8ffb7d2 | ||
|
|
af53d79e5e | ||
|
|
c3aa11abeb | ||
|
|
8e1454b3ab | ||
|
|
1c32f4eaee | ||
|
|
cd15b9197b | ||
|
|
afc18236c2 | ||
|
|
9bac4df02a | ||
|
|
fb1c8eb80d | ||
|
|
19f3a24257 | ||
|
|
5bf95d8b87 | ||
|
|
7d7bb0d52a | ||
|
|
0a459d256d | ||
|
|
6185edcc1b | ||
|
|
814c726323 | ||
|
|
db4c9c1844 | ||
|
|
a96820af36 | ||
|
|
f2c6829cd9 | ||
|
|
790429e286 | ||
|
|
b42366cad2 | ||
|
|
5069088501 | ||
|
|
122db6330e | ||
|
|
cac93a73af | ||
|
|
f1692b20be | ||
|
|
855d4f73b9 | ||
|
|
92401de443 | ||
|
|
31e358ca7c | ||
|
|
851dec76d2 | ||
|
|
19dfa4e011 | ||
|
|
422735a1a2 | ||
|
|
50c2ecbfdf | ||
|
|
5143941356 | ||
|
|
05bd715d6c | ||
|
|
4f8ac36a9a | ||
|
|
6300ab8a03 | ||
|
|
0cadb0cd90 | ||
|
|
a9eb707d8b | ||
|
|
834505ee24 | ||
|
|
9a56bff1d4 | ||
|
|
4715ab7057 | ||
|
|
4ed5f163ed | ||
|
|
e95c4db843 | ||
|
|
efaf41c4e2 | ||
|
|
21cd09e2a9 | ||
|
|
8d84b94bc7 | ||
|
|
ce2b7d7175 | ||
|
|
9a21792b27 | ||
|
|
110cd9535b | ||
|
|
a11e608463 | ||
|
|
185acb4d2a | ||
|
|
087ec7ebaf | ||
|
|
2a507ba994 | ||
|
|
1d852abad3 | ||
|
|
4762663507 | ||
|
|
e5524af557 | ||
|
|
5baec2dca5 | ||
|
|
5866379b92 | ||
|
|
46a5567386 | ||
|
|
4c6c9a722c | ||
|
|
bbcc627a70 | ||
|
|
0416956af8 | ||
|
|
3a250d2744 | ||
|
|
36e3b788a6 | ||
|
|
c51a07c4e9 | ||
|
|
70a38d1d3a | ||
|
|
bc3943b386 | ||
|
|
c6f8fe259a | ||
|
|
53baf47e58 | ||
|
|
29c6a94c92 | ||
|
|
c0c77fff67 | ||
|
|
c3e74c2e09 | ||
|
|
9bd058e319 | ||
|
|
50ff5f0920 | ||
|
|
d7dce7a6d7 | ||
|
|
d1f87ca5d9 | ||
|
|
956e5bd34f | ||
|
|
112b895d56 | ||
|
|
d824e8a934 | ||
|
|
3e02961c77 | ||
|
|
17b0dcc8fb | ||
|
|
6f5b44be11 | ||
|
|
2938b08e3d | ||
|
|
68c3287ad9 | ||
|
|
274fef2112 | ||
|
|
0cc01d3355 | ||
|
|
ba6a32ad3f | ||
|
|
a6ff929e22 | ||
|
|
840e27f20a | ||
|
|
007f4b8185 | ||
|
|
12659700af | ||
|
|
37a65efd89 | ||
|
|
4f7fb896cb | ||
|
|
5d452578e2 | ||
|
|
08974738f1 | ||
|
|
89070f0bbf | ||
|
|
18dff3f8e3 | ||
|
|
4b43c39ff5 | ||
|
|
2eec82fb99 | ||
|
|
24ea511ad1 | ||
|
|
18463b958f | ||
|
|
c4f71c9dda | ||
|
|
584f4efcb1 | ||
|
|
afc7963644 | ||
|
|
53f41d396e | ||
|
|
f561f27332 | ||
|
|
744239ca76 | ||
|
|
e942d1a1e6 | ||
|
|
a5430cda7b | ||
|
|
c57e43ea1d | ||
|
|
7710cba7ad | ||
|
|
3047311652 | ||
|
|
3459fbc871 | ||
|
|
370f683985 | ||
|
|
1baf19d486 | ||
|
|
0e16a271b7 | ||
|
|
4a756399f1 | ||
|
|
4401fbc6a6 | ||
|
|
9a68f0784d | ||
|
|
8ca1d31b90 | ||
|
|
cf3cd3d2d1 | ||
|
|
c16952a4c9 | ||
|
|
b6e785bd2a | ||
|
|
da0dbeb6b3 | ||
|
|
76f5b99c52 | ||
|
|
60986083dc | ||
|
|
adec441065 | ||
|
|
fe8fb5fa42 | ||
|
|
d62de27f63 | ||
|
|
fd4c0d97a2 | ||
|
|
2669e11c9d | ||
|
|
ee6b270776 | ||
|
|
9651ad7ef1 | ||
|
|
e19f2042bb | ||
|
|
a54f30b8ae | ||
|
|
10825d3f70 | ||
|
|
fa35ba87e5 | ||
|
|
20c0b48070 | ||
|
|
09cae00038 | ||
|
|
68557a8248 | ||
|
|
1021674f61 | ||
|
|
3859d3149b | ||
|
|
7eb76ee80c | ||
|
|
9b94ce6405 | ||
|
|
9f2852a56e | ||
|
|
69133c9118 | ||
|
|
24ef246391 | ||
|
|
36882e09dd | ||
|
|
eb27e3e711 | ||
|
|
dc882fffd8 | ||
|
|
585a64f7ac | ||
|
|
1bfe99b9cd | ||
|
|
b73f92c78c | ||
|
|
b787cdeda7 | ||
|
|
773e6aef04 | ||
|
|
ca5bfcaaed | ||
|
|
d29aa79996 | ||
|
|
b1e940c7d9 | ||
|
|
272ddcd831 | ||
|
|
175f3e3f0e | ||
|
|
f29104ad5d | ||
|
|
64ceef447c | ||
|
|
d44ee8814a | ||
|
|
5c2be54ad6 | ||
|
|
05b52469ea | ||
|
|
b58d107ba7 | ||
|
|
3e29ae0923 | ||
|
|
0496fc3790 | ||
|
|
b3ce52a6c8 | ||
|
|
9540045a6f | ||
|
|
c24c1cd689 | ||
|
|
7d7ef5ea74 | ||
|
|
5ca2bf4ae8 | ||
|
|
a1039db7af | ||
|
|
5adceea7e9 | ||
|
|
7f83252e23 | ||
|
|
de1d824439 | ||
|
|
9e2cd09cd7 | ||
|
|
ae7c43bcfd | ||
|
|
d660bae517 | ||
|
|
c1ac1add27 | ||
|
|
35dbc444db | ||
|
|
0ec20681d2 | ||
|
|
817fae5955 | ||
|
|
426ec0cb67 | ||
|
|
560c1c94b4 | ||
|
|
126ca98252 | ||
|
|
398c56c20b | ||
|
|
cd816b6774 | ||
|
|
613e985fdb | ||
|
|
84e64785bb | ||
|
|
ea9b4568bf | ||
|
|
76016cd3f8 | ||
|
|
af282312f1 | ||
|
|
97d8c63951 | ||
|
|
97607ac033 | ||
|
|
ea6dc3ef22 | ||
|
|
6fb49a3a8a | ||
|
|
77a7e3da2e | ||
|
|
39745be4e8 | ||
|
|
f7c968128a | ||
|
|
22387f42f3 | ||
|
|
3007d997bf | ||
|
|
7843b30796 | ||
|
|
52ace4f5a7 | ||
|
|
7c42541a0b | ||
|
|
ae8ca63fa7 | ||
|
|
1232ecacf7 | ||
|
|
95f362c9ce | ||
|
|
060b55bc6e | ||
|
|
deda94a779 | ||
|
|
f99f42a979 | ||
|
|
b3f35fb16e | ||
|
|
1c462e8784 | ||
|
|
07d95d189c | ||
|
|
d8688975a2 | ||
|
|
74f6890ce8 | ||
|
|
ab17fb2fdd | ||
|
|
2bb6d1d4db | ||
|
|
07beafc679 | ||
|
|
a40c61a8fa | ||
|
|
e13ded1a5d | ||
|
|
98534bc836 | ||
|
|
9b673591db | ||
|
|
496be79aa1 | ||
|
|
c0fcac3393 | ||
|
|
4d43812c77 | ||
|
|
5a65269ef3 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
# Composer
|
||||
.composer
|
||||
/vendor
|
||||
vendor/*
|
||||
!*/vendor/*
|
||||
|
||||
# Sass
|
||||
.sass-cache
|
||||
@@ -25,6 +26,7 @@ user/plugins/*
|
||||
user/themes/*
|
||||
!user/themes/.*
|
||||
user/localhost/config/security.yaml
|
||||
user/config/security.yaml
|
||||
|
||||
# OS Generated
|
||||
.DS_Store*
|
||||
|
||||
10
.htaccess
10
.htaccess
@@ -13,6 +13,16 @@ RewriteEngine On
|
||||
|
||||
## End - RewriteBase
|
||||
|
||||
## Begin - X-Forwarded-Proto
|
||||
# In some hosted or load balanced environments, SSL negotiation happens upstream.
|
||||
# In order for Grav to recognize the connection as secure, you need to uncomment
|
||||
# the following lines.
|
||||
#
|
||||
# RewriteCond %{HTTP:X-Forwarded-Proto} https
|
||||
# RewriteRule .* - [E=HTTPS:on]
|
||||
#
|
||||
## End - X-Forwarded-Proto
|
||||
|
||||
## Begin - Exploits
|
||||
# If you experience problems on your site block out the operations listed below
|
||||
# This attempts to block the most common type of exploit `attempts` to Grav
|
||||
|
||||
@@ -43,8 +43,6 @@ env:
|
||||
- secure: "einUtSEkUWy2IrqLXyVjwUU+mwaaoiOXRRVdLBpA3Zye6bZx8cm5h/5AplkPWhM/NmCJoW/MwNZHHkFhlr3mDRov5iOxVmTTYfnXB+I5lxYTSgduOLLErS7mU8hfADpVDU8bHNU44fNGD3UEiG1PD4qQBX4DMlqIFmR20mjs81k="
|
||||
# GH_API_USER [for curl]
|
||||
- secure: "AQGcX1B2NrI8ajflY4AimZDNcK2kBA3F6mbtEFQ78NkDoWhMipsQHayWXiSTzRc0YJKvQl2Y16MTwQF4VHzjTAiiZFATgA8J88vQUjIPabi/kKjqSmcLFoaAOAxStQbW6e0z2GiQ6KBMcNF1y5iUuI63xVrBvtKrYX/w5y+ako8="
|
||||
# Latest Release version
|
||||
- TRAVIS_TAG=$(curl --fail -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4)
|
||||
|
||||
before_install:
|
||||
- export TZ=Pacific/Honolulu
|
||||
@@ -56,6 +54,7 @@ before_install:
|
||||
composer install --dev --prefer-dist;
|
||||
fi
|
||||
- if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "5.6" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
|
||||
export TRAVIS_TAG=$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4);
|
||||
go get github.com/aktau/github-release;
|
||||
git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null;
|
||||
if [ ! -z "$TRAVIS_TAG" ]; then
|
||||
@@ -69,7 +68,7 @@ script:
|
||||
- if [ $TRAVIS_BRANCH == 'develop' ] || [ $TRAVIS_PULL_REQUEST != 'false' ]; then
|
||||
vendor/bin/codecept run;
|
||||
fi
|
||||
- echo $TRAVIS_TAG
|
||||
- echo "Latest Release Tag - ${TRAVIS_TAG}"
|
||||
- if [ ! -z "$TRAVIS_TAG" ] && [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "5.6" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
|
||||
FILES="$RT_DEVTOOLS/grav-dist/*.zip";
|
||||
for file in ${FILES[@]}; do
|
||||
|
||||
238
CHANGELOG.md
238
CHANGELOG.md
@@ -1,3 +1,237 @@
|
||||
# v1.1.14
|
||||
## 01/18/2017
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed `page.collection()` returning array and not Collection object when header variable did not exist
|
||||
* Revert `Content-Encoding: identity` fix, and let you set `cache: allow_webserver_gzip:` option to switch to `identity` [#548](https://github.com/getgrav/grav/issues/548)
|
||||
|
||||
# v1.1.13
|
||||
## 01/17/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added new `never_cache_twig` page option in `system.yaml` and frontmatter. Allows dynamic Twig logic in regular and modular Twig templates [#1244](https://github.com/getgrav/grav/pull/1244)
|
||||
1. [](#improved)
|
||||
* Several improvements to aid theme development [#232](https://github.com/getgrav/grav/pull/1232)
|
||||
* Added `hash` cache check option and made dropdown more descriptive [Admin #923](https://github.com/getgrav/grav-plugin-admin/issues/923)
|
||||
1. [](#bugfix)
|
||||
* Fixed cross volume file system operations [#635](https://github.com/getgrav/grav/issues/635)
|
||||
* Fix issue with pages folders validation not accepting uppercase letters
|
||||
* Fix renaming the folder name if the page, in the default language, had a custom slug set in its header
|
||||
* Fixed issue with `Content-Encoding: none`. It should really be `Content-Encoding: identity` instead
|
||||
* Fixed broken `hash` method on page modifications detection
|
||||
* Fixed issue with multi-lang pages not caching independently without unique `.md` file [#1211](https://github.com/getgrav/grav/issues/1211)
|
||||
* Fixed all `$_GET` parameters missing in Nginx (please update your nginx.conf) [#1245](https://github.com/getgrav/grav/issues/1245)
|
||||
* Fixed issue in trying to process broken symlink [#1254](https://github.com/getgrav/grav/issues/1254)
|
||||
|
||||
# v1.1.12
|
||||
## 12/26/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed issue with JSON calls throwing errors due to debugger enabled [#1227](https://github.com/getgrav/grav/issues/1227)
|
||||
|
||||
# v1.1.11
|
||||
## 12/22/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Fall back properly to HTML if template type not found
|
||||
1. [](#bugfix)
|
||||
* Fix issue with modular pages folders validation [#900](https://github.com/getgrav/grav-plugin-admin/issues/900)
|
||||
|
||||
# v1.1.10
|
||||
## 12/21/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Improve detection of home path. Also allow `~/.grav` on Windows, drop `ConsoleTrait::isWindows()` method, used only for that [#1204](https://github.com/getgrav/grav/pull/1204)
|
||||
* Reworked PHP CLI router [#1219](https://github.com/getgrav/grav/pull/1219)
|
||||
* More robust theme/plugin logic in `bin/gpm direct-install`
|
||||
1. [](#bugfix)
|
||||
* Fixed case where extracting a package would cause an error during rename
|
||||
* Fix issue with using `Yaml::parse` direcly on a filename, now deprecated
|
||||
* Add pattern for frontend validation of folder slugs [#891](https://github.com/getgrav/grav-plugin-admin/issues/891)
|
||||
* Fix issue with Inflector when translation is disabled [SimpleSearch #87](https://github.com/getgrav/grav-plugin-simplesearch/issues/87)
|
||||
* Explicitly expose `array_unique` Twig filter [Admin #897](https://github.com/getgrav/grav-plugin-admin/issues/897)
|
||||
|
||||
# v1.1.9
|
||||
## 12/13/2016
|
||||
|
||||
1. [](#new)
|
||||
* RC released as stable
|
||||
1. [](#improved)
|
||||
* Better error handling in cache clear
|
||||
* YAML syntax fixes for the future compatibility
|
||||
* Added new parameter `remove` for `onBeforeCacheClear` event
|
||||
* Add support for calling Media object as function to get medium by filename
|
||||
1. [](#bugfix)
|
||||
* Added checks before accessing admin reference during `Page::blueprints()` call. Allows to access `page.blueprints` from Twig in the frontend
|
||||
|
||||
# v1.1.9-rc.3
|
||||
## 12/07/2016
|
||||
|
||||
1. [](#new)
|
||||
* Add `ignore_empty` property to be used on array fields, if positive only save options with a value
|
||||
* Use new `permissions` field in user account
|
||||
* Add `range(int start, int end, int step)` twig function to generate an array of numbers between start and end, inclusive
|
||||
* New retina Media image derivatives array support (``) [#1147](https://github.com/getgrav/grav/pull/1147)
|
||||
* Added stream support for images (``)
|
||||
* Added stream support for links (`[Download PDF](user://data/pdf/my.pdf)`)
|
||||
* Added new `onBeforeCacheClear` event to add custom paths to cache clearing process
|
||||
1. [](#improved)
|
||||
* Added alias `selfupdate` to the `self-upgrade` `bin/gpm` CLI command
|
||||
* Synced `webserver-configs/htaccess.txt` with `.htaccess`
|
||||
* Use permissions field in group details.
|
||||
* Updated vendor libraries
|
||||
* Added a warning on GPM update to update Grav first if needed [#1194](https://github.com/getgrav/grav/pull/1194)
|
||||
1. [](#bugfix)
|
||||
* Fix page collections problem with `@page.modular` [#1178](https://github.com/getgrav/grav/pull/1178)
|
||||
* Fix issue with using a multiple taxonomy filter of which one had no results, thanks to @hughbris [#1184](https://github.com/getgrav/grav/issues/1184)
|
||||
* Fix saving permissions in group
|
||||
* Fixed issue with redirect of a page getting moved to a different location
|
||||
|
||||
# v1.1.9-rc.2
|
||||
## 11/26/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added two new sort order options for pages: `publish_date` and `unpublish_date` [#1173](https://github.com/getgrav/grav/pull/1173))
|
||||
1. [](#improved)
|
||||
* Multisite: Create image cache folder if it doesn't exist
|
||||
* Add 2 new language values for French [#1174](https://github.com/getgrav/grav/issues/1174)
|
||||
1. [](#bugfix)
|
||||
* Fixed issue when we have a meta file without corresponding media [#1179](https://github.com/getgrav/grav/issues/1179)
|
||||
* Update class namespace for Admin class [Admin #874](https://github.com/getgrav/grav-plugin-admin/issues/874)
|
||||
|
||||
# v1.1.9-rc.1
|
||||
## 11/09/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added a `CompiledJsonFile` object to better handle Json files.
|
||||
* Added Base32 encode/decode class
|
||||
* Added a new `User::find()` method
|
||||
1. [](#improved)
|
||||
* Moved `messages` object into core Grav from login plugin
|
||||
* Added `getTaxonomyItemKeys` to the Taxonomy object [#1124](https://github.com/getgrav/grav/issues/1124)
|
||||
* Added a `redirect_me` Twig function [#1124](https://github.com/getgrav/grav/issues/1124)
|
||||
* Added a Caddyfile for newer Caddy versions [#1115](https://github.com/getgrav/grav/issues/1115)
|
||||
* Allow to override sorting flags for page header-based or default ordering. If the `intl` PHP extension is loaded, only these flags are available: https://secure.php.net/manual/en/collator.asort.php. Otherwise, you can use the PHP standard sorting flags (https://secure.php.net/manual/en/array.constants.php) [#1169](https://github.com/getgrav/grav/issues/1169)
|
||||
1. [](#bugfix)
|
||||
* Fixed an issue with site redirects/routes, not processing with extension (.html, .json, etc.)
|
||||
* Don't truncate HTML if content length is less than summary size [#1125](https://github.com/getgrav/grav/issues/1125)
|
||||
* Return max available number when calling random() on a collection passing an int > available items [#1135](https://github.com/getgrav/grav/issues/1135)
|
||||
* Use correct ratio when applying image filters to image alternatives [#1147](https://github.com/getgrav/grav/issues/1147)
|
||||
* Fixed URI path in multi-site when query parameters were used in front page
|
||||
|
||||
# v1.1.8
|
||||
## 10/22/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed warning with unset `ssl` option when using GPM [#1132](https://github.com/getgrav/grav/issues/1132)
|
||||
|
||||
# v1.1.7
|
||||
## 10/22/2016
|
||||
|
||||
1. [](#improved)
|
||||
* Improved the capabilities of Image derivatives [#1107](https://github.com/getgrav/grav/pull/1107)
|
||||
1. [](#bugfix)
|
||||
* Only pass verify_peer settings to cURL and fopen if the setting is disabled [#1120](https://github.com/getgrav/grav/issues/1120)
|
||||
|
||||
# v1.1.6
|
||||
## 10/19/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added ability for Page to override the output format (`html`, `xml`, etc..) [#1067](https://github.com/getgrav/grav/issues/1067)
|
||||
* Added `Utils::getExtensionByMime()` and cleaned up `Utils::getMimeByExtension` + tests
|
||||
* Added a `cache.check.method: 'hash'` option in `system.yaml` that checks all files + dates inclusively
|
||||
* Include jQuery 3.x in the Grav assets
|
||||
* Added the option to automatically fix orientation on images based on their Exif data, by enabling `system.images.auto_fix_orientation`.
|
||||
1. [](#improved)
|
||||
* Add `batch()` function to Page Collection class
|
||||
* Added new `cache.redis.socket` setting that allow to pass a UNIX socket as redis server
|
||||
* It is now possible to opt-out of the SSL verification via the new `system.gpm.verify_peer` setting. This is sometimes necessary when receiving a "GPM Unable to Connect" error. More details in ([#1053](https://github.com/getgrav/grav/issues/1053))
|
||||
* It is now possible to force the use of either `curl` or `fopen` as `Response` connection method, via the new `system.gpm.method` setting. By default this is set to 'auto' and gives priority to 'fopen' first, curl otherwise.
|
||||
* InstallCommand can now handle Licenses
|
||||
* Uses more helpful `1x`, `2x`, `3x`, etc names in the Retina derivatives cache files.
|
||||
* Added new method `Plugins::isPluginActiveAdmin()` to check if plugin route is active in Admin plugin
|
||||
* Added new `Cache::setEnabled` and `Cache::getEnabled` to enable outside control of cache
|
||||
* Updated vendor libs including Twig `1.25.0`
|
||||
* Avoid git ignoring any vendor folder in a Grav site subfolder (but still ignore the main `vendor/` folder)
|
||||
* Added an option to get just a route back from `Uri::convertUrl()` function
|
||||
* Added option to control split session [#1096](https://github.com/getgrav/grav/pull/1096)
|
||||
* Added new `verbosity` levels to `system.error.display` to allow for system error messages [#1091](https://github.com/getgrav/grav/pull/1091)
|
||||
* Improved the API for Grav plugins to access the Parsedown parser directly [#1062](https://github.com/getgrav/grav/pull/1062)
|
||||
1. [](#bugfix)
|
||||
* Fixed missing `progress` method in the DirectInstall Command
|
||||
* `Response` class now handles better unsuccessful requests such as 404 and 401
|
||||
* Fixed saving of `external` page types [Admin #789](https://github.com/getgrav/grav-plugin-admin/issues/789)
|
||||
* Fixed issue deleting parent folder of folder with `param_sep` in the folder name [admin #796](https://github.com/getgrav/grav-plugin-admin/issues/796)
|
||||
* Fixed an issue with streams in `bin/plugin`
|
||||
* Fixed `jpeg` file format support in Media
|
||||
|
||||
# v1.1.5
|
||||
## 09/09/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added new `bin/gpm direct-install` command to install local and remote zip archives
|
||||
1. [](#improved)
|
||||
* Refactored `onPageNotFound` event to fire after `onPageInitialized`
|
||||
* Follow symlinks in `Folder::all()`
|
||||
* Twig variable `base_url` now supports multi-site by path feature
|
||||
* Improved `bin/plugin` to list plugins with commands faster by limiting the depth of recursion
|
||||
1. [](#bugfix)
|
||||
* Quietly skip missing streams in `Cache::clearCache()`
|
||||
* Fix issue in calling page.summary when no content is present in a page
|
||||
* Fix for HUGE session timeouts [#1050](https://github.com/getgrav/grav/issues/1050)
|
||||
|
||||
# v1.1.4
|
||||
## 09/07/2016
|
||||
|
||||
1. [](#new)
|
||||
* Added new `tmp` folder at root. Accessible via stream `tmp://`. Can be cleared with `bin/grav clear --tmp-only` as well as `--all`.
|
||||
* Added support for RTL in `LanguageCodes` so you can determine if a language is RTL or not
|
||||
* Ability to set `custom_base_url` in system configuration
|
||||
* Added `override` and `force` options for Streams setup
|
||||
1. [](#improved)
|
||||
* Important vendor updates to provide PHP 7.1 beta support!
|
||||
* Added a `Util::arrayFlatten()` static function
|
||||
* Added support for 'external_url' page header to enable easier external URL based menu items
|
||||
* Improved the UI for CLI GPM Index view to use a table
|
||||
* Added `@page.modular` Collection type [#988](https://github.com/getgrav/grav/issues/988)
|
||||
* Added support for `self@`, `page@`, `taxonomy@`, `root@` Collection syntax for cleaner YAML compatibility
|
||||
* Improved GPM commands to allow for `-y` to automate **yes** responses and `-o` for **update** and **selfupgrade** to overwrite installations [#985](https://github.com/getgrav/grav/issues/985)
|
||||
* Added randomization to `safe_email` Twig filter for greater security [#998](https://github.com/getgrav/grav/issues/998)
|
||||
* Allow `Utils::setDotNotation` to merge data, rather than just set
|
||||
* Moved default `Image::filter()` to the `save` action to ensure they are applied last [#984](https://github.com/getgrav/grav/issues/984)
|
||||
* Improved the `Truncator` code to be more reliable [#1019](https://github.com/getgrav/grav/issues/1019)
|
||||
* Moved media blueprints out of core (now in Admin plugin)
|
||||
1. [](#bugfix)
|
||||
* Removed 307 redirect code option as it is not well supported [#743](https://github.com/getgrav/grav-plugin-admin/issues/743)
|
||||
* Fixed issue with folders with name `*.md` are not confused with pages [#995](https://github.com/getgrav/grav/issues/995)
|
||||
* Fixed an issue when filtering collections causing null key
|
||||
* Fix for invalid HTML when rendering GIF and Vector media [#1001](https://github.com/getgrav/grav/issues/1001)
|
||||
* Use pages.markdown.extra in the user's system.yaml [#1007](https://github.com/getgrav/grav/issues/1007)
|
||||
* Fix for `Memcached` connection [#1020](https://github.com/getgrav/grav/issues/1020)
|
||||
|
||||
# v1.1.3
|
||||
## 08/14/2016
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fix for lightbox media function throwing error [#981](https://github.com/getgrav/grav/issues/981)
|
||||
|
||||
# v1.1.2
|
||||
## 08/10/2016
|
||||
|
||||
1. [](#new)
|
||||
* Allow forcing SSL by setting `system.force_ssl` (Force SSL in the Admin System Config) [#899](https://github.com/getgrav/grav/pull/899)
|
||||
1. [](#improved)
|
||||
* Improved `authorize` Twig extension to accept a nested array of authorizations [#948](https://github.com/getgrav/grav/issues/948)
|
||||
* Don't add timestamps on remote assets as it can cause conflicts
|
||||
* Grav now looks at types from `media.yaml` when retrieving page mime types [#966](https://github.com/getgrav/grav/issues/966)
|
||||
* Added support for dumping exceptions in the Debugger
|
||||
1. [](#bugfix)
|
||||
* Fixed `Folder::delete` method to recursively remove files and folders and causing Upgrade to fail.
|
||||
* Fix [#952](https://github.com/getgrav/grav/issues/952) hyphenize the session name.
|
||||
* If no parent is set and siblings collection is called, return a new and empty collection [grav-plugin-sitemap/issues/22](https://github.com/getgrav/grav-plugin-sitemap/issues/22)
|
||||
* Prevent exception being thrown when calling the Collator constructor failed in a Windows environment with the Intl PHP Extension enabled [#961](https://github.com/getgrav/grav/issues/961)
|
||||
* Fix for markdown images not properly rendering `id` attribute [#956](https://github.com/getgrav/grav/issues/956)
|
||||
|
||||
# v1.1.1
|
||||
## 07/16/2016
|
||||
|
||||
@@ -731,7 +965,7 @@
|
||||
* Added new `onImageMediumSaved()` event (useful for post-image processing)
|
||||
* Added `Vary: Accept-Encoding` option
|
||||
2. [](#improved)
|
||||
* Multilang-safe delimeter position
|
||||
* Multilang-safe delimiter position
|
||||
* Refactored Twig classes and added optional umask setting
|
||||
* Removed `pageinit()` timing
|
||||
* `Page->routable()` now takes `published()` state into account
|
||||
@@ -817,7 +1051,7 @@
|
||||
* Improved query string handling
|
||||
* Optimization to image handling supporting URL encoded filenames
|
||||
* Use global `composer` when available rather than Grv provided one
|
||||
* Use `PHP_BINARY` contant rather than `php` executable
|
||||
* Use `PHP_BINARY` constant rather than `php` executable
|
||||
* Updated Doctrine Cache library
|
||||
* Updated Symfony libraries
|
||||
* Moved `convertUrl()` method to Uri object
|
||||
|
||||
Binary file not shown.
20
bin/gpm
20
bin/gpm
@@ -7,6 +7,7 @@ if (!file_exists(__DIR__ . '/../vendor')){
|
||||
}
|
||||
|
||||
use Grav\Common\Composer;
|
||||
use Grav\Common\Config\Setup;
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
@@ -37,9 +38,24 @@ if (!function_exists('curl_version')) {
|
||||
exit('FATAL: GPM requires PHP Curl module to be installed');
|
||||
}
|
||||
|
||||
$climate = new League\CLImate\CLImate;
|
||||
$climate->arguments->add([
|
||||
'environment' => [
|
||||
'prefix' => 'e',
|
||||
'longPrefix' => 'env',
|
||||
'description' => 'Configuration Environment',
|
||||
'defaultValue' => 'localhost'
|
||||
]
|
||||
]);
|
||||
$climate->arguments->parse();
|
||||
$environment = $climate->arguments->get('environment');
|
||||
|
||||
// Set up environment based on params.
|
||||
Setup::$environment = $environment;
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav['config']->init();
|
||||
$grav['uri']->init();
|
||||
$grav['config']->init();
|
||||
$grav['streams'];
|
||||
|
||||
$app = new Application('Grav Package Manager', GRAV_VERSION);
|
||||
@@ -51,7 +67,7 @@ $app->addCommands(array(
|
||||
new \Grav\Console\Gpm\UninstallCommand(),
|
||||
new \Grav\Console\Gpm\UpdateCommand(),
|
||||
new \Grav\Console\Gpm\SelfupgradeCommand(),
|
||||
new \Grav\Console\Gpm\DirectInstallCommand(),
|
||||
));
|
||||
|
||||
$app->setDefaultCommand('index');
|
||||
$app->run();
|
||||
|
||||
25
bin/plugin
25
bin/plugin
@@ -21,6 +21,7 @@ use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Config\Setup;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
|
||||
$autoload = require_once(__DIR__ . '/../vendor/autoload.php');
|
||||
@@ -37,12 +38,29 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
|
||||
$climate = new League\CLImate\CLImate;
|
||||
$climate->arguments->add([
|
||||
'environment' => [
|
||||
'prefix' => 'e',
|
||||
'longPrefix' => 'env',
|
||||
'description' => 'Configuration Environment',
|
||||
'defaultValue' => 'localhost'
|
||||
]
|
||||
]);
|
||||
$climate->arguments->parse();
|
||||
$environment = $climate->arguments->get('environment');
|
||||
|
||||
// Set up environment based on params.
|
||||
Setup::$environment = $environment;
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav['uri']->init();
|
||||
$grav['config']->init();
|
||||
$grav['streams'];
|
||||
$grav['plugins']->init();
|
||||
$grav['themes']->init();
|
||||
|
||||
|
||||
$app = new Application('Grav Plugins Commands', GRAV_VERSION);
|
||||
$pattern = '([A-Z]\w+Command\.php)';
|
||||
|
||||
@@ -70,7 +88,7 @@ if (!$name) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>Example:</red>");
|
||||
$output->writeln(" {$bin} error log -l 1 --trace");
|
||||
$list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm']);
|
||||
$list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm', 'levels' => 2]);
|
||||
|
||||
if (count($list)) {
|
||||
$available = [];
|
||||
@@ -101,14 +119,15 @@ if ($plugin === null) {
|
||||
$path = 'plugins://' . $name . '/cli';
|
||||
|
||||
try {
|
||||
$commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm']);
|
||||
$commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm', 'levels' => 1]);
|
||||
} catch (\RuntimeException $e) {
|
||||
$output->writeln("<red>No Console Commands for <white>'{$name}'</white> where found in <white>'{$path}'</white></red>");
|
||||
exit;
|
||||
}
|
||||
|
||||
foreach ($commands as $command_path) {
|
||||
require_once "plugins://{$name}/cli/{$command_path}";
|
||||
$full_path = $grav['locator']->findResource("plugins://{$name}/cli/{$command_path}");
|
||||
require_once $full_path;
|
||||
|
||||
$command_class = 'Grav\Plugin\Console\\' . preg_replace('/.php$/', '', $command_path);
|
||||
$command = new $command_class();
|
||||
|
||||
@@ -19,15 +19,17 @@
|
||||
"filp/whoops": "~2.0",
|
||||
"matthiasmullie/minify": "^1.3",
|
||||
"monolog/monolog": "~1.0",
|
||||
"gregwar/image": "~2.0",
|
||||
"gregwar/image": "dev-master#72568cfbeb77515278f2ccb386fc344e874b7ae8",
|
||||
"donatj/phpuseragentparser": "~0.3",
|
||||
"pimple/pimple": "~3.0",
|
||||
"rockettheme/toolbox": "dev-develop",
|
||||
"rockettheme/toolbox": "~1.0",
|
||||
"maximebf/debugbar": "~1.10",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-zip": "*"
|
||||
"ext-zip": "*",
|
||||
"league/climate": "^3.2",
|
||||
"antoligy/dom-string-iterators": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.1",
|
||||
|
||||
820
composer.lock
generated
820
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,12 @@ if (!is_file($autoload)) {
|
||||
die("Please run: <i>bin/grav install</i>");
|
||||
}
|
||||
|
||||
if (PHP_SAPI == 'cli-server') {
|
||||
if (!isset($_SERVER['PHP_CLI_ROUTER'])) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER["SERVER_NAME"]}:{$_SERVER["SERVER_PORT"]} system/router.php</pre>");
|
||||
}
|
||||
}
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
|
||||
4
system/assets/jquery/jquery-3.x.min.js
vendored
Normal file
4
system/assets/jquery/jquery-3.x.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -16,6 +16,13 @@ form:
|
||||
placeholder: PLUGIN_ADMIN.SITE_TITLE_PLACEHOLDER
|
||||
help: PLUGIN_ADMIN.SITE_TITLE_HELP
|
||||
|
||||
default_lang:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.SITE_DEFAULT_LANG
|
||||
size: vsmall
|
||||
placeholder: PLUGIN_ADMIN.SITE_DEFAULT_LANG_PLACEHOLDER
|
||||
help: PLUGIN_ADMIN.SITE_DEFAULT_LANG_HELP
|
||||
|
||||
author.name:
|
||||
type: text
|
||||
size: large
|
||||
|
||||
@@ -178,8 +178,9 @@ form:
|
||||
help: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE_HELP
|
||||
options:
|
||||
301: 301 - Permanent
|
||||
302: 302 - Found
|
||||
303: 303 - Other
|
||||
307: 307 - Temporary
|
||||
304: 304 - Not Modified
|
||||
|
||||
pages.redirect_trailing_slash:
|
||||
type: toggle
|
||||
@@ -243,6 +244,17 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
pages.never_cache_twig:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.NEVER_CACHE_TWIG
|
||||
help: PLUGIN_ADMIN.NEVER_CACHE_TWIG_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
pages.frontmatter.process_twig:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.FRONTMATTER_PROCESS_TWIG
|
||||
@@ -460,14 +472,15 @@ form:
|
||||
|
||||
cache.check.method:
|
||||
type: select
|
||||
size: small
|
||||
size: medium
|
||||
classes: fancy
|
||||
label: PLUGIN_ADMIN.CACHE_CHECK_METHOD
|
||||
help: PLUGIN_ADMIN.CACHE_CHECK_METHOD_HELP
|
||||
options:
|
||||
file: File
|
||||
folder: Folder
|
||||
none: None
|
||||
file: Markdown + Yaml file timestamps
|
||||
folder: Folder timestamps
|
||||
hash: All files timestamps
|
||||
none: No timestamp checking
|
||||
|
||||
cache.driver:
|
||||
type: select
|
||||
@@ -513,7 +526,14 @@ form:
|
||||
label: PLUGIN_ADMIN.MEMCACHED_PORT
|
||||
help: PLUGIN_ADMIN.MEMCACHED_PORT_HELP
|
||||
placeholder: "11211"
|
||||
|
||||
|
||||
cache.redis.socket:
|
||||
type: text
|
||||
size: medium
|
||||
label: PLUGIN_ADMIN.REDIS_SOCKET
|
||||
help: PLUGIN_ADMIN.REDIS_SOCKET_HELP
|
||||
placeholder: "/var/run/redis/redis.sock"
|
||||
|
||||
cache.redis.server:
|
||||
type: text
|
||||
size: medium
|
||||
@@ -555,6 +575,17 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
cache.allow_webserver_gzip:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP
|
||||
help: PLUGIN_ADMIN.ALLOW_WEBSERVER_GZIP_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.TWIG_TEMPLATING
|
||||
@@ -756,15 +787,16 @@ form:
|
||||
|
||||
fields:
|
||||
errors.display:
|
||||
type: toggle
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.DISPLAY_ERRORS
|
||||
help: PLUGIN_ADMIN.DISPLAY_ERRORS_HELP
|
||||
highlight: 0
|
||||
size: medium
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
-1: PLUGIN_ADMIN.ERROR_SYSTEM
|
||||
0: PLUGIN_ADMIN.ERROR_SIMPLE
|
||||
1: PLUGIN_ADMIN.ERROR_FULL_BACKTRACE
|
||||
|
||||
|
||||
errors.log:
|
||||
type: toggle
|
||||
@@ -843,7 +875,6 @@ form:
|
||||
'0755': '0755'
|
||||
'0775': '0775'
|
||||
|
||||
|
||||
images.debug:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.IMAGES_DEBUG
|
||||
@@ -855,6 +886,17 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
images.auto_fix_orientation:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION
|
||||
help: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
media.upload_limit:
|
||||
type: text
|
||||
append: bytes
|
||||
@@ -953,6 +995,24 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
session.path:
|
||||
type: text
|
||||
size: small
|
||||
label: PLUGIN_ADMIN.SESSION_PATH
|
||||
help: PLUGIN_ADMIN.SESSION_PATH_HELP
|
||||
|
||||
session.split:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.SESSION_SPLIT
|
||||
help: PLUGIN_ADMIN.SESSION_SPLIT_HELP
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
default: true
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
advanced:
|
||||
type: section
|
||||
title: PLUGIN_ADMIN.ADVANCED
|
||||
@@ -975,6 +1035,27 @@ form:
|
||||
label: PLUGIN_ADMIN.PROXY_URL
|
||||
help: PLUGIN_ADMIN.PROXY_URL_HELP
|
||||
|
||||
gpm.method:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.GPM_METHOD
|
||||
highlight: auto
|
||||
help: PLUGIN_ADMIN.GPM_METHOD_HELP
|
||||
options:
|
||||
auto: PLUGIN_ADMIN.AUTO
|
||||
fopen: PLUGIN_ADMIN.FOPEN
|
||||
curl: PLUGIN_ADMIN.CURL
|
||||
|
||||
gpm.verify_peer:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.GPM_VERIFY_PEER
|
||||
highlight: 1
|
||||
help: PLUGIN_ADMIN.GPM_VERIFY_PEER_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
reverse_proxy_setup:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.REVERSE_PROXY
|
||||
@@ -1018,3 +1099,21 @@ form:
|
||||
options:
|
||||
':': ': (default)'
|
||||
';': '; (for Apache running on Windows)'
|
||||
|
||||
force_ssl:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.FORCE_SSL
|
||||
highlight: 0
|
||||
help: PLUGIN_ADMIN.FORCE_SSL_HELP
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
custom_base_url:
|
||||
type: text
|
||||
size: medium
|
||||
placeholder: "e.g. http://localhost:8080"
|
||||
label: PLUGIN_ADMIN.CUSTOM_BASE_URL
|
||||
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
alt_text:
|
||||
type: string
|
||||
label: Alt Text
|
||||
@@ -1,8 +0,0 @@
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
route:
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.PAGE
|
||||
classes: fancy
|
||||
data-options@: '\Grav\Common\Page\Pages::parents'
|
||||
@@ -1,8 +0,0 @@
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
new_file_name:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN_PRO.NEW_FILE_NAME
|
||||
validate:
|
||||
required: true
|
||||
@@ -2,7 +2,7 @@ title: PLUGIN_ADMIN.DEFAULT
|
||||
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-zа-я][a-zа-я0-9_\-]+"
|
||||
pattern: '[a-zа-я][a-zа-я0-9_\-]+'
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
@@ -133,13 +133,14 @@ form:
|
||||
label: PLUGIN_ADMIN.FOLDER_NAME
|
||||
validate:
|
||||
type: slug
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.PARENT
|
||||
classes: fancy
|
||||
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
|
||||
data-default@: '\Grav\Plugin\admin::rawRoute'
|
||||
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
|
||||
options:
|
||||
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
|
||||
|
||||
@@ -224,6 +225,30 @@ form:
|
||||
twig: Twig
|
||||
use: keys
|
||||
|
||||
header.twig_first:
|
||||
type: toggle
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.TWIG_FIRST
|
||||
help: PLUGIN_ADMIN.TWIG_FIRST_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
header.never_cache_twig:
|
||||
type: toggle
|
||||
toggleable: true
|
||||
label: PLUGIN_ADMIN.NEVER_CACHE_TWIG
|
||||
help: PLUGIN_ADMIN.NEVER_CACHE_TWIG_HELP
|
||||
highlight: 0
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
header.child_type:
|
||||
type: select
|
||||
toggleable: true
|
||||
|
||||
58
system/blueprints/pages/external.yaml
Normal file
58
system/blueprints/pages/external.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
title: PLUGIN_ADMIN:EXTERNAL
|
||||
@extends:
|
||||
type: default
|
||||
context: blueprints://pages
|
||||
|
||||
rules:
|
||||
slug:
|
||||
pattern: '[a-zа-я][a-zа-я0-9_\-]+'
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
validation: loose
|
||||
fields:
|
||||
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
|
||||
fields:
|
||||
|
||||
content:
|
||||
fields:
|
||||
|
||||
header.title:
|
||||
type: text
|
||||
autofocus: true
|
||||
style: horizontal
|
||||
label: PLUGIN_ADMIN.TITLE
|
||||
|
||||
content:
|
||||
unset@: true
|
||||
|
||||
uploads:
|
||||
unset@: true
|
||||
|
||||
header.external_url:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.EXTERNAL_URL
|
||||
placeholder: https://getgrav.org
|
||||
validate:
|
||||
required: true
|
||||
options:
|
||||
fields:
|
||||
|
||||
publishing:
|
||||
|
||||
fields:
|
||||
|
||||
header.date:
|
||||
unset@: true
|
||||
|
||||
header.metadata:
|
||||
unset@: true
|
||||
|
||||
taxonomies:
|
||||
unset@: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
pattern: '[a-z][a-z0-9_\-]+'
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
@@ -24,13 +24,14 @@ form:
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.PAGE
|
||||
classes: fancy
|
||||
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
|
||||
data-default@: '\Grav\Plugin\admin::rawRoute'
|
||||
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
pattern: '[a-z][a-z0-9_\-]+'
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
@@ -73,13 +73,14 @@ form:
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.PARENT
|
||||
classes: fancy
|
||||
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
|
||||
data-default@: '\Grav\Plugin\admin::rawRoute'
|
||||
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
|
||||
options:
|
||||
'': PLUGIN_ADMIN.DEFAULT_OPTION_SELECT
|
||||
validate:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
pattern: '[a-z][a-z0-9_\-]+'
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
@@ -12,6 +12,6 @@ form:
|
||||
label: PLUGIN_ADMIN.PARENT
|
||||
classes: fancy
|
||||
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
|
||||
data-default@: '\Grav\Plugin\admin::rawRoute'
|
||||
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
|
||||
options:
|
||||
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
pattern: '[a-z][a-z0-9_\-]+'
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
@@ -26,13 +26,14 @@ form:
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.PARENT_PAGE
|
||||
classes: fancy
|
||||
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
|
||||
data-default@: '\Grav\Plugin\admin::getLastPageRoute'
|
||||
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageRoute'
|
||||
options:
|
||||
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
|
||||
validate:
|
||||
@@ -44,7 +45,7 @@ form:
|
||||
label: PLUGIN_ADMIN.PAGE_FILE
|
||||
help: PLUGIN_ADMIN.PAGE_FILE_HELP
|
||||
data-options@: '\Grav\Common\Page\Pages::types'
|
||||
data-default@: '\Grav\Plugin\admin::getLastPageName'
|
||||
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageName'
|
||||
validate:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
pattern: '[a-z][a-z0-9_\-]+'
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
@@ -19,13 +19,14 @@ form:
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.PARENT_PAGE
|
||||
classes: fancy
|
||||
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
|
||||
data-default@: '\Grav\Plugin\admin::getLastPageRoute'
|
||||
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageRoute'
|
||||
options:
|
||||
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
|
||||
validate:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
pattern: '[a-z][a-z0-9_\-]+'
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
@@ -73,13 +73,14 @@ form:
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
|
||||
|
||||
route:
|
||||
type: select
|
||||
label: PLUGIN_ADMIN.PARENT
|
||||
classes: fancy
|
||||
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
|
||||
data-default@: '\Grav\Plugin\admin::rawRoute'
|
||||
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
|
||||
options:
|
||||
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
title: Site
|
||||
title: Account
|
||||
form:
|
||||
validation: loose
|
||||
|
||||
@@ -56,7 +56,7 @@ form:
|
||||
label: PLUGIN_ADMIN.LANGUAGE
|
||||
size: medium
|
||||
classes: fancy
|
||||
data-options@: '\Grav\Plugin\admin::adminLanguages'
|
||||
data-options@: '\Grav\Plugin\Admin\Admin::adminLanguages'
|
||||
default: 'en'
|
||||
help: PLUGIN_ADMIN.LANGUAGE_HELP
|
||||
|
||||
@@ -77,16 +77,9 @@ form:
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
access.admin:
|
||||
type: array
|
||||
label: PLUGIN_ADMIN.ADMIN_ACCESS
|
||||
multiple: false
|
||||
validate:
|
||||
type: array
|
||||
|
||||
access.site:
|
||||
type: array
|
||||
label: PLUGIN_ADMIN.SITE_ACCESS
|
||||
multiple: false
|
||||
access:
|
||||
type: permissions
|
||||
label: PLUGIN_ADMIN.PERMISSIONS
|
||||
ignore_empty: true
|
||||
validate:
|
||||
type: array
|
||||
|
||||
@@ -29,16 +29,9 @@ form:
|
||||
size: small
|
||||
label: PLUGIN_ADMIN_PRO.ICON
|
||||
|
||||
access.admin:
|
||||
type: array
|
||||
label: PLUGIN_ADMIN.ADMIN_ACCESS
|
||||
multiple: false
|
||||
validate:
|
||||
type: array
|
||||
|
||||
access.site:
|
||||
type: array
|
||||
label: PLUGIN_ADMIN.SITE_ACCESS
|
||||
multiple: false
|
||||
access:
|
||||
type: permissions
|
||||
label: PLUGIN_ADMIN.PERMISSIONS
|
||||
ignore_empty: true
|
||||
validate:
|
||||
type: array
|
||||
|
||||
@@ -114,6 +114,9 @@ types:
|
||||
xlm:
|
||||
type: file
|
||||
mime: application/vnd.ms-excel
|
||||
xlsm:
|
||||
type: file
|
||||
mime: application/vnd.ms-excel
|
||||
xld:
|
||||
type: file
|
||||
mime: application/vnd.ms-excel
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
title: Grav # Name of the site
|
||||
default_lang: en # Default language for site (potentially used by theme)
|
||||
|
||||
author:
|
||||
name: John Appleseed # Default author name
|
||||
@@ -16,12 +17,12 @@ summary:
|
||||
delimiter: === # The summary delimiter
|
||||
|
||||
redirects:
|
||||
# /redirect-test: / # Redirect test goes to home page
|
||||
# /old/(.*): /new/$1 # Would redirect /old/my-page to /new/my-page
|
||||
# '/redirect-test': '/' # Redirect test goes to home page
|
||||
# '/old/(.*)': '/new/$1' # Would redirect /old/my-page to /new/my-page
|
||||
|
||||
routes:
|
||||
# /something/else: '/blog/sample-3' # Alias for /blog/sample-3
|
||||
# /new/(.*): '/blog/$1' # Regex any /new/my-page URL to /blog/my-page Route
|
||||
# '/something/else': '/blog/sample-3' # Alias for /blog/sample-3
|
||||
# '/new/(.*)': '/blog/$1' # Regex any /new/my-page URL to /blog/my-page Route
|
||||
|
||||
blog:
|
||||
route: '/blog' # Custom value added (accessible via system.blog.route)
|
||||
|
||||
@@ -4,7 +4,8 @@ default_locale: # Default locale (defaults to system
|
||||
param_sep: ':' # Parameter separator, use ';' for Apache on windows
|
||||
wrapped_site: false # For themes/plugins to know if Grav is wrapped by another platform
|
||||
reverse_proxy_setup: false # Running in a reverse proxy scenario with different webserver ports than proxy
|
||||
|
||||
force_ssl: false # If enabled, Grav forces to be accessed via HTTPS (NOTE: Not an ideal solution)
|
||||
custom_base_url: '' # Set the base_url manually
|
||||
|
||||
languages:
|
||||
supported: [] # List of languages supported. eg: [en, fr, de]
|
||||
@@ -35,9 +36,10 @@ pages:
|
||||
markdown: true # Process Markdown
|
||||
twig: false # Process Twig
|
||||
twig_first: false # Process Twig before markdown when processing both on a page
|
||||
never_cache_twig: false # Only cache content, never cache twig processed in content (incompatible with `twig_first: true`)
|
||||
events:
|
||||
page: true # Enable page level events
|
||||
twig: true # Enable twig level events
|
||||
twig: true # Enable Twig level events
|
||||
markdown:
|
||||
extra: false # Enable support for Markdown Extra support (GFM by default)
|
||||
auto_line_breaks: false # Enable automatic line breaks
|
||||
@@ -61,19 +63,22 @@ pages:
|
||||
url_taxonomy_filters: true # Enable auto-magic URL-based taxonomy filters for page collections
|
||||
frontmatter:
|
||||
process_twig: false # Should the frontmatter be processed to replace Twig variables?
|
||||
ignore_fields: ['form'] # Fields that might contain Twig variables and should not be processed
|
||||
ignore_fields: ['form','forms'] # Fields that might contain Twig variables and should not be processed
|
||||
|
||||
cache:
|
||||
enabled: true # Set to true to enable caching
|
||||
check:
|
||||
method: file # Method to check for updates in pages: file|folder|none
|
||||
method: file # Method to check for updates in pages: file|folder|hash|none
|
||||
driver: auto # One of: auto|file|apc|xcache|memcache|wincache
|
||||
prefix: 'g' # Cache prefix string (prevents cache conflicts)
|
||||
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
|
||||
gzip: false # GZip compress the page output
|
||||
allow_webserver_gzip: false # If true, `content-encoding: identity` but connection isn't closed before `onShutDown()` event
|
||||
redis:
|
||||
socket: false # Path to redis unix socket (e.g. /var/run/redis/redis.sock), false = use server and port to connect
|
||||
|
||||
twig:
|
||||
cache: true # Set to true to enable twig caching
|
||||
cache: true # Set to true to enable Twig caching
|
||||
debug: true # Enable Twig debug
|
||||
auto_reload: true # Refresh cache on changes
|
||||
autoescape: false # Autoescape Twig vars
|
||||
@@ -97,7 +102,7 @@ assets: # Configuration for Assets Manager (
|
||||
jquery: system://assets/jquery/jquery-2.x.min.js
|
||||
|
||||
errors:
|
||||
display: false # Display full backtrace-style error page
|
||||
display: 0 # Display either (1) Full backtrace | (0) Simple Error | (-1) System Error
|
||||
log: true # Log errors to /logs folder
|
||||
|
||||
debugger:
|
||||
@@ -110,6 +115,7 @@ images:
|
||||
cache_all: false # Cache all image by default
|
||||
cache_perms: '0755' # MUST BE IN QUOTES!! Default cache folder perms. Usually '0755' or '0775'
|
||||
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
|
||||
auto_fix_orientation: false # Automatically fix the image orientation based on the Exif data
|
||||
|
||||
media:
|
||||
enable_media_timestamp: false # Enable media timetsamps
|
||||
@@ -123,7 +129,11 @@ session:
|
||||
name: grav-site # Name prefix of the session cookie. Use alphanumeric, dashes or underscores only. Do not use dots in the session name
|
||||
secure: false # Set session secure. If true, indicates that communication for this cookie must be over an encrypted transmission. Enable this only on sites that run exclusively on HTTPS
|
||||
httponly: true # Set session HTTP only. If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed.
|
||||
split: true # Sessions should be independent between site and plugins (such as admin)
|
||||
path:
|
||||
|
||||
gpm:
|
||||
releases: stable # Set to either 'stable' or 'testing'
|
||||
proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
|
||||
method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
|
||||
verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.1.1');
|
||||
define('GRAV_VERSION', '1.1.14');
|
||||
define('GRAV_TESTING', false);
|
||||
define('DS', '/');
|
||||
define('GRAV_PHP_MIN', '5.5.9');
|
||||
|
||||
5
system/pages/notfound.md
Normal file
5
system/pages/notfound.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Not Found
|
||||
routable: false
|
||||
notfound: true
|
||||
---
|
||||
26
system/router.php
Normal file
26
system/router.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Core
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli-server') {
|
||||
exit('This script cannot be run from browser. Run it from a CLI.');
|
||||
}
|
||||
|
||||
$_SERVER['PHP_CLI_ROUTER'] = true;
|
||||
|
||||
if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_NAME'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$_SERVER = array_merge($_SERVER, $_ENV);
|
||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR . 'index.php';
|
||||
|
||||
require 'index.php';
|
||||
|
||||
error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4);
|
||||
@@ -207,7 +207,7 @@ class Assets
|
||||
$this->assets_url = $locator->findResource('asset://', false);
|
||||
|
||||
$this->config($asset_config);
|
||||
$this->base_url = $base_url . '/';
|
||||
$this->base_url = ($config->get('system.absolute_urls') ? '' : '/') . ltrim(ltrim($base_url, '/') . '/', '/');
|
||||
|
||||
// Register any preconfigured collections
|
||||
foreach ($config->get('system.assets.collections', []) as $name => $collection) {
|
||||
@@ -281,8 +281,9 @@ class Assets
|
||||
return $this;
|
||||
}
|
||||
|
||||
$modified = 0;
|
||||
if (!$this->isRemoteLink($asset)) {
|
||||
$modified = false;
|
||||
$remote = $this->isRemoteLink($asset);
|
||||
if (!$remote) {
|
||||
$modified = $this->getLastModificationTime($asset);
|
||||
$asset = $this->buildLocalLink($asset);
|
||||
}
|
||||
@@ -294,6 +295,7 @@ class Assets
|
||||
|
||||
$data = [
|
||||
'asset' => $asset,
|
||||
'remote' => $remote,
|
||||
'priority' => intval($priority ?: 10),
|
||||
'order' => count($this->css),
|
||||
'pipeline' => (bool) $pipeline,
|
||||
@@ -345,8 +347,9 @@ class Assets
|
||||
return $this;
|
||||
}
|
||||
|
||||
$modified = 0;
|
||||
if (!$this->isRemoteLink($asset)) {
|
||||
$modified = false;
|
||||
$remote = $this->isRemoteLink($asset);
|
||||
if (!$remote) {
|
||||
$modified = $this->getLastModificationTime($asset);
|
||||
$asset = $this->buildLocalLink($asset);
|
||||
}
|
||||
@@ -358,6 +361,7 @@ class Assets
|
||||
|
||||
$data = [
|
||||
'asset' => $asset,
|
||||
'remote' => $remote ,
|
||||
'priority' => intval($priority ?: 10),
|
||||
'order' => count($this->js),
|
||||
'pipeline' => (bool) $pipeline,
|
||||
@@ -558,7 +562,7 @@ class Assets
|
||||
foreach ($this->css_no_pipeline as $file) {
|
||||
if ($group && $file['group'] == $group) {
|
||||
$media = isset($file['media']) ? sprintf(' media="%s"', $file['media']) : '';
|
||||
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . $media . ' />' . "\n";
|
||||
$output .= '<link href="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . $media . ' />' . "\n";
|
||||
}
|
||||
}
|
||||
if (!$this->css_pipeline_before_excludes && $pipeline_result) {
|
||||
@@ -568,7 +572,7 @@ class Assets
|
||||
foreach ($this->css as $file) {
|
||||
if ($group && $file['group'] == $group) {
|
||||
$media = isset($file['media']) ? sprintf(' media="%s"', $file['media']) : '';
|
||||
$output .= '<link href="' . $file['asset'] . $this->timestamp . '"' . $attributes . $media . ' />' . "\n";
|
||||
$output .= '<link href="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . $media . ' />' . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,7 +640,7 @@ class Assets
|
||||
}
|
||||
foreach ($this->js_no_pipeline as $file) {
|
||||
if ($group && $file['group'] == $group) {
|
||||
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
|
||||
$output .= '<script src="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
|
||||
}
|
||||
}
|
||||
if (!$this->js_pipeline_before_excludes && $pipeline_result) {
|
||||
@@ -645,7 +649,7 @@ class Assets
|
||||
} else {
|
||||
foreach ($this->js as $file) {
|
||||
if ($group && $file['group'] == $group) {
|
||||
$output .= '<script src="' . $file['asset'] . $this->timestamp . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
|
||||
$output .= '<script src="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -693,14 +697,14 @@ class Assets
|
||||
|
||||
// If pipeline exist return it
|
||||
if (file_exists($this->assets_dir . $file)) {
|
||||
return $relative_path . $this->timestamp;
|
||||
return $relative_path . $this->getTimestamp();
|
||||
}
|
||||
|
||||
// Remove any non-pipeline files
|
||||
foreach ($this->css as $id => $asset) {
|
||||
if ($asset['group'] == $group) {
|
||||
if (!$asset['pipeline'] ||
|
||||
($this->isRemoteLink($asset['asset']) && $this->css_pipeline_include_externals === false)) {
|
||||
($asset['remote'] && $this->css_pipeline_include_externals === false)) {
|
||||
$this->css_no_pipeline[$id] = $asset;
|
||||
} else {
|
||||
$temp_css[$id] = $asset;
|
||||
@@ -740,7 +744,7 @@ class Assets
|
||||
if (strlen(trim($buffer)) > 0) {
|
||||
file_put_contents($this->assets_dir . $file, $buffer);
|
||||
|
||||
return $relative_path . $this->timestamp;
|
||||
return $relative_path . $this->getTimestamp();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -775,14 +779,14 @@ class Assets
|
||||
|
||||
// If pipeline exist return it
|
||||
if (file_exists($this->assets_dir . $file)) {
|
||||
return $relative_path . $this->timestamp;
|
||||
return $relative_path . $this->getTimestamp();
|
||||
}
|
||||
|
||||
// Remove any non-pipeline files
|
||||
foreach ($this->js as $id => $asset) {
|
||||
if ($asset['group'] == $group) {
|
||||
if (!$asset['pipeline'] ||
|
||||
($this->isRemoteLink($asset['asset']) && $this->js_pipeline_include_externals === false)) {
|
||||
($asset['remote'] && $this->js_pipeline_include_externals === false)) {
|
||||
$this->js_no_pipeline[] = $asset;
|
||||
} else {
|
||||
$temp_js[$id] = $asset;
|
||||
@@ -812,7 +816,7 @@ class Assets
|
||||
if (strlen(trim($buffer)) > 0) {
|
||||
file_put_contents($this->assets_dir . $file, $buffer);
|
||||
|
||||
return $relative_path . $this->timestamp;
|
||||
return $relative_path . $this->getTimestamp();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1342,6 +1346,21 @@ class Assets
|
||||
$this->timestamp = '?' . $value;
|
||||
}
|
||||
|
||||
public function getTimestamp($asset = null)
|
||||
{
|
||||
if (is_array($asset)) {
|
||||
if ($asset['remote'] === false) {
|
||||
if (Utils::contains($asset['asset'], '?')) {
|
||||
return str_replace('?', '&', $this->timestamp);
|
||||
} else {
|
||||
return $this->timestamp;
|
||||
}
|
||||
}
|
||||
} elseif (empty($asset)) {
|
||||
return $this->timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,8 @@ class ZipBackup
|
||||
'backup',
|
||||
'cache',
|
||||
'images',
|
||||
'logs'
|
||||
'logs',
|
||||
'tmp'
|
||||
];
|
||||
|
||||
protected static $ignoreFolders = [
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Grav\Common;
|
||||
use \Doctrine\Common\Cache as DoctrineCache;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
/**
|
||||
* The GravCache object is used throughout Grav to store and retrieve cached data.
|
||||
@@ -65,7 +65,8 @@ class Cache extends Getters
|
||||
protected static $all_remove = [
|
||||
'cache://',
|
||||
'cache://images',
|
||||
'asset://'
|
||||
'asset://',
|
||||
'tmp://'
|
||||
];
|
||||
|
||||
protected static $assets_remove = [
|
||||
@@ -80,6 +81,10 @@ class Cache extends Getters
|
||||
'cache://'
|
||||
];
|
||||
|
||||
protected static $tmp_remove = [
|
||||
'tmp://'
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -110,7 +115,9 @@ class Cache extends Getters
|
||||
|
||||
$prefix = $this->config->get('system.cache.prefix');
|
||||
|
||||
$this->enabled = (bool)$this->config->get('system.cache.enabled');
|
||||
if (is_null($this->enabled)) {
|
||||
$this->enabled = (bool)$this->config->get('system.cache.enabled');
|
||||
}
|
||||
|
||||
// Cache key allows us to invalidate all cache on configuration changes.
|
||||
$this->key = ($prefix ? $prefix : 'g') . '-' . substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION),
|
||||
@@ -122,10 +129,36 @@ class Cache extends Getters
|
||||
|
||||
// Set the cache namespace to our unique key
|
||||
$this->driver->setNamespace($this->key);
|
||||
}
|
||||
|
||||
// Dump Cache state
|
||||
$grav['debugger']->addMessage('Cache: [' . ($this->enabled ? 'true' : 'false') . '] Setting: [' . $this->driver_setting . '] Driver: [' . $this->driver_name . ']');
|
||||
/**
|
||||
* Public accessor to set the enabled state of the cache
|
||||
*
|
||||
* @param $enabled
|
||||
*/
|
||||
public function setEnabled($enabled)
|
||||
{
|
||||
$this->enabled = (bool) $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current enabled state
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getEnabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache state
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheStatus()
|
||||
{
|
||||
return 'Cache: [' . ($this->enabled ? 'true' : 'false') . '] Setting: [' . $this->driver_setting . '] Driver: [' . $this->driver_name . ']';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +216,7 @@ class Cache extends Getters
|
||||
|
||||
case 'memcached':
|
||||
$memcached = new \Memcached();
|
||||
$memcached->connect($this->config->get('system.cache.memcached.server', 'localhost'),
|
||||
$memcached->addServer($this->config->get('system.cache.memcached.server', 'localhost'),
|
||||
$this->config->get('system.cache.memcached.port', 11211));
|
||||
$driver = new DoctrineCache\MemcachedCache();
|
||||
$driver->setMemcached($memcached);
|
||||
@@ -191,8 +224,15 @@ class Cache extends Getters
|
||||
|
||||
case 'redis':
|
||||
$redis = new \Redis();
|
||||
$redis->connect($this->config->get('system.cache.redis.server', 'localhost'),
|
||||
$socket = $this->config->get('system.cache.redis.socket', false);
|
||||
|
||||
if ($socket) {
|
||||
$redis->connect($socket);
|
||||
} else {
|
||||
$redis->connect($this->config->get('system.cache.redis.server', 'localhost'),
|
||||
$this->config->get('system.cache.redis.port', 6379));
|
||||
}
|
||||
|
||||
$driver = new DoctrineCache\RedisCache();
|
||||
$driver->setRedis($redis);
|
||||
break;
|
||||
@@ -309,39 +349,45 @@ class Cache extends Getters
|
||||
case 'cache-only':
|
||||
$remove_paths = self::$cache_remove;
|
||||
break;
|
||||
case 'tmp-only':
|
||||
$remove_paths = self::$tmp_remove;
|
||||
break;
|
||||
default:
|
||||
$remove_paths = self::$standard_remove;
|
||||
}
|
||||
|
||||
// Clearing cache event to add paths to clear
|
||||
Grav::instance()->fireEvent('onBeforeCacheClear', new Event(['remove' => $remove, 'paths' => &$remove_paths]));
|
||||
|
||||
foreach ($remove_paths as $stream) {
|
||||
|
||||
// Convert stream to a real path
|
||||
$path = $locator->findResource($stream, true, true);
|
||||
// Make sure path exists before proceeding, otherwise we would wipe ROOT_DIR
|
||||
if (!$path) {
|
||||
throw new \RuntimeException("Stream '{$stream}' not found", 500);
|
||||
}
|
||||
try {
|
||||
$path = $locator->findResource($stream, true, true);
|
||||
|
||||
$anything = false;
|
||||
$files = glob($path . '/*');
|
||||
$anything = false;
|
||||
$files = glob($path . '/*');
|
||||
|
||||
if (is_array($files)) {
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
if (@unlink($file)) {
|
||||
$anything = true;
|
||||
}
|
||||
} elseif (is_dir($file)) {
|
||||
if (Folder::delete($file)) {
|
||||
$anything = true;
|
||||
if (is_array($files)) {
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
if (@unlink($file)) {
|
||||
$anything = true;
|
||||
}
|
||||
} elseif (is_dir($file)) {
|
||||
if (Folder::delete($file)) {
|
||||
$anything = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($anything) {
|
||||
$output[] = '<red>Cleared: </red>' . $path . '/*';
|
||||
if ($anything) {
|
||||
$output[] = '<red>Cleared: </red>' . $path . '/*';
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// stream not found or another error while deleting files.
|
||||
$output[] = '<red>ERROR: </red>' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Setup extends Data
|
||||
{
|
||||
public static $environment;
|
||||
|
||||
protected $streams = [
|
||||
'system' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
@@ -26,6 +28,7 @@ class Setup extends Data
|
||||
],
|
||||
'user' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['user'],
|
||||
]
|
||||
@@ -78,6 +81,7 @@ class Setup extends Data
|
||||
],
|
||||
'cache' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['cache'],
|
||||
'images' => ['images']
|
||||
@@ -85,16 +89,25 @@ class Setup extends Data
|
||||
],
|
||||
'log' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['logs']
|
||||
]
|
||||
],
|
||||
'backup' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['backup']
|
||||
]
|
||||
],
|
||||
'tmp' => [
|
||||
'type' => 'Stream',
|
||||
'force' => true,
|
||||
'prefixes' => [
|
||||
'' => ['tmp']
|
||||
]
|
||||
],
|
||||
'image' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
@@ -120,7 +133,7 @@ class Setup extends Data
|
||||
*/
|
||||
public function __construct($container)
|
||||
{
|
||||
$environment = $container['uri']->environment() ?: 'localhost';
|
||||
$environment = isset(static::$environment) ? static::$environment : ($container['uri']->environment() ?: 'localhost');
|
||||
|
||||
// Pre-load setup.php which contains our initial configuration.
|
||||
// Configuration may contain dynamic parts, which is why we need to always load it.
|
||||
@@ -137,8 +150,8 @@ class Setup extends Data
|
||||
parent::__construct($setup);
|
||||
|
||||
// Set up environment.
|
||||
$this->def('environment', $environment);
|
||||
$this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->environment}"]]);
|
||||
$this->def('environment', $environment ?: 'cli');
|
||||
$this->def('streams.schemes.environment.prefixes', ['' => ($environment ? ["user://{$this->environment}"] : [])]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,9 +207,13 @@ class Setup extends Data
|
||||
if (isset($config['paths'])) {
|
||||
$locator->addPath($scheme, '', $config['paths']);
|
||||
}
|
||||
|
||||
$override = isset($config['override']) ? $config['override'] : false;
|
||||
$force = isset($config['force']) ? $config['force'] : false;
|
||||
|
||||
if (isset($config['prefixes'])) {
|
||||
foreach ($config['prefixes'] as $prefix => $paths) {
|
||||
$locator->addPath($scheme, $prefix, $paths);
|
||||
$locator->addPath($scheme, $prefix, $paths, $override, $force);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,18 @@ class Blueprint extends BlueprintForm
|
||||
return $this->blueprintSchema->filter($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return blueprint data schema.
|
||||
*
|
||||
* @return BlueprintSchema
|
||||
*/
|
||||
public function schema()
|
||||
{
|
||||
$this->initInternals();
|
||||
|
||||
return $this->blueprintSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize validator.
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Parser;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
@@ -37,12 +38,6 @@ class Validation
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
|
||||
// Special case for files, value is never empty and errors with code 4 instead.
|
||||
if (empty($validate['required']) && $field['type'] == 'file' && isset($value['error'])
|
||||
&& ($value['error'] == UPLOAD_ERR_NO_FILE || in_array(UPLOAD_ERR_NO_FILE, $value['error']))) {
|
||||
return $messages;
|
||||
}
|
||||
|
||||
// Get language class.
|
||||
$language = Grav::instance()['language'];
|
||||
|
||||
@@ -101,12 +96,6 @@ class Validation
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
|
||||
// Special case for files, value is never empty and errors with code 4 instead.
|
||||
if (empty($validate['required']) && $field['type'] == 'file' && isset($value['error'])
|
||||
&& ($value['error'] == UPLOAD_ERR_NO_FILE || in_array(UPLOAD_ERR_NO_FILE, $value['error']))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If this is a YAML field, simply parse it and return the value.
|
||||
if (isset($field['yaml']) && $field['yaml'] === true) {
|
||||
try {
|
||||
@@ -581,6 +570,7 @@ class Validation
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if ($options) {
|
||||
$useKey = isset($field['use']) && $field['use'] == 'keys';
|
||||
foreach ($values as $key => $value) {
|
||||
@@ -592,9 +582,22 @@ class Validation
|
||||
foreach ($values as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$value = implode(',', $value);
|
||||
$values[$key] = array_map('trim', explode(',', $value));
|
||||
} else {
|
||||
$values[$key] = trim($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) {
|
||||
foreach ($values as $key => $value) {
|
||||
foreach ($value as $inner_key => $inner_value) {
|
||||
if ($inner_value == '') {
|
||||
unset($value[$inner_key]);
|
||||
}
|
||||
}
|
||||
|
||||
$values[$key] = array_map('trim', explode(',', $value));
|
||||
$values[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,14 @@ class Debugger
|
||||
public function addAssets()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
|
||||
// Only add assets if Page is HTML
|
||||
$page = $this->grav['page'];
|
||||
if ($page->templateFormat() != 'html') {
|
||||
$this->enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Assets $assets */
|
||||
$assets = $this->grav['assets'];
|
||||
|
||||
@@ -217,4 +225,19 @@ class Debugger
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump exception into the Messages tab of the Debug Bar
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @return Debugger
|
||||
*/
|
||||
public function addException(\Exception $e)
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
$this->debugbar['exceptions']->addException($e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
24
system/src/Grav/Common/Errors/BareHandler.php
Normal file
24
system/src/Grav/Common/Errors/BareHandler.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Errors
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Errors;
|
||||
|
||||
use Whoops\Handler\Handler;
|
||||
|
||||
class BareHandler extends Handler
|
||||
{
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
return Handler::QUIT;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,16 +22,30 @@ class Errors
|
||||
// Setup Whoops-based error handler
|
||||
$whoops = new \Whoops\Run;
|
||||
|
||||
$verbosity = 1;
|
||||
|
||||
if (isset($config['display'])) {
|
||||
if ($config['display']) {
|
||||
if (is_int($config['display'])) {
|
||||
$verbosity = $config['display'];
|
||||
} else {
|
||||
$verbosity = $config['display'] ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($verbosity) {
|
||||
case 1:
|
||||
$error_page = new Whoops\Handler\PrettyPageHandler;
|
||||
$error_page->setPageTitle('Crikey! There was an error...');
|
||||
$error_page->addResourcePath(GRAV_ROOT . '/system/assets');
|
||||
$error_page->addCustomCss('whoops.css');
|
||||
$whoops->pushHandler($error_page);
|
||||
} else {
|
||||
break;
|
||||
case -1:
|
||||
$whoops->pushHandler(new BareHandler);
|
||||
break;
|
||||
default:
|
||||
$whoops->pushHandler(new SimplePageHandler);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (method_exists('Whoops\Util\Misc', 'isAjaxRequest')) { //Whoops 2.0
|
||||
|
||||
@@ -23,58 +23,63 @@ trait CompiledFile
|
||||
// Set some options
|
||||
$this->settings(['native' => true, 'compat' => true]);
|
||||
|
||||
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
|
||||
if ($var === null && $this->raw === null && $this->content === null) {
|
||||
$key = md5($this->filename);
|
||||
$file = PhpFile::instance(CACHE_DIR . DS . "compiled/files/{$key}{$this->extension}.php");
|
||||
try {
|
||||
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
|
||||
if ($var === null && $this->raw === null && $this->content === null) {
|
||||
$key = md5($this->filename);
|
||||
$file = PhpFile::instance(CACHE_DIR . DS . "compiled/files/{$key}{$this->extension}.php");
|
||||
|
||||
$modified = $this->modified();
|
||||
$modified = $this->modified();
|
||||
|
||||
if (!$modified) {
|
||||
return $this->decode($this->raw());
|
||||
}
|
||||
|
||||
$class = get_class($this);
|
||||
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
|
||||
// Load real file if cache isn't up to date (or is invalid).
|
||||
if (
|
||||
!isset($cache['@class'])
|
||||
|| $cache['@class'] != $class
|
||||
|| $cache['modified'] != $modified
|
||||
|| $cache['filename'] != $this->filename
|
||||
) {
|
||||
// Attempt to lock the file for writing.
|
||||
try {
|
||||
$file->lock(false);
|
||||
} catch (\Exception $e) {
|
||||
// Another process has locked the file; we will check this in a bit.
|
||||
if (!$modified) {
|
||||
return $this->decode($this->raw());
|
||||
}
|
||||
|
||||
// Decode RAW file into compiled array.
|
||||
$data = (array) $this->decode($this->raw());
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'filename' => $this->filename,
|
||||
'modified' => $modified,
|
||||
'data' => $data
|
||||
];
|
||||
$class = get_class($this);
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
$cache = $file->exists() ? $file->content() : null;
|
||||
|
||||
// Compile cached file into bytecode cache
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate($file->filename(), true);
|
||||
// Load real file if cache isn't up to date (or is invalid).
|
||||
if (
|
||||
!isset($cache['@class'])
|
||||
|| $cache['@class'] != $class
|
||||
|| $cache['modified'] != $modified
|
||||
|| $cache['filename'] != $this->filename
|
||||
) {
|
||||
// Attempt to lock the file for writing.
|
||||
try {
|
||||
$file->lock(false);
|
||||
} catch (\Exception $e) {
|
||||
// Another process has locked the file; we will check this in a bit.
|
||||
}
|
||||
|
||||
// Decode RAW file into compiled array.
|
||||
$data = (array)$this->decode($this->raw());
|
||||
$cache = [
|
||||
'@class' => $class,
|
||||
'filename' => $this->filename,
|
||||
'modified' => $modified,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
// If compiled file wasn't already locked by another process, save it.
|
||||
if ($file->locked() !== false) {
|
||||
$file->save($cache);
|
||||
$file->unlock();
|
||||
|
||||
// Compile cached file into bytecode cache
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate($file->filename(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$file->free();
|
||||
$file->free();
|
||||
|
||||
$this->content = $cache['data'];
|
||||
$this->content = $cache['data'];
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException(sprintf('Failed to read %s: %s', basename($this->filename), $e->getMessage()), 500, $e);
|
||||
}
|
||||
|
||||
return parent::content($var);
|
||||
|
||||
28
system/src/Grav/Common/File/CompiledJsonFile.php
Normal file
28
system/src/Grav/Common/File/CompiledJsonFile.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.File
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\File;
|
||||
|
||||
use RocketTheme\Toolbox\File\JsonFile;
|
||||
|
||||
class CompiledJsonFile extends JsonFile
|
||||
{
|
||||
use CompiledFile;
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @param bool $assoc
|
||||
* @return array mixed
|
||||
*/
|
||||
protected function decode($var, $assoc = true)
|
||||
{
|
||||
return (array) json_decode($var, $assoc);
|
||||
}
|
||||
}
|
||||
@@ -70,15 +70,47 @@ abstract class Folder
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($iterator as $filepath => $file) {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_modified;
|
||||
try {
|
||||
$file_modified = $file->getMTime();
|
||||
if ($file_modified > $last_modified) {
|
||||
$last_modified = $file_modified;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Grav::instance()['log']->error('Could not process file: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $last_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively md5 hash all files in a path
|
||||
*
|
||||
* @param $path
|
||||
* @return string
|
||||
*/
|
||||
public static function hashAllFiles($path)
|
||||
{
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
$files = [];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new \RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
foreach ($iterator as $filepath => $file) {
|
||||
$files[] = $file->getPath() . $file->getMTime();
|
||||
}
|
||||
|
||||
return md5(serialize($files));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relative path between target and base path. If path isn't relative, return full path.
|
||||
*
|
||||
@@ -179,7 +211,8 @@ abstract class Folder
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
if ($recursive) {
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF;
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS
|
||||
+ \FilesystemIterator::CURRENT_AS_SELF + \FilesystemIterator::FOLLOW_SYMLINKS;
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
@@ -311,12 +344,10 @@ abstract class Folder
|
||||
// Make sure that path to the target exists before moving.
|
||||
self::create(dirname($target));
|
||||
|
||||
// Just rename the directory.
|
||||
$success = @rename($source, $target);
|
||||
|
||||
if (!$success) {
|
||||
$error = error_get_last();
|
||||
throw new \RuntimeException($error['message']);
|
||||
self::copy($source, $target);
|
||||
self::delete($source);
|
||||
}
|
||||
|
||||
// Make sure that the change will be detected when caching.
|
||||
@@ -357,7 +388,6 @@ abstract class Folder
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
public static function mkdir($folder)
|
||||
{
|
||||
@@ -367,7 +397,6 @@ abstract class Folder
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
public static function create($folder)
|
||||
{
|
||||
@@ -402,10 +431,7 @@ abstract class Folder
|
||||
|
||||
// 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;
|
||||
}
|
||||
Folder::mkdir($dest);
|
||||
}
|
||||
|
||||
// Open the source directory to read in files
|
||||
@@ -436,22 +462,11 @@ abstract class Folder
|
||||
return @unlink($folder);
|
||||
}
|
||||
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
/** @var \DirectoryIterator $fileinfo */
|
||||
foreach ($files as $fileinfo) {
|
||||
if ($fileinfo->isDir()) {
|
||||
if (false === @rmdir($fileinfo->getRealPath())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (false === @unlink($fileinfo->getRealPath())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Go through all items in filesystem and recursively remove everything.
|
||||
$files = array_diff(scandir($folder), array('.', '..'));
|
||||
foreach ($files as $file) {
|
||||
$path = "{$folder}/{$file}";
|
||||
(is_dir($path)) ? self::doDelete($path) : @unlink($path);
|
||||
}
|
||||
|
||||
return $include_target ? @rmdir($folder) : true;
|
||||
|
||||
@@ -61,14 +61,36 @@ class GPM extends Iterator
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Locally installed packages
|
||||
* @return Iterator The installed packages
|
||||
* Return the locally installed packages
|
||||
*
|
||||
* @return Local\Packages
|
||||
*/
|
||||
public function getInstalled()
|
||||
{
|
||||
return $this->installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Locally installable packages
|
||||
*
|
||||
* @param array $list_type_installed
|
||||
* @return Iterator The installed packages
|
||||
*/
|
||||
public function getInstallable($list_type_installed = ['plugins' => true, 'themes' => true])
|
||||
{
|
||||
$items = ['total' => 0];
|
||||
foreach ($list_type_installed as $type => $type_installed) {
|
||||
if ($type_installed === false) {
|
||||
continue;
|
||||
}
|
||||
$methodInstallableType = 'getInstalled' . ucfirst($type);
|
||||
$to_install = $this->$methodInstallableType();
|
||||
$items[$type] = $to_install;
|
||||
$items['total'] += count($to_install);
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of locally installed packages
|
||||
* @return integer Amount of installed packages
|
||||
|
||||
@@ -29,6 +29,8 @@ class Installer
|
||||
const ZIP_OPEN_ERROR = 32;
|
||||
/** @const Error while trying to extract the ZIP package */
|
||||
const ZIP_EXTRACT_ERROR = 64;
|
||||
/** @const Invalid source file */
|
||||
const INVALID_SOURCE = 128;
|
||||
|
||||
/**
|
||||
* Destination folder on which validation checks are applied
|
||||
@@ -62,13 +64,13 @@ class Installer
|
||||
/**
|
||||
* Installs a given package to a given destination.
|
||||
*
|
||||
* @param string $package The local path to the ZIP package
|
||||
* @param string $zip the local path to ZIP package
|
||||
* @param string $destination The local path to the Grav Instance
|
||||
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
|
||||
*
|
||||
* @return boolean True if everything went fine, False otherwise.
|
||||
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
|
||||
* @param string $extracted The local path to the extacted ZIP package
|
||||
* @return bool True if everything went fine, False otherwise.
|
||||
*/
|
||||
public static function install($package, $destination, $options = [])
|
||||
public static function install($zip, $destination, $options = [], $extracted = null)
|
||||
{
|
||||
$destination = rtrim($destination, DS);
|
||||
$options = array_merge(self::$options, $options);
|
||||
@@ -86,34 +88,26 @@ class Installer
|
||||
return false;
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$archive = $zip->open($package);
|
||||
$cache_dir = Grav::instance()['locator']->findResource('cache://', true);
|
||||
$tmp = $cache_dir . DS . 'tmp/Grav-' . uniqid();
|
||||
// Create a tmp location
|
||||
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
|
||||
$tmp = $tmp_dir . '/Grav-' . uniqid();
|
||||
|
||||
if ($archive !== true) {
|
||||
self::$error = self::ZIP_OPEN_ERROR;
|
||||
if (!$extracted) {
|
||||
$extracted = self::unZip($zip, $tmp);
|
||||
if (!$extracted) {
|
||||
Folder::delete($tmp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists($extracted)) {
|
||||
self::$error = self::INVALID_SOURCE;
|
||||
return false;
|
||||
}
|
||||
|
||||
Folder::mkdir($tmp);
|
||||
|
||||
$unzip = $zip->extractTo($tmp);
|
||||
|
||||
if (!$unzip) {
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
$zip->close();
|
||||
Folder::delete($tmp);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$package_folder_name = $zip->getNameIndex(0);
|
||||
$installer_file_folder = $tmp . '/' . $package_folder_name;
|
||||
|
||||
$is_install = true;
|
||||
$installer = self::loadInstaller($installer_file_folder, $is_install);
|
||||
$installer = self::loadInstaller($extracted, $is_install);
|
||||
|
||||
if (isset($options['is_update']) && $options['is_update'] === true) {
|
||||
$method = 'preUpdate';
|
||||
@@ -135,16 +129,15 @@ class Installer
|
||||
|
||||
if (!$options['sophisticated']) {
|
||||
if ($options['theme']) {
|
||||
self::copyInstall($zip, $install_path, $tmp);
|
||||
self::copyInstall($extracted, $install_path);
|
||||
} else {
|
||||
self::moveInstall($zip, $install_path, $tmp);
|
||||
self::moveInstall($extracted, $install_path);
|
||||
}
|
||||
} else {
|
||||
self::sophisticatedInstall($zip, $install_path, $tmp);
|
||||
self::sophisticatedInstall($extracted, $install_path);
|
||||
}
|
||||
|
||||
Folder::delete($tmp);
|
||||
$zip->close();
|
||||
|
||||
if (isset($options['is_update']) && $options['is_update'] === true) {
|
||||
$method = 'postUpdate';
|
||||
@@ -163,6 +156,43 @@ class Installer
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzip a file to somewhere
|
||||
*
|
||||
* @param $zip_file
|
||||
* @param $destination
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function unZip($zip_file, $destination)
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
$archive = $zip->open($zip_file);
|
||||
|
||||
if ($archive === true) {
|
||||
Folder::mkdir($destination);
|
||||
|
||||
$unzip = $zip->extractTo($destination);
|
||||
|
||||
|
||||
if (!$unzip) {
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
Folder::delete($destination);
|
||||
$zip->close();
|
||||
return false;
|
||||
}
|
||||
|
||||
$package_folder_name = preg_replace('#\./$#', '', $zip->getNameIndex(0));
|
||||
$zip->close();
|
||||
$extracted_folder = $destination . '/' . $package_folder_name;
|
||||
|
||||
return $extracted_folder;
|
||||
}
|
||||
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates and returns the package installer class
|
||||
*
|
||||
@@ -211,80 +241,67 @@ class Installer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ZipArchive $zip
|
||||
* @param $source_path
|
||||
* @param $install_path
|
||||
* @param $tmp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function moveInstall(\ZipArchive $zip, $install_path, $tmp)
|
||||
public static function moveInstall($source_path, $install_path)
|
||||
{
|
||||
$container = $zip->getNameIndex(0);
|
||||
if (file_exists($install_path)) {
|
||||
Folder::delete($install_path);
|
||||
}
|
||||
|
||||
Folder::move($tmp . DS . $container, $install_path);
|
||||
Folder::move($source_path, $install_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ZipArchive $zip
|
||||
* @param $source_path
|
||||
* @param $install_path
|
||||
* @param $tmp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function copyInstall(\ZipArchive $zip, $install_path, $tmp)
|
||||
public static function copyInstall($source_path, $install_path)
|
||||
{
|
||||
$firstDir = $zip->getNameIndex(0);
|
||||
if (empty($firstDir)) {
|
||||
throw new \RuntimeException("Directory $firstDir is missing");
|
||||
if (empty($source_path)) {
|
||||
throw new \RuntimeException("Directory $source_path is missing");
|
||||
} else {
|
||||
$tmp = realpath($tmp . DS . $firstDir);
|
||||
Folder::rcopy($tmp, $install_path);
|
||||
Folder::rcopy($source_path, $install_path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ZipArchive $zip
|
||||
* @param $source_path
|
||||
* @param $install_path
|
||||
* @param $tmp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function sophisticatedInstall(\ZipArchive $zip, $install_path, $tmp)
|
||||
public static function sophisticatedInstall($source_path, $install_path)
|
||||
{
|
||||
for ($i = 0, $l = $zip->numFiles; $i < $l; $i++) {
|
||||
$filename = $zip->getNameIndex($i);
|
||||
$fileinfo = pathinfo($filename);
|
||||
$depth = count(explode(DS, rtrim($filename, '/')));
|
||||
foreach (new \DirectoryIterator($source_path) as $file) {
|
||||
|
||||
if ($depth > 2) {
|
||||
if ($file->isLink() || $file->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $install_path . DS . $fileinfo['basename'];
|
||||
$path = $install_path . DS . $file->getBasename();
|
||||
|
||||
if (is_link($path)) {
|
||||
continue;
|
||||
} else {
|
||||
if (is_dir($path)) {
|
||||
Folder::delete($path);
|
||||
Folder::move($tmp . DS . $filename, $path);
|
||||
if ($file->isDir()) {
|
||||
Folder::delete($path);
|
||||
Folder::move($file->getPathname(), $path);
|
||||
|
||||
if ($fileinfo['basename'] == 'bin') {
|
||||
foreach (glob($path . DS . '*') as $file) {
|
||||
@chmod($file, 0755);
|
||||
}
|
||||
if ($file->getBasename() == 'bin') {
|
||||
foreach (glob($path . DS . '*') as $bin_file) {
|
||||
@chmod($bin_file, 0755);
|
||||
}
|
||||
} else {
|
||||
@unlink($path);
|
||||
@copy($tmp . DS . $filename, $path);
|
||||
}
|
||||
} else {
|
||||
@unlink($path);
|
||||
@copy($file->getPathname(), $path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
126
system/src/Grav/Common/GPM/Licenses.php
Normal file
126
system/src/Grav/Common/GPM/Licenses.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
|
||||
/**
|
||||
* Class Licenses
|
||||
*
|
||||
* @package Grav\Common\GPM
|
||||
*/
|
||||
class Licenses
|
||||
{
|
||||
|
||||
/**
|
||||
* Regex to validate the format of a License
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $regex = '^(?:[A-F0-9]{8}-){3}(?:[A-F0-9]{8}){1}$';
|
||||
|
||||
protected static $file;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the license for a Premium package
|
||||
*
|
||||
* @param $slug
|
||||
* @param $license
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function set($slug, $license)
|
||||
{
|
||||
$licenses = self::getLicenseFile();
|
||||
$data = $licenses->content();
|
||||
$slug = strtolower($slug);
|
||||
|
||||
if ($license && !self::validate($license)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_string($license)) {
|
||||
if (isset($data['licenses'][$slug])) {
|
||||
unset($data['licenses'][$slug]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$data['licenses'][$slug] = $license;
|
||||
}
|
||||
|
||||
$licenses->save($data);
|
||||
$licenses->free();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the license for a Premium package
|
||||
*
|
||||
* @param $slug
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get($slug = null)
|
||||
{
|
||||
$licenses = self::getLicenseFile();
|
||||
$data = $licenses->content();
|
||||
$licenses->free();
|
||||
$slug = strtolower($slug);
|
||||
|
||||
if (!$slug) {
|
||||
return isset($data['licenses']) ? $data['licenses'] : [];
|
||||
}
|
||||
|
||||
if (!isset($data['licenses']) || !isset($data['licenses'][$slug])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $data['licenses'][$slug];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the License format
|
||||
*
|
||||
* @param $license
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validate($license = null)
|
||||
{
|
||||
if (!is_string($license)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return preg_match('#' . self::$regex. '#', $license);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the License File object
|
||||
*
|
||||
* @return \RocketTheme\Toolbox\File\FileInterface
|
||||
*/
|
||||
public static function getLicenseFile()
|
||||
|
||||
{
|
||||
if (!isset(self::$file)) {
|
||||
$path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';;
|
||||
if (!file_exists($path)) {
|
||||
touch($path);
|
||||
}
|
||||
self::$file = CompiledYamlFile::instance($path);
|
||||
}
|
||||
|
||||
return self::$file;
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,10 @@ class Response
|
||||
CURLOPT_USERAGENT => 'Grav GPM',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_HEADER => false,
|
||||
//CURLOPT_SSL_VERIFYPEER => true, // this is set in the constructor since it's a setting
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
@@ -48,11 +50,17 @@ class Response
|
||||
//CURLOPT_PROGRESSFUNCTION => [$this, 'progress']
|
||||
],
|
||||
'fopen' => [
|
||||
'method' => 'GET',
|
||||
'user_agent' => 'Grav GPM',
|
||||
'max_redirects' => 5,
|
||||
'follow_location' => 1,
|
||||
'timeout' => 15,
|
||||
'method' => 'GET',
|
||||
'user_agent' => 'Grav GPM',
|
||||
'max_redirects' => 5,
|
||||
'follow_location' => 1,
|
||||
'timeout' => 15,
|
||||
/* // this is set in the constructor since it's a setting
|
||||
'ssl' => [
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
*/
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
@@ -101,8 +109,61 @@ class Response
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
$options = array_replace_recursive(self::$defaults, $options);
|
||||
$method = 'get' . ucfirst(strtolower(self::$method));
|
||||
$config = Grav::instance()['config'];
|
||||
$overrides = [];
|
||||
|
||||
// SSL Verify Peer and Proxy Setting
|
||||
$settings = [
|
||||
'method' => $config->get('system.gpm.method', self::$method),
|
||||
'verify_peer' => $config->get('system.gpm.verify_peer', true),
|
||||
// `system.proxy_url` is for fallback
|
||||
// introduced with 1.1.0-beta.1 probably safe to remove at some point
|
||||
'proxy_url' => $config->get('system.gpm.proxy_url', $config->get('system.proxy_url', false)),
|
||||
];
|
||||
|
||||
if (!$settings['verify_peer']) {
|
||||
$overrides = array_replace_recursive([], $overrides, [
|
||||
'curl' => [
|
||||
CURLOPT_SSL_VERIFYPEER => $settings['verify_peer']
|
||||
],
|
||||
'fopen' => [
|
||||
'ssl' => [
|
||||
'verify_peer' => $settings['verify_peer'],
|
||||
'verify_peer_name' => $settings['verify_peer'],
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// Proxy Setting
|
||||
if ($settings['proxy_url']) {
|
||||
$proxy = parse_url($settings['proxy_url']);
|
||||
$fopen_proxy = ($proxy['scheme'] ?: 'http') . '://' . $proxy['host'] . (isset($proxy['port']) ? ':' . $proxy['port'] : '');
|
||||
|
||||
$overrides = array_replace_recursive([], $overrides, [
|
||||
'curl' => [
|
||||
CURLOPT_PROXY => $proxy['host'],
|
||||
CURLOPT_PROXYTYPE => 'HTTP'
|
||||
],
|
||||
'fopen' => [
|
||||
'proxy' => $fopen_proxy,
|
||||
'request_fulluri' => true
|
||||
]
|
||||
]);
|
||||
|
||||
if (isset($proxy['port'])) {
|
||||
$overrides['curl'][CURLOPT_PROXYPORT] = $proxy['port'];
|
||||
}
|
||||
|
||||
if (isset($proxy['user']) && isset($proxy['pass'])) {
|
||||
$fopen_auth = $auth = base64_encode($proxy['user'] . ':' . $proxy['pass']);
|
||||
$overrides['curl'][CURLOPT_PROXYUSERPWD] = $proxy['user'] . ':' . $proxy['pass'];
|
||||
$overrides['fopen']['header'] = "Proxy-Authorization: Basic $fopen_auth";
|
||||
}
|
||||
}
|
||||
|
||||
$options = array_replace_recursive(self::$defaults, $options, $overrides);
|
||||
$method = 'get' . ucfirst(strtolower($settings['method']));
|
||||
|
||||
self::$callback = $callback;
|
||||
return static::$method($uri, $options, $callback);
|
||||
@@ -199,30 +260,39 @@ class Response
|
||||
$options = $args[1];
|
||||
$callback = $args[2];
|
||||
|
||||
// if proxy set add that
|
||||
$config = Grav::instance()['config'];
|
||||
$proxy_url = $config->get('system.gpm.proxy_url', $config->get('system.proxy_url'));
|
||||
if ($proxy_url) {
|
||||
$parsed_url = parse_url($proxy_url);
|
||||
|
||||
$options['fopen']['proxy'] = ($parsed_url['scheme'] ?: 'http') . '://' . $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
|
||||
$options['fopen']['request_fulluri'] = true;
|
||||
|
||||
if (isset($parsed_url['user']) && isset($parsed_url['pass'])) {
|
||||
$auth = base64_encode($parsed_url['user'] . ':' . $parsed_url['pass']);
|
||||
$options['fopen']['header'] = "Proxy-Authorization: Basic $auth";
|
||||
}
|
||||
}
|
||||
|
||||
if ($callback) {
|
||||
$options['fopen']['notification'] = ['self', 'progress'];
|
||||
}
|
||||
|
||||
$stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
|
||||
if (isset($options['fopen']['ssl'])) {
|
||||
$ssl = $options['fopen']['ssl'];
|
||||
unset($options['fopen']['ssl']);
|
||||
|
||||
$stream = stream_context_create([
|
||||
'http' => $options['fopen'],
|
||||
'ssl' => $ssl
|
||||
], $options['fopen']);
|
||||
} else {
|
||||
$stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
|
||||
}
|
||||
|
||||
|
||||
$content = @file_get_contents($uri, false, $stream);
|
||||
|
||||
if ($content === false) {
|
||||
throw new \RuntimeException("Error while trying to download '$uri'");
|
||||
$code = null;
|
||||
if (isset($http_response_header)) {
|
||||
$code = explode(' ', $http_response_header[0])[1];
|
||||
}
|
||||
|
||||
switch ($code) {
|
||||
case '404':
|
||||
throw new \RuntimeException("Page not found");
|
||||
case '401':
|
||||
throw new \RuntimeException("Invalid LICENSE");
|
||||
default:
|
||||
throw new \RuntimeException("Error while trying to download '$uri'\n");
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
@@ -248,8 +318,17 @@ class Response
|
||||
$errno = curl_errno($ch);
|
||||
|
||||
if ($errno) {
|
||||
$error_message = curl_strerror($errno);
|
||||
throw new \RuntimeException("cURL error ({$errno}):\n {$error_message}");
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error_message = curl_strerror($errno) . "\n" . curl_error($ch);
|
||||
|
||||
switch ($code) {
|
||||
case '404':
|
||||
throw new \RuntimeException("Page not found");
|
||||
case '401':
|
||||
throw new \RuntimeException("Invalid LICENSE");
|
||||
default:
|
||||
throw new \RuntimeException("Error while trying to download '$uri'\nMessage: $error_message");
|
||||
}
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
@@ -276,24 +355,6 @@ class Response
|
||||
);
|
||||
}
|
||||
|
||||
// if proxy set add that
|
||||
$config = Grav::instance()['config'];
|
||||
$proxy_url = $config->get('system.gpm.proxy_url', $config->get('system.proxy_url'));
|
||||
if ($proxy_url) {
|
||||
$parsed_url = parse_url($proxy_url);
|
||||
|
||||
$options['curl'][CURLOPT_PROXY] = $parsed_url['host'];
|
||||
$options['curl'][CURLOPT_PROXYTYPE] = 'HTTP';
|
||||
|
||||
if (isset($parsed_url['port'])) {
|
||||
$options['curl'][CURLOPT_PROXYPORT] = $parsed_url['port'];
|
||||
}
|
||||
|
||||
if (isset($parsed_url['user']) && isset($parsed_url['pass'])) {
|
||||
$options['curl'][CURLOPT_PROXYUSERPWD] = $parsed_url['user'] . ':' . $parsed_url['pass'];
|
||||
}
|
||||
}
|
||||
|
||||
// no open_basedir set, we can proceed normally
|
||||
if (!ini_get('open_basedir')) {
|
||||
curl_setopt_array($ch, $options['curl']);
|
||||
|
||||
@@ -29,6 +29,7 @@ class Grav extends Container
|
||||
'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
|
||||
'cache' => 'Grav\Common\Cache',
|
||||
'session' => 'Grav\Common\Session',
|
||||
'Grav\Common\Service\MessagesServiceProvider',
|
||||
'plugins' => 'Grav\Common\Plugins',
|
||||
'themes' => 'Grav\Common\Themes',
|
||||
'twig' => 'Grav\Common\Twig\Twig',
|
||||
@@ -208,42 +209,17 @@ class Grav extends Container
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns mime type for the file format.
|
||||
*
|
||||
* @param string $format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function mime($format)
|
||||
{
|
||||
switch ($format) {
|
||||
case 'json':
|
||||
return 'application/json';
|
||||
case 'html':
|
||||
return 'text/html';
|
||||
case 'atom':
|
||||
return 'application/atom+xml';
|
||||
case 'rss':
|
||||
return 'application/rss+xml';
|
||||
case 'xml':
|
||||
return 'application/xml';
|
||||
}
|
||||
|
||||
return 'text/html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set response header.
|
||||
*/
|
||||
public function header()
|
||||
{
|
||||
$extension = $this['uri']->extension();
|
||||
|
||||
/** @var Page $page */
|
||||
$page = $this['page'];
|
||||
|
||||
header('Content-type: ' . $this->mime($extension));
|
||||
$format = $page->templateFormat();
|
||||
|
||||
header('Content-type: ' . Utils::getMimeByExtension($format, 'text/html'));
|
||||
|
||||
// Calculate Expires Headers if set to > 0
|
||||
$expires = $page->expires();
|
||||
@@ -266,7 +242,7 @@ class Grav extends Container
|
||||
}
|
||||
|
||||
// Set debugger data in headers
|
||||
if (!($extension === null || $extension == 'html')) {
|
||||
if (!($format === null || $format == 'html')) {
|
||||
$this['debugger']->enabled(false);
|
||||
}
|
||||
|
||||
@@ -330,7 +306,12 @@ class Grav extends Container
|
||||
} else {
|
||||
// Without gzip we have no other choice than to prevent server from compressing the output.
|
||||
// This action turns off mod_deflate which would prevent us from closing the connection.
|
||||
header('Content-Encoding: none');
|
||||
if ($this['config']->get('system.cache.allow_webserver_gzip')) {
|
||||
header('Content-Encoding: identity');
|
||||
} else {
|
||||
header('Content-Encoding: none');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
103
system/src/Grav/Common/Helpers/Base32.php
Normal file
103
system/src/Grav/Common/Helpers/Base32.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Helpers
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Helpers;
|
||||
|
||||
class Base32 {
|
||||
protected static $base32Chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
protected static $base32Lookup = array(
|
||||
0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7'
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // '8', '9', ':', ';', '<', '=', '>', '?'
|
||||
0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
|
||||
0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'
|
||||
0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'
|
||||
0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_'
|
||||
0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g'
|
||||
0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
|
||||
0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
|
||||
0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL'
|
||||
);
|
||||
|
||||
/**
|
||||
* Encode in Base32
|
||||
*
|
||||
* @param $bytes
|
||||
* @return string
|
||||
*/
|
||||
public static function encode( $bytes ) {
|
||||
$i = 0; $index = 0; $digit = 0;
|
||||
$base32 = "";
|
||||
$bytes_len = strlen($bytes);
|
||||
while( $i < $bytes_len ) {
|
||||
$currByte = ord($bytes{$i});
|
||||
/* Is the current digit going to span a byte boundary? */
|
||||
if( $index > 3 ) {
|
||||
if( ($i + 1) < $bytes_len ) {
|
||||
$nextByte = ord($bytes{$i+1});
|
||||
} else {
|
||||
$nextByte = 0;
|
||||
}
|
||||
$digit = $currByte & (0xFF >> $index);
|
||||
$index = ($index + 5) % 8;
|
||||
$digit <<= $index;
|
||||
$digit |= $nextByte >> (8 - $index);
|
||||
$i++;
|
||||
} else {
|
||||
$digit = ($currByte >> (8 - ($index + 5))) & 0x1F;
|
||||
$index = ($index + 5) % 8;
|
||||
if( $index == 0 ) $i++;
|
||||
}
|
||||
$base32 .= self::$base32Chars{$digit};
|
||||
}
|
||||
return $base32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode in Base32
|
||||
*
|
||||
* @param $base32
|
||||
* @return string
|
||||
*/
|
||||
public static function decode( $base32 ) {
|
||||
$bytes = array();
|
||||
$base32_len = strlen($base32);
|
||||
for( $i=$base32_len*5/8-1; $i>=0; --$i ) {
|
||||
$bytes[] = 0;
|
||||
}
|
||||
for( $i = 0, $index = 0, $offset = 0; $i < $base32_len; $i++ ) {
|
||||
$lookup = ord($base32{$i}) - ord('0');
|
||||
/* Skip chars outside the lookup table */
|
||||
if( $lookup < 0 || $lookup >= count(self::$base32Lookup) ) {
|
||||
continue;
|
||||
}
|
||||
$digit = self::$base32Lookup[$lookup];
|
||||
/* If this digit is not in the table, ignore it */
|
||||
if( $digit == 0xFF ) continue;
|
||||
if( $index <= 3 ) {
|
||||
$index = ($index + 5) % 8;
|
||||
if( $index == 0) {
|
||||
$bytes[$offset] |= $digit;
|
||||
$offset++;
|
||||
if( $offset >= count($bytes) ) break;
|
||||
} else {
|
||||
$bytes[$offset] |= $digit << (8 - $index);
|
||||
}
|
||||
} else {
|
||||
$index = ($index + 5) % 8;
|
||||
$bytes[$offset] |= ($digit >> $index);
|
||||
$offset++;
|
||||
if ($offset >= count($bytes) ) break;
|
||||
$bytes[$offset] |= $digit << (8 - $index);
|
||||
}
|
||||
}
|
||||
$bites = "";
|
||||
foreach( $bytes as $byte ) $bites .= chr($byte);
|
||||
return $bites;
|
||||
}
|
||||
}
|
||||
350
system/src/Grav/Common/Helpers/Excerpts.php
Normal file
350
system/src/Grav/Common/Helpers/Excerpts.php
Normal file
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Helpers
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Helpers;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Excerpts
|
||||
{
|
||||
/**
|
||||
* Process Grav image media URL from HTML tag
|
||||
*
|
||||
* @param $html HTML tag e.g. `<img src="image.jpg" />`
|
||||
* @param $page The current page object
|
||||
* @return string Returns final HTML string
|
||||
*/
|
||||
public static function processImageHtml($html, $page)
|
||||
{
|
||||
$excerpt = static::getExcerptFromHtml($html, 'img');
|
||||
|
||||
$original_src = $excerpt['element']['attributes']['src'];
|
||||
$excerpt['element']['attributes']['href'] = $original_src;
|
||||
|
||||
$excerpt = static::processLinkExcerpt($excerpt, $page, 'image');
|
||||
|
||||
$excerpt['element']['attributes']['src'] = $excerpt['element']['attributes']['href'];
|
||||
unset ($excerpt['element']['attributes']['href']);
|
||||
|
||||
$excerpt = static::processImageExcerpt($excerpt, $page);
|
||||
|
||||
$excerpt['element']['attributes']['data-src'] = $original_src;
|
||||
|
||||
$html = static::getHtmlFromExcerpt($excerpt);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an Excerpt array from a chunk of HTML
|
||||
*
|
||||
* @param $html Chunk of HTML
|
||||
* @param $tag a tag, for example `img`
|
||||
* @return array|null returns nested array excerpt
|
||||
*/
|
||||
public static function getExcerptFromHtml($html, $tag)
|
||||
{
|
||||
$doc = new \DOMDocument();
|
||||
$doc->loadHtml($html);
|
||||
$images = $doc->getElementsByTagName($tag);
|
||||
$excerpt = null;
|
||||
|
||||
foreach ($images as $image) {
|
||||
$attributes = [];
|
||||
foreach ($image->attributes as $name => $value) {
|
||||
$attributes[$name] = $value->value;
|
||||
}
|
||||
$excerpt = [
|
||||
'element' => [
|
||||
'name' => $image->tagName,
|
||||
'attributes' => $attributes
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild HTML tag from an excerpt array
|
||||
*
|
||||
* @param $excerpt
|
||||
* @return string
|
||||
*/
|
||||
public static function getHtmlFromExcerpt($excerpt)
|
||||
{
|
||||
$element = $excerpt['element'];
|
||||
$html = '<'.$element['name'];
|
||||
|
||||
if (isset($element['attributes'])) {
|
||||
foreach ($element['attributes'] as $name => $value) {
|
||||
if ($value === null) {
|
||||
continue;
|
||||
}
|
||||
$html .= ' '.$name.'="'.$value.'"';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($element['text'])) {
|
||||
$html .= '>';
|
||||
$html .= $element['text'];
|
||||
$html .= '</'.$element['name'].'>';
|
||||
} else {
|
||||
$html .= ' />';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a Link excerpt
|
||||
*
|
||||
* @param $excerpt
|
||||
* @param $page
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
public static function processLinkExcerpt($excerpt, $page, $type = 'link')
|
||||
{
|
||||
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['href']));
|
||||
|
||||
$url_parts = static::parseUrl($url);
|
||||
|
||||
// If there is a query, then parse it and build action calls.
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
|
||||
$carry[$parts[0]] = $value;
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
// Valid attributes supported.
|
||||
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
|
||||
|
||||
// Unless told to not process, go through actions.
|
||||
if (array_key_exists('noprocess', $actions)) {
|
||||
unset($actions['noprocess']);
|
||||
} else {
|
||||
// Loop through actions for the image and call them.
|
||||
foreach ($actions as $attrib => $value) {
|
||||
$key = $attrib;
|
||||
|
||||
if (in_array($attrib, $valid_attributes)) {
|
||||
// support both class and classes.
|
||||
if ($attrib == 'classes') {
|
||||
$attrib = 'class';
|
||||
}
|
||||
$excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
|
||||
unset($actions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
// If no query elements left, unset query.
|
||||
if (empty($url_parts['query'])) {
|
||||
unset ($url_parts['query']);
|
||||
}
|
||||
|
||||
// Set path to / if not set.
|
||||
if (empty($url_parts['path'])) {
|
||||
$url_parts['path'] = '';
|
||||
}
|
||||
|
||||
// If scheme isn't http(s)..
|
||||
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
|
||||
// Handle custom streams.
|
||||
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
|
||||
$url_parts['path'] = Grav::instance()['base_url_relative'] . '/' . static::resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
|
||||
unset($url_parts['stream'], $url_parts['scheme']);
|
||||
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
// Handle paths and such.
|
||||
$url_parts = Uri::convertUrl($page, $url_parts, $type);
|
||||
|
||||
// Build the URL from the component parts and set it on the element.
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an image excerpt
|
||||
*
|
||||
* @param $excerpt
|
||||
* @param $page
|
||||
* @return mixed
|
||||
*/
|
||||
public static function processImageExcerpt($excerpt, $page)
|
||||
{
|
||||
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
|
||||
$url_parts = static::parseUrl($url);
|
||||
|
||||
$media = null;
|
||||
$filename = null;
|
||||
|
||||
if (!empty($url_parts['stream'])) {
|
||||
$filename = $url_parts['scheme'] . '://' . (isset($url_parts['path']) ? $url_parts['path'] : '');
|
||||
|
||||
$media = $page->media();
|
||||
|
||||
} else {
|
||||
// File is also local if scheme is http(s) and host matches.
|
||||
$local_file = isset($url_parts['path'])
|
||||
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https']))
|
||||
&& (empty($url_parts['host']) || $url_parts['host'] == Grav::instance()['uri']->host());
|
||||
|
||||
if ($local_file) {
|
||||
$filename = basename($url_parts['path']);
|
||||
$folder = dirname($url_parts['path']);
|
||||
|
||||
// Get the local path to page media if possible.
|
||||
if ($folder === $page->url(false, false, false)) {
|
||||
// Get the media objects for this page.
|
||||
$media = $page->media();
|
||||
} else {
|
||||
// see if this is an external page to this one
|
||||
$base_url = rtrim(Grav::instance()['base_url_relative'] . Grav::instance()['pages']->base(), '/');
|
||||
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
|
||||
|
||||
$ext_page = Grav::instance()['pages']->dispatch($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->media();
|
||||
} else {
|
||||
Grav::instance()->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a media file that matches the path referenced..
|
||||
if ($media && $filename && isset($media[$filename])) {
|
||||
// Get the medium object.
|
||||
/** @var Medium $medium */
|
||||
$medium = $media[$filename];
|
||||
|
||||
// Process operations
|
||||
$medium = static::processMediaActions($medium, $url_parts);
|
||||
|
||||
$alt = isset($excerpt['element']['attributes']['alt']) ? $excerpt['element']['attributes']['alt'] : '';
|
||||
$title = isset($excerpt['element']['attributes']['title']) ? $excerpt['element']['attributes']['title'] : '';
|
||||
$class = isset($excerpt['element']['attributes']['class']) ? $excerpt['element']['attributes']['class'] : '';
|
||||
$id = isset($excerpt['element']['attributes']['id']) ? $excerpt['element']['attributes']['id'] : '';
|
||||
|
||||
$excerpt['element'] = $medium->parseDownElement($title, $alt, $class, $id, true);
|
||||
|
||||
} else {
|
||||
// Not a current page media file, see if it needs converting to relative.
|
||||
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process media actions
|
||||
*
|
||||
* @param $medium
|
||||
* @param $url
|
||||
* @return mixed
|
||||
*/
|
||||
public static function processMediaActions($medium, $url)
|
||||
{
|
||||
if (!is_array($url)) {
|
||||
$url_parts = parse_url($url);
|
||||
} else {
|
||||
$url_parts = $url;
|
||||
}
|
||||
|
||||
$actions = [];
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = isset($parts[1]) ? $parts[1] : null;
|
||||
$carry[] = ['method' => $parts[0], 'params' => $value];
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
}
|
||||
|
||||
if (Grav::instance()['config']->get('system.images.auto_fix_orientation')) {
|
||||
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action) {
|
||||
$matches = [];
|
||||
|
||||
if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
|
||||
$args = [explode(',', $matches[1])];
|
||||
} else {
|
||||
$args = explode(',', $action['params']);
|
||||
}
|
||||
|
||||
$medium = call_user_func_array([$medium, $action['method']], $args);
|
||||
}
|
||||
|
||||
if (isset($url_parts['fragment'])) {
|
||||
$medium->urlHash($url_parts['fragment']);
|
||||
}
|
||||
|
||||
return $medium;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variation of parse_url() which works also with local streams.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array|bool
|
||||
*/
|
||||
protected static function parseUrl($url)
|
||||
{
|
||||
$url_parts = parse_url($url);
|
||||
|
||||
if (isset($url_parts['scheme'])) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
// Special handling for the streams.
|
||||
if ($locator->schemeExists($url_parts['scheme'])) {
|
||||
if (isset($url_parts['host'])) {
|
||||
// Merge host and path into a path.
|
||||
$url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
|
||||
unset($url_parts['host']);
|
||||
}
|
||||
|
||||
$url_parts['stream'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $url_parts;
|
||||
}
|
||||
|
||||
protected static function resolveStream($url)
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
return $locator->isStream($url) ? ($locator->findResource($url, false) ?: $locator->findResource($url, false, true)) : $url;
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,16 @@
|
||||
|
||||
namespace Grav\Common\Helpers;
|
||||
|
||||
use DOMText;
|
||||
use DOMDocument;
|
||||
use DOMWordsIterator;
|
||||
use DOMLettersIterator;
|
||||
|
||||
/**
|
||||
* This file is part of urodoz/truncateHTML.
|
||||
* This file is part of https://github.com/Bluetel-Solutions/twig-truncate-extension
|
||||
*
|
||||
* (c) Albert Lacarta <urodoz@gmail.com>
|
||||
* Copyright (c) 2015 Bluetel Solutions developers@bluetel.co.uk
|
||||
* Copyright (c) 2015 Alex Wilson ajw@bluetel.co.uk
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
@@ -21,181 +25,192 @@ use DOMDocument;
|
||||
|
||||
class Truncator {
|
||||
|
||||
public static $default_options = array(
|
||||
'ellipsis' => '…',
|
||||
'break' => ' ',
|
||||
'length_in_chars' => false,
|
||||
'word_safe' => false,
|
||||
);
|
||||
/**
|
||||
* Safely truncates HTML by a given number of words.
|
||||
* @param string $html Input HTML.
|
||||
* @param integer $limit Limit to how many words we preserve.
|
||||
* @param string $ellipsis String to use as ellipsis (if any).
|
||||
* @return string Safe truncated HTML.
|
||||
*/
|
||||
public static function truncateWords($html, $limit = 0, $ellipsis = "")
|
||||
{
|
||||
if ($limit <= 0) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
// These tags are allowed to have an ellipsis inside
|
||||
public static $ellipsable_tags = array(
|
||||
'p', 'ol', 'ul', 'li',
|
||||
'div', 'header', 'article', 'nav',
|
||||
'section', 'footer', 'aside',
|
||||
'dd', 'dt', 'dl',
|
||||
);
|
||||
$dom = self::htmlToDomDocument($html);
|
||||
|
||||
public static $self_closing_tags = array(
|
||||
'br', 'hr', 'img',
|
||||
);
|
||||
// Grab the body of our DOM.
|
||||
$body = $dom->getElementsByTagName("body")->item(0);
|
||||
|
||||
// Iterate over words.
|
||||
$words = new DOMWordsIterator($body);
|
||||
foreach ($words as $word) {
|
||||
|
||||
// If we have exceeded the limit, we delete the remainder of the content.
|
||||
if ($words->key() >= $limit) {
|
||||
|
||||
// Grab current position.
|
||||
$currentWordPosition = $words->currentWordPosition();
|
||||
$curNode = $currentWordPosition[0];
|
||||
$offset = $currentWordPosition[1];
|
||||
$words = $currentWordPosition[2];
|
||||
|
||||
$curNode->nodeValue = substr(
|
||||
$curNode->nodeValue,
|
||||
0,
|
||||
$words[$offset][1] + strlen($words[$offset][0])
|
||||
);
|
||||
|
||||
self::removeProceedingNodes($curNode, $body);
|
||||
|
||||
if (!empty($ellipsis)) {
|
||||
self::insertEllipsis($curNode, $ellipsis);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return self::innerHTML($body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate given HTML string to specified length.
|
||||
* If length_in_chars is false it's trimmed by number
|
||||
* of words, otherwise by number of characters.
|
||||
*
|
||||
* @param string $html
|
||||
* @param integer $length
|
||||
* @param string|array $opts
|
||||
* @return string
|
||||
* Safely truncates HTML by a given number of letters.
|
||||
* @param string $html Input HTML.
|
||||
* @param integer $limit Limit to how many letters we preserve.
|
||||
* @param string $ellipsis String to use as ellipsis (if any).
|
||||
* @return string Safe truncated HTML.
|
||||
*/
|
||||
public static function truncate($html, $length, $opts=array())
|
||||
public static function truncateLetters($html, $limit = 0, $ellipsis = "")
|
||||
{
|
||||
if (is_string($opts)) $opts = array('ellipsis' => $opts);
|
||||
$opts = array_merge(static::$default_options, $opts);
|
||||
// wrap the html in case it consists of adjacent nodes like <p>foo</p><p>bar</p>
|
||||
$html = mb_convert_encoding("<div>".$html."</div>", 'HTML-ENTITIES', 'UTF-8');
|
||||
|
||||
$root_node = null;
|
||||
// Parse using HTML5Lib if it's available.
|
||||
if (class_exists('HTML5Lib\\Parser')) {
|
||||
try {
|
||||
$doc = \HTML5Lib\Parser::parse($html);
|
||||
$root_node = $doc->documentElement->lastChild->lastChild;
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
;
|
||||
}
|
||||
if ($limit <= 0) {
|
||||
return $html;
|
||||
}
|
||||
if ($root_node === null) {
|
||||
// HTML5Lib not available so we'll have to use DOMDocument
|
||||
// We'll only be able to parse HTML5 if it's valid XML
|
||||
$doc = new DOMDocument('4.01', 'utf-8');
|
||||
$doc->formatOutput = false;
|
||||
$doc->preserveWhiteSpace = true;
|
||||
// loadHTML will fail with HTML5 tags (article, nav, etc)
|
||||
// so we need to suppress errors and if it fails to parse we
|
||||
// retry with the XML parser instead
|
||||
$prev_use_errors = libxml_use_internal_errors(true);
|
||||
if ($doc->loadHTML($html)) {
|
||||
$root_node = $doc->documentElement->lastChild->lastChild;
|
||||
}
|
||||
else if ($doc->loadXML($html)) {
|
||||
$root_node = $doc->documentElement;
|
||||
}
|
||||
else {
|
||||
libxml_use_internal_errors($prev_use_errors);
|
||||
throw new \RuntimeException;
|
||||
}
|
||||
libxml_use_internal_errors($prev_use_errors);
|
||||
}
|
||||
list($text, $_, $opts) = static::truncateNode($doc, $root_node, $length, $opts);
|
||||
|
||||
$text = mb_substr(mb_substr($text, 0, -6), 5);
|
||||
$dom = self::htmlToDomDocument($html);
|
||||
|
||||
return $text;
|
||||
}
|
||||
// Grab the body of our DOM.
|
||||
$body = $dom->getElementsByTagName("body")->item(0);
|
||||
|
||||
protected static function truncateNode($doc, $node, $length, $opts)
|
||||
{
|
||||
if ($length === 0 && !static::ellipsable($node)) {
|
||||
return array('', 1, $opts);
|
||||
}
|
||||
list($inner, $remaining, $opts) = static::innerTruncate($doc, $node, $length, $opts);
|
||||
if (0 === mb_strlen($inner)) {
|
||||
return array(in_array(mb_strtolower($node->nodeName), static::$self_closing_tags) ? $doc->saveXML($node) : "", $length - $remaining, $opts);
|
||||
}
|
||||
while($node->firstChild) {
|
||||
$node->removeChild($node->firstChild);
|
||||
}
|
||||
$newNode = $doc->createDocumentFragment();
|
||||
// handle the ampersand
|
||||
$newNode->appendXml(static::xmlEscape($inner));
|
||||
$node->appendChild($newNode);
|
||||
return array($doc->saveXML($node), $length - $remaining, $opts);
|
||||
}
|
||||
// Iterate over letters.
|
||||
$letters = new DOMLettersIterator($body);
|
||||
foreach ($letters as $letter) {
|
||||
|
||||
protected static function innerTruncate($doc, $node, $length, $opts)
|
||||
{
|
||||
$inner = '';
|
||||
$remaining = $length;
|
||||
foreach($node->childNodes as $childNode) {
|
||||
if ($childNode->nodeType === XML_ELEMENT_NODE) {
|
||||
list($txt, $nb, $opts) = static::truncateNode($doc, $childNode, $remaining, $opts);
|
||||
}
|
||||
else if ($childNode->nodeType === XML_TEXT_NODE) {
|
||||
list($txt, $nb, $opts) = static::truncateText($childNode, $remaining, $opts);
|
||||
} else {
|
||||
$txt = '';
|
||||
$nb = 0;
|
||||
}
|
||||
// If we have exceeded the limit, we want to delete the remainder of this document.
|
||||
if ($letters->key() >= $limit) {
|
||||
|
||||
// unhandle the ampersand
|
||||
$txt = static::xmlUnescape($txt);
|
||||
$currentText = $letters->currentTextPosition();
|
||||
$currentText[0]->nodeValue = substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
|
||||
self::removeProceedingNodes($currentText[0], $body);
|
||||
|
||||
$remaining -= $nb;
|
||||
$inner .= $txt;
|
||||
if ($remaining < 0) {
|
||||
if (static::ellipsable($node)) {
|
||||
$inner = preg_replace('/(?:[\s\pP]+|(?:&(?:[a-z]+|#[0-9]+);?))*$/u', '', $inner).$opts['ellipsis'];
|
||||
$opts['ellipsis'] = '';
|
||||
$opts['was_truncated'] = true;
|
||||
if (!empty($ellipsis)) {
|
||||
self::insertEllipsis($currentText[0], $ellipsis);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return array($inner, $remaining, $opts);
|
||||
|
||||
return self::innerHTML($body);
|
||||
}
|
||||
|
||||
protected static function truncateText($node, $length, $opts)
|
||||
/**
|
||||
* Builds a DOMDocument object from a string containing HTML.
|
||||
* @param string HTML to load
|
||||
* @returns DOMDocument Returns a DOMDocument object.
|
||||
*/
|
||||
public static function htmlToDomDocument($html)
|
||||
{
|
||||
$string = $node->textContent;
|
||||
if (!$html) {
|
||||
$html = '<p></p>';
|
||||
}
|
||||
|
||||
if ($opts['length_in_chars']) {
|
||||
$count = mb_strlen($string);
|
||||
if ($count <= $length && $length > 0) {
|
||||
return array($string, $count, $opts);
|
||||
}
|
||||
if ($opts['word_safe']) {
|
||||
if (false !== ($breakpoint = mb_strpos($string, $opts['break'], $length))) {
|
||||
if ($breakpoint < mb_strlen($string) - 1) {
|
||||
$string = mb_substr($string, 0, $breakpoint) . $opts['break'];
|
||||
}
|
||||
// Transform multibyte entities which otherwise display incorrectly.
|
||||
$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
|
||||
|
||||
// Internal errors enabled as HTML5 not fully supported.
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
// Instantiate new DOMDocument object, and then load in UTF-8 HTML.
|
||||
$dom = new DOMDocument();
|
||||
$dom->encoding = 'UTF-8';
|
||||
$dom->loadHTML($html);
|
||||
|
||||
return $dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all nodes after the current node.
|
||||
* @param DOMNode|DOMElement $domNode
|
||||
* @param DOMNode|DOMElement $topNode
|
||||
* @return void
|
||||
*/
|
||||
private static function removeProceedingNodes($domNode, $topNode)
|
||||
{
|
||||
$nextNode = $domNode->nextSibling;
|
||||
|
||||
if ($nextNode !== null) {
|
||||
self::removeProceedingNodes($nextNode, $topNode);
|
||||
$domNode->parentNode->removeChild($nextNode);
|
||||
} else {
|
||||
//scan upwards till we find a sibling
|
||||
$curNode = $domNode->parentNode;
|
||||
while ($curNode !== $topNode) {
|
||||
if ($curNode->nextSibling !== null) {
|
||||
$curNode = $curNode->nextSibling;
|
||||
self::removeProceedingNodes($curNode, $topNode);
|
||||
$curNode->parentNode->removeChild($curNode);
|
||||
break;
|
||||
}
|
||||
return array($string, $count, $opts);
|
||||
$curNode = $curNode->parentNode;
|
||||
}
|
||||
return array(mb_substr($node->textContent, 0, $length), $count, $opts);
|
||||
}
|
||||
else {
|
||||
preg_match_all('/\s*\S+/', $string, $words);
|
||||
$words = $words[0];
|
||||
$count = count($words);
|
||||
if ($count <= $length && $length > 0) {
|
||||
return array($string, $count, $opts);
|
||||
}
|
||||
return array(implode('', array_slice($words, 0, $length)), $count, $opts);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function ellipsable($node)
|
||||
/**
|
||||
* Inserts an ellipsis
|
||||
* @param DOMNode|DOMElement $domNode Element to insert after.
|
||||
* @param string $ellipsis Text used to suffix our document.
|
||||
* @return void
|
||||
*/
|
||||
private static function insertEllipsis($domNode, $ellipsis)
|
||||
{
|
||||
return ($node instanceof DOMDocument)
|
||||
|| in_array(mb_strtolower($node->nodeName), static::$ellipsable_tags)
|
||||
;
|
||||
$avoid = array('a', 'strong', 'em', 'h1', 'h2', 'h3', 'h4', 'h5'); //html tags to avoid appending the ellipsis to
|
||||
|
||||
if (in_array($domNode->parentNode->nodeName, $avoid) && $domNode->parentNode->parentNode !== null) {
|
||||
// Append as text node to parent instead
|
||||
$textNode = new DOMText($ellipsis);
|
||||
|
||||
if ($domNode->parentNode->parentNode->nextSibling) {
|
||||
$domNode->parentNode->parentNode->insertBefore($textNode, $domNode->parentNode->parentNode->nextSibling);
|
||||
} else {
|
||||
$domNode->parentNode->parentNode->appendChild($textNode);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Append to current node
|
||||
$domNode->nodeValue = rtrim($domNode->nodeValue) . $ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
protected static function xmlEscape($string)
|
||||
{
|
||||
$string = str_replace('&', '&', $string);
|
||||
$string = str_replace('<?', '<?', $string);
|
||||
return $string;
|
||||
/**
|
||||
* Returns the innerHTML of a particular DOMElement
|
||||
*
|
||||
* @param $element
|
||||
* @return string
|
||||
*/
|
||||
private static function innerHTML($element) {
|
||||
$innerHTML = "";
|
||||
$children = $element->childNodes;
|
||||
foreach ($children as $child)
|
||||
{
|
||||
$tmp_dom = new DOMDocument();
|
||||
$tmp_dom->appendChild($tmp_dom->importNode($child, true));
|
||||
$innerHTML.=trim($tmp_dom->saveHTML());
|
||||
}
|
||||
return $innerHTML;
|
||||
}
|
||||
|
||||
protected static function xmlUnescape($string)
|
||||
{
|
||||
$string = str_replace('&', '&', $string);
|
||||
$string = str_replace('<?', '<?', $string);
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ class Inflector
|
||||
{
|
||||
if (empty($this->plural)) {
|
||||
$language = Grav::instance()['language'];
|
||||
$this->plural = $language->translate('INFLECTOR_PLURALS', null, true);
|
||||
$this->singular = $language->translate('INFLECTOR_SINGULAR', null, true);
|
||||
$this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true);
|
||||
$this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true);
|
||||
$this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true);
|
||||
$this->plural = $language->translate('INFLECTOR_PLURALS', null, true) ?: [];
|
||||
$this->singular = $language->translate('INFLECTOR_SINGULAR', null, true) ?: [];
|
||||
$this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true) ?: [];
|
||||
$this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true) ?: [];
|
||||
$this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true) ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -189,6 +189,10 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
*/
|
||||
public function random($num = 1)
|
||||
{
|
||||
if ($num > count($this->items)) {
|
||||
$num = count($this->items);
|
||||
}
|
||||
|
||||
$this->items = array_intersect_key($this->items, array_flip((array)array_rand($this->items, $num)));
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -14,7 +14,7 @@ class LanguageCodes
|
||||
'af' => [ 'name' => 'Afrikaans', 'nativeName' => 'Afrikaans' ],
|
||||
'ak' => [ 'name' => 'Akan', 'nativeName' => 'Akan' ], // unverified native name
|
||||
'ast' => [ 'name' => 'Asturian', 'nativeName' => 'Asturianu' ],
|
||||
'ar' => [ 'name' => 'Arabic', 'nativeName' => 'عربي' ],
|
||||
'ar' => [ 'name' => 'Arabic', 'nativeName' => 'عربي', 'orientation' => 'rtl'],
|
||||
'as' => [ 'name' => 'Assamese', 'nativeName' => 'অসমীয়া' ],
|
||||
'be' => [ 'name' => 'Belarusian', 'nativeName' => 'Беларуская' ],
|
||||
'bg' => [ 'name' => 'Bulgarian', 'nativeName' => 'Български' ],
|
||||
@@ -48,10 +48,12 @@ class LanguageCodes
|
||||
'es-MX' => [ 'name' => 'Spanish (Mexico)', 'nativeName' => 'Español (de México)' ],
|
||||
'et' => [ 'name' => 'Estonian', 'nativeName' => 'Eesti keel' ],
|
||||
'eu' => [ 'name' => 'Basque', 'nativeName' => 'Euskara' ],
|
||||
'fa' => [ 'name' => 'Persian', 'nativeName' => 'فارسی' ],
|
||||
'fa' => [ 'name' => 'Persian', 'nativeName' => 'فارسی' , 'orientation' => 'rtl' ],
|
||||
'fi' => [ 'name' => 'Finnish', 'nativeName' => 'Suomi' ],
|
||||
'fj-FJ' => [ 'name' => 'Fijian', 'nativeName' => 'Vosa vaka-Viti' ],
|
||||
'fr' => [ 'name' => 'French', 'nativeName' => 'Français' ],
|
||||
'fr-CA' => [ 'name' => 'French (Canada)', 'nativeName' => 'Français (Canada)' ],
|
||||
'fr-FR' => [ 'name' => 'French (France)', 'nativeName' => 'Français (France)' ],
|
||||
'fur' => [ 'name' => 'Friulian', 'nativeName' => 'Furlan' ],
|
||||
'fur-IT' => [ 'name' => 'Friulian', 'nativeName' => 'Furlan' ],
|
||||
'fy' => [ 'name' => 'Frisian', 'nativeName' => 'Frysk' ],
|
||||
@@ -62,7 +64,7 @@ class LanguageCodes
|
||||
'gl' => [ 'name' => 'Galician', 'nativeName' => 'Galego' ],
|
||||
'gu' => [ 'name' => 'Gujarati', 'nativeName' => 'ગુજરાતી' ],
|
||||
'gu-IN' => [ 'name' => 'Gujarati', 'nativeName' => 'ગુજરાતી' ],
|
||||
'he' => [ 'name' => 'Hebrew', 'nativeName' => 'עברית' ],
|
||||
'he' => [ 'name' => 'Hebrew', 'nativeName' => 'עברית', 'orientation' => 'rtl' ],
|
||||
'hi' => [ 'name' => 'Hindi', 'nativeName' => 'हिन्दी' ],
|
||||
'hi-IN' => [ 'name' => 'Hindi (India)', 'nativeName' => 'हिन्दी (भारत)' ],
|
||||
'hr' => [ 'name' => 'Croatian', 'nativeName' => 'Hrvatski' ],
|
||||
@@ -135,7 +137,7 @@ class LanguageCodes
|
||||
'tt' => [ 'name' => 'Tatar', 'nativeName' => 'Tatarça' ],
|
||||
'tt-RU' => [ 'name' => 'Tatar', 'nativeName' => 'Tatarça' ],
|
||||
'uk' => [ 'name' => 'Ukrainian', 'nativeName' => 'Українська' ],
|
||||
'ur' => [ 'name' => 'Urdu', 'nativeName' => 'اُردو' ],
|
||||
'ur' => [ 'name' => 'Urdu', 'nativeName' => 'اُردو', 'orientation' => 'rtl' ],
|
||||
've' => [ 'name' => 'Venda', 'nativeName' => 'Tshivenḓa' ],
|
||||
'vi' => [ 'name' => 'Vietnamese', 'nativeName' => 'Tiếng Việt' ],
|
||||
'wo' => [ 'name' => 'Wolof', 'nativeName' => 'Wolof' ],
|
||||
@@ -165,6 +167,24 @@ class LanguageCodes
|
||||
}
|
||||
}
|
||||
|
||||
public static function getOrientation($code)
|
||||
{
|
||||
if (isset(static::$codes[$code])) {
|
||||
if (isset(static::$codes[$code]['orientation'])) {
|
||||
return static::get($code, 'orientation');
|
||||
}
|
||||
}
|
||||
return 'ltr';
|
||||
}
|
||||
|
||||
public static function isRtl($code)
|
||||
{
|
||||
if (static::getOrientation($code) == 'rtl') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getNames(array $keys)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Helpers\Excerpts;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
trait ParsedownGravTrait
|
||||
@@ -18,13 +17,6 @@ trait ParsedownGravTrait
|
||||
/** @var Page $page */
|
||||
protected $page;
|
||||
|
||||
/** @var Pages $pages */
|
||||
protected $pages;
|
||||
|
||||
/** @var Uri $uri */
|
||||
protected $uri;
|
||||
|
||||
protected $pages_dir;
|
||||
protected $special_chars;
|
||||
protected $twig_link_regex = '/\!*\[(?:.*)\]\((\{([\{%#])\s*(.*?)\s*(?:\2|\})\})\)/';
|
||||
|
||||
@@ -42,10 +34,7 @@ trait ParsedownGravTrait
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->page = $page;
|
||||
$this->pages = $grav['pages'];
|
||||
$this->uri = $grav['uri'];
|
||||
$this->BlockTypes['{'] [] = "TwigTag";
|
||||
$this->pages_dir = Grav::instance()['locator']->findResource('page://');
|
||||
$this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
|
||||
|
||||
if ($defaults === null) {
|
||||
@@ -69,16 +58,23 @@ trait ParsedownGravTrait
|
||||
*/
|
||||
public function addBlockType($type, $tag, $continuable = false, $completable = false, $index = null)
|
||||
{
|
||||
$block = &$this->unmarkedBlockTypes;
|
||||
if ($type) {
|
||||
if (!isset($this->BlockTypes[$type])) {
|
||||
$this->BlockTypes[$type] = [];
|
||||
}
|
||||
$block = &$this->BlockTypes[$type];
|
||||
}
|
||||
|
||||
if (!isset($index)) {
|
||||
$this->BlockTypes[$type] [] = $tag;
|
||||
$block[] = $tag;
|
||||
} else {
|
||||
array_splice($this->BlockTypes[$type], $index, 0, $tag);
|
||||
array_splice($block, $index, 0, [$tag]);
|
||||
}
|
||||
|
||||
if ($continuable) {
|
||||
$this->continuable_blocks[] = $tag;
|
||||
}
|
||||
|
||||
if ($completable) {
|
||||
$this->completable_blocks[] = $tag;
|
||||
}
|
||||
@@ -92,10 +88,10 @@ trait ParsedownGravTrait
|
||||
*/
|
||||
public function addInlineType($type, $tag, $index = null)
|
||||
{
|
||||
if (!isset($index)) {
|
||||
if (!isset($index) || !isset($this->InlineTypes[$type])) {
|
||||
$this->InlineTypes[$type] [] = $tag;
|
||||
} else {
|
||||
array_splice($this->InlineTypes[$type], $index, 0, $tag);
|
||||
array_splice($this->InlineTypes[$type], $index, 0, [$tag]);
|
||||
}
|
||||
|
||||
if (strpos($this->inlineMarkerList, $type) === false) {
|
||||
@@ -151,7 +147,7 @@ trait ParsedownGravTrait
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
function setSpecialChars($special_chars)
|
||||
public function setSpecialChars($special_chars)
|
||||
{
|
||||
$this->special_chars = $special_chars;
|
||||
|
||||
@@ -203,74 +199,9 @@ trait ParsedownGravTrait
|
||||
$excerpt = parent::inlineImage($excerpt);
|
||||
}
|
||||
|
||||
// Some stuff we will need
|
||||
$actions = [];
|
||||
$media = null;
|
||||
|
||||
// if this is an image
|
||||
// if this is an image process it
|
||||
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']));
|
||||
|
||||
$this_host = isset($url['host']) && $url['host'] == $this->uri->host();
|
||||
|
||||
// if there is no host set but there is a path, the file is local
|
||||
if ((!isset($url['host']) || $this_host) && isset($url['path'])) {
|
||||
$path_parts = pathinfo($url['path']);
|
||||
|
||||
// get the local path to page media if possible
|
||||
if ($path_parts['dirname'] == $this->page->url(false, false, false)) {
|
||||
// get the media objects for this page
|
||||
$media = $this->page->media();
|
||||
} else {
|
||||
// see if this is an external page to this one
|
||||
$base_url = rtrim(Grav::instance()['base_url_relative'] . Grav::instance()['pages']->base(), '/');
|
||||
$page_route = '/' . ltrim(str_replace($base_url, '', $path_parts['dirname']), '/');
|
||||
|
||||
$ext_page = $this->pages->dispatch($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->media();
|
||||
}
|
||||
}
|
||||
|
||||
// if there is a media file that matches the path referenced..
|
||||
if ($media && isset($media->all()[$path_parts['basename']])) {
|
||||
// get the medium object
|
||||
$medium = $media->all()[$path_parts['basename']];
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url['query'])) {
|
||||
$url['query'] = htmlspecialchars_decode(urldecode($url['query']));
|
||||
$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) {
|
||||
$medium = call_user_func_array([$medium, $action['method']],
|
||||
explode(',', urldecode($action['params'])));
|
||||
}
|
||||
|
||||
if (isset($url['fragment'])) {
|
||||
$medium->urlHash($url['fragment']);
|
||||
}
|
||||
|
||||
$excerpt['element'] = $medium->parseDownElement($title, $alt, $class, true);
|
||||
|
||||
} else {
|
||||
// not a current page media file, see if it needs converting to relative
|
||||
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url);
|
||||
}
|
||||
}
|
||||
$excerpt = Excerpts::processImageExcerpt($excerpt, $this->page);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
@@ -298,63 +229,7 @@ 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 a query, then parse it and build action calls
|
||||
if (isset($url['query'])) {
|
||||
$actions = array_reduce(explode('&', $url['query']), function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
|
||||
$carry[$parts[0]] = $value;
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
// valid attributes supported
|
||||
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
|
||||
|
||||
// Unless told to not process, go through actions
|
||||
if (array_key_exists('noprocess', $actions)) {
|
||||
unset($actions['noprocess']);
|
||||
} else {
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $attrib => $value) {
|
||||
$key = $attrib;
|
||||
|
||||
if (in_array($attrib, $valid_attributes)) {
|
||||
// support both class and classes
|
||||
if ($attrib == 'classes') {
|
||||
$attrib = 'class';
|
||||
}
|
||||
$excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
|
||||
unset($actions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$url['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
// if no query elements left, unset query
|
||||
if (empty($url['query'])) {
|
||||
unset ($url['query']);
|
||||
}
|
||||
|
||||
// set path to / if not set
|
||||
if (empty($url['path'])) {
|
||||
$url['path'] = '';
|
||||
}
|
||||
|
||||
// if special scheme, just return
|
||||
if(isset($url['scheme']) && !Utils::startsWith($url['scheme'], 'http')) {
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
// handle paths and such
|
||||
$url = Uri::convertUrl($this->page, $url, $type);
|
||||
|
||||
// build the URL from the component parts and set it on the element
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url);
|
||||
$excerpt = Excerpts::processLinkExcerpt($excerpt, $this->page, $type);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
|
||||
@@ -84,7 +84,6 @@ class Collection extends Iterator
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->params = array_merge($this->params, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -124,6 +123,24 @@ class Collection extends Iterator
|
||||
return !empty($this->items[$offset]) ? $this->pages->get($offset) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split collection into array of smaller collections.
|
||||
*
|
||||
* @param $size
|
||||
* @return array|Collection[]
|
||||
*/
|
||||
public function batch($size)
|
||||
{
|
||||
$chunks = array_chunk($this->items, $size, true);
|
||||
|
||||
$list = [];
|
||||
foreach ($chunks as $chunk) {
|
||||
$list[] = new static($chunk, $this->params, $this->pages);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from the list.
|
||||
*
|
||||
@@ -154,12 +171,13 @@ class Collection extends Iterator
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array $manual
|
||||
* @param string $sort_flags
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function order($by, $dir = 'asc', $manual = null)
|
||||
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
|
||||
{
|
||||
$this->items = $this->pages->sortCollection($this, $by, $dir, $manual);
|
||||
$this->items = $this->pages->sortCollection($this, $by, $dir, $manual, $sort_flags);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -8,51 +8,77 @@
|
||||
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Getters;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Page\Medium\AbstractMedia;
|
||||
use Grav\Common\Page\Medium\GlobalMedia;
|
||||
use Grav\Common\Page\Medium\MediumFactory;
|
||||
|
||||
class Media extends Getters
|
||||
class Media extends AbstractMedia
|
||||
{
|
||||
protected $gettersVariable = 'instances';
|
||||
protected $path;
|
||||
protected static $global;
|
||||
|
||||
protected $instances = [];
|
||||
protected $images = [];
|
||||
protected $videos = [];
|
||||
protected $audios = [];
|
||||
protected $files = [];
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
if (!isset(static::$global)) {
|
||||
// Add fallback to global media.
|
||||
static::$global = new GlobalMedia($path);
|
||||
}
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return parent::offsetExists($offset) ?: isset(static::$global[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return parent::offsetGet($offset) ?: static::$global[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class.
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
|
||||
// Handle special cases where page doesn't exist in filesystem.
|
||||
if (!is_dir($path)) {
|
||||
if (!is_dir($this->path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->path = $path;
|
||||
|
||||
$iterator = new \FilesystemIterator($path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
|
||||
$iterator = new \FilesystemIterator($this->path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
|
||||
|
||||
$media = [];
|
||||
|
||||
/** @var \DirectoryIterator $info */
|
||||
foreach ($iterator as $path => $info) {
|
||||
// Ignore folders and Markdown files.
|
||||
if (!$info->isFile() || $info->getExtension() == 'md' || $info->getBasename() === '.DS_Store') {
|
||||
if (!$info->isFile() || $info->getExtension() == 'md' || $info->getBasename()[0] === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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] = [ 'file' => $path, 'size' => $info->getSize() ];
|
||||
} else {
|
||||
$media["{$basename}.{$ext}"][$type] = [ 'file' => $path, 'size' => $info->getSize() ];
|
||||
@@ -74,16 +100,16 @@ class Media extends Getters
|
||||
}
|
||||
|
||||
// Create the base medium
|
||||
if (!empty($types['base'])) {
|
||||
if (empty($types['base'])) {
|
||||
if (!isset($types['alternative'])) {
|
||||
continue;
|
||||
}
|
||||
$max = max(array_keys($types['alternative']));
|
||||
$medium = $types['alternative'][$max]['file'];
|
||||
$medium = MediumFactory::scaledFromMedium($medium, $max, 1)['file'];
|
||||
} else {
|
||||
$medium = MediumFactory::fromFile($types['base']['file']);
|
||||
$medium && $medium->set('size', $types['base']['size']);
|
||||
} else if (!empty($types['alternative'])) {
|
||||
$altMedium = reset($types['alternative']);
|
||||
$ratio = key($types['alternative']);
|
||||
|
||||
$altMedium = $altMedium['file'];
|
||||
|
||||
$medium = MediumFactory::scaledFromMedium($altMedium, $ratio, 1)['file'];
|
||||
}
|
||||
|
||||
if (empty($medium)) {
|
||||
@@ -103,10 +129,9 @@ class Media extends Getters
|
||||
// Build missing alternatives
|
||||
if (!empty($types['alternative'])) {
|
||||
$alternatives = $types['alternative'];
|
||||
|
||||
$max = max(array_keys($alternatives));
|
||||
|
||||
for ($i=2; $i < $max; $i++) {
|
||||
for ($i=$max; $i > 1; $i--) {
|
||||
if (isset($alternatives[$i])) {
|
||||
continue;
|
||||
}
|
||||
@@ -114,142 +139,15 @@ class Media extends Getters
|
||||
$types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max]['file'], $max, $i);
|
||||
}
|
||||
|
||||
foreach ($types['alternative'] as $ratio => $altMedium) {
|
||||
$medium->addAlternative($ratio, $altMedium['file']);
|
||||
foreach ($types['alternative'] as $altMedium) {
|
||||
if ($altMedium['file'] != $medium) {
|
||||
$ratio = $altMedium['file']->get('width') / $medium->get('width');
|
||||
$medium->addAlternative($ratio, $altMedium['file']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->add($name, $medium);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get medium by filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return Medium|null
|
||||
*/
|
||||
public function get($filename)
|
||||
{
|
||||
return isset($this->instances[$filename]) ? $this->instances[$filename] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
ksort($this->instances, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all image media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function images()
|
||||
{
|
||||
ksort($this->images, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all video media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function videos()
|
||||
{
|
||||
ksort($this->videos, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->videos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all audio media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function audios()
|
||||
{
|
||||
ksort($this->audios, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->audios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all file media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
ksort($this->files, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected function add($name, $file)
|
||||
{
|
||||
$this->instances[$name] = $file;
|
||||
switch ($file->type) {
|
||||
case 'image':
|
||||
$this->images[$name] = $file;
|
||||
break;
|
||||
case 'video':
|
||||
$this->videos[$name] = $file;
|
||||
break;
|
||||
case 'audio':
|
||||
$this->audios[$name] = $file;
|
||||
break;
|
||||
default:
|
||||
$this->files[$name] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filename, extension and meta part.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return array
|
||||
*/
|
||||
protected function getFileParts($filename)
|
||||
{
|
||||
$fileParts = explode('.', $filename);
|
||||
|
||||
$name = array_shift($fileParts);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array($name, $extension, $type, $extra);
|
||||
}
|
||||
}
|
||||
|
||||
164
system/src/Grav/Common/Page/Medium/AbstractMedia.php
Normal file
164
system/src/Grav/Common/Page/Medium/AbstractMedia.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Page
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Getters;
|
||||
|
||||
abstract class AbstractMedia extends Getters
|
||||
{
|
||||
protected $gettersVariable = 'instances';
|
||||
|
||||
protected $instances = [];
|
||||
protected $images = [];
|
||||
protected $videos = [];
|
||||
protected $audios = [];
|
||||
protected $files = [];
|
||||
|
||||
/**
|
||||
* Get medium by filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return Medium|null
|
||||
*/
|
||||
public function get($filename)
|
||||
{
|
||||
return $this->offsetGet($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call object as function to get medium by filename.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return mixed
|
||||
*/
|
||||
public function __invoke($filename)
|
||||
{
|
||||
return $this->offsetGet($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
ksort($this->instances, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all image media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function images()
|
||||
{
|
||||
ksort($this->images, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all video media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function videos()
|
||||
{
|
||||
ksort($this->videos, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->videos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all audio media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function audios()
|
||||
{
|
||||
ksort($this->audios, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->audios;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all file media.
|
||||
*
|
||||
* @return array|Medium[]
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
ksort($this->files, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param Medium $file
|
||||
*/
|
||||
protected function add($name, $file)
|
||||
{
|
||||
$this->instances[$name] = $file;
|
||||
switch ($file->type) {
|
||||
case 'image':
|
||||
$this->images[$name] = $file;
|
||||
break;
|
||||
case 'video':
|
||||
$this->videos[$name] = $file;
|
||||
break;
|
||||
case 'audio':
|
||||
$this->audios[$name] = $file;
|
||||
break;
|
||||
default:
|
||||
$this->files[$name] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filename, extension and meta part.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return array
|
||||
*/
|
||||
protected function getFileParts($filename)
|
||||
{
|
||||
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 {
|
||||
$fileParts = explode('.', $filename);
|
||||
|
||||
$name = array_shift($fileParts);
|
||||
$extension = null;
|
||||
$extra = null;
|
||||
$type = 'base';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array($name, $extension, $type, $extra);
|
||||
}
|
||||
}
|
||||
117
system/src/Grav/Common/Page/Medium/GlobalMedia.php
Normal file
117
system/src/Grav/Common/Page/Medium/GlobalMedia.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Page
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class GlobalMedia extends AbstractMedia
|
||||
{
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return parent::offsetExists($offset) ?: !empty($this->resolveStream($offset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return parent::offsetGet($offset) ?: $this->addMedium($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return string|null
|
||||
*/
|
||||
protected function resolveStream($filename)
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
return $locator->isStream($filename) ? ($locator->findResource($filename) ?: null) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stream
|
||||
* @return Medium|null
|
||||
*/
|
||||
protected function addMedium($stream)
|
||||
{
|
||||
$filename = $this->resolveStream($stream);
|
||||
if (!$filename) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = dirname($filename);
|
||||
list($basename, $ext,, $extra) = $this->getFileParts(basename($filename));
|
||||
$medium = MediumFactory::fromFile($filename);
|
||||
|
||||
if (empty($medium)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$medium->set('size', filesize($filename));
|
||||
$scale = (int) ($extra ?: 1);
|
||||
|
||||
if ($scale !== 1) {
|
||||
$altMedium = $medium;
|
||||
|
||||
// Create scaled down regular sized image.
|
||||
$medium = MediumFactory::scaledFromMedium($altMedium, $scale, 1)['file'];
|
||||
|
||||
if (empty($medium)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add original sized image as alternative.
|
||||
$medium->addAlternative($scale, $altMedium['file']);
|
||||
|
||||
// Locate or generate smaller retina images.
|
||||
for ($i = $scale-1; $i > 1; $i--) {
|
||||
$altFilename = "{$path}/{$basename}@{$i}x.{$ext}";
|
||||
|
||||
if (file_exists($altFilename)) {
|
||||
$scaled = MediumFactory::fromFile($altFilename);
|
||||
} else {
|
||||
$scaled = MediumFactory::scaledFromMedium($altMedium, $scale, $i)['file'];
|
||||
}
|
||||
|
||||
if ($scaled) {
|
||||
$medium->addAlternative($i, $scaled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$meta = "{$path}/{$basename}.{$ext}.yaml";
|
||||
if (file_exists($meta)) {
|
||||
$medium->addMetaFile($meta);
|
||||
}
|
||||
$meta = "{$path}/{$basename}.{$ext}.meta.yaml";
|
||||
if (file_exists($meta)) {
|
||||
$medium->addMetaFile($meta);
|
||||
}
|
||||
|
||||
$thumb = "{$path}/{$basename}.thumb.{$ext}";
|
||||
if (file_exists($thumb)) {
|
||||
$medium->set('thumbnails.page', $thumb);
|
||||
}
|
||||
|
||||
$this->add($stream, $medium);
|
||||
|
||||
return $medium;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class ImageMedium extends Medium
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $thumbnailTypes = [ 'page', 'media', 'default' ];
|
||||
protected $thumbnailTypes = ['page', 'media', 'default'];
|
||||
|
||||
/**
|
||||
* @var ImageFile
|
||||
@@ -58,18 +58,13 @@ class ImageMedium extends Medium
|
||||
* @var array
|
||||
*/
|
||||
public static $magic_resize_actions = [
|
||||
'resize' => [ 0, 1 ],
|
||||
'forceResize' => [ 0, 1 ],
|
||||
'cropResize' => [ 0, 1 ],
|
||||
'crop' => [ 0, 1, 2, 3 ],
|
||||
'zoomCrop' => [ 0, 1 ]
|
||||
'resize' => [0, 1],
|
||||
'forceResize' => [0, 1],
|
||||
'cropResize' => [0, 1],
|
||||
'crop' => [0, 1, 2, 3],
|
||||
'zoomCrop' => [0, 1]
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -197,66 +192,120 @@ class ImageMedium extends Medium
|
||||
*/
|
||||
public function srcset($reset = true)
|
||||
{
|
||||
if (empty($this->alternatives) && empty($this->derivatives)) {
|
||||
if (empty($this->alternatives)) {
|
||||
if ($reset) {
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!empty($this->derivatives)) {
|
||||
asort($this->derivatives);
|
||||
|
||||
foreach ($this->derivatives as $url => $width) {
|
||||
$srcset[] = $url . ' ' . $width . 'w';
|
||||
}
|
||||
|
||||
$srcset[] = $this->url($reset) . ' ' . $this->get('width') . 'w';
|
||||
}
|
||||
else {
|
||||
$srcset = [ $this->url($reset) . ' ' . $this->get('width') . 'w' ];
|
||||
foreach ($this->alternatives as $ratio => $medium) {
|
||||
$srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
|
||||
}
|
||||
$srcset = [];
|
||||
foreach ($this->alternatives as $ratio => $medium) {
|
||||
$srcset[] = $medium->url($reset) . ' ' . $medium->get('width') . 'w';
|
||||
}
|
||||
$srcset[] = $this->url($reset) . ' ' . $this->get('width') . 'w';
|
||||
|
||||
return implode(', ', $srcset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate derivatives
|
||||
* Allows the ability to override the Inmage's Pretty name stored in cache
|
||||
*
|
||||
* @param int $min_width
|
||||
* @param int $max_width
|
||||
* @param int $step
|
||||
* @return $this
|
||||
* @param $name
|
||||
*/
|
||||
public function derivatives($min_width, $max_width, $step = 200) {
|
||||
$width = $min_width;
|
||||
|
||||
// Do not upscale images.
|
||||
if ($max_width > $this->get('width')) {
|
||||
$max_width = $this->get('width');
|
||||
}
|
||||
|
||||
while ($width <= $max_width) {
|
||||
$ratio = $width / $this->get('width');
|
||||
$derivative = MediumFactory::scaledFromMedium($this, 1, $ratio);
|
||||
if (is_array($derivative)) {
|
||||
$this->addDerivative($derivative['file']);
|
||||
public function setImagePrettyName($name)
|
||||
{
|
||||
$this->set('prettyname', $name);
|
||||
if ($this->image) {
|
||||
$this->image->setPrettyName($name);
|
||||
}
|
||||
}
|
||||
|
||||
public function getImagePrettyName()
|
||||
{
|
||||
if ($this->get('prettyname')) {
|
||||
return $this->get('prettyname');
|
||||
} else {
|
||||
$basename = $this->get('basename');
|
||||
if (preg_match('/[a-z0-9]{40}-(.*)/', $basename, $matches)) {
|
||||
$basename = $matches[1];
|
||||
}
|
||||
return $basename;
|
||||
}
|
||||
$width += $step;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a derivative
|
||||
* Generate alternative image widths, using either an array of integers, or
|
||||
* a min width, a max width, and a step parameter to fill out the necessary
|
||||
* widths. Existing image alternatives won't be overwritten.
|
||||
*
|
||||
* @param ImageMedium $image
|
||||
* @param int|int[] $min_width
|
||||
* @param int [$max_width=2500]
|
||||
* @param int [$step=200]
|
||||
* @return $this
|
||||
*/
|
||||
public function addDerivative(ImageMedium $image) {
|
||||
$this->derivatives[$image->url()] = $image->get('width');
|
||||
public function derivatives($min_width, $max_width = 2500, $step = 200) {
|
||||
if (!empty($this->alternatives)) {
|
||||
$max = max(array_keys($this->alternatives));
|
||||
$base = $this->alternatives[$max];
|
||||
} else {
|
||||
$base = $this;
|
||||
}
|
||||
|
||||
$widths = [];
|
||||
|
||||
if (func_num_args() === 1) {
|
||||
foreach ((array) func_get_arg(0) as $width) {
|
||||
if ($width < $base->get('width')) {
|
||||
$widths[] = $width;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$max_width = min($max_width, $base->get('width'));
|
||||
|
||||
for ($width = $min_width; $width < $max_width; $width = $width + $step) {
|
||||
$widths[] = $width;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($widths as $width) {
|
||||
// Only generate image alternatives that don't already exist
|
||||
if (array_key_exists((int) $width, $this->alternatives)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$derivative = MediumFactory::fromFile($base->get('filepath'));
|
||||
|
||||
// It's possible that MediumFactory::fromFile returns null if the
|
||||
// original image file no longer exists and this class instance was
|
||||
// retrieved from the page cache
|
||||
if (isset($derivative)) {
|
||||
$index = 2;
|
||||
$alt_widths = array_keys($this->alternatives);
|
||||
sort($alt_widths);
|
||||
|
||||
foreach ($alt_widths as $i => $key) {
|
||||
if ($width > $key) {
|
||||
$index += max($i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
$basename = preg_replace('/(@\d+x){0,1}$/', "@{$width}w", $base->get('basename'), 1);
|
||||
$derivative->setImagePrettyName($basename);
|
||||
|
||||
$ratio = $base->get('width') / $width;
|
||||
$height = $derivative->get('height') / $ratio;
|
||||
|
||||
$derivative->resize($width, $height);
|
||||
$derivative->set('width', $width);
|
||||
$derivative->set('height', $height);
|
||||
|
||||
$this->addAlternative($ratio, $derivative);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -463,7 +512,11 @@ class ImageMedium extends Medium
|
||||
try {
|
||||
call_user_func_array([$this->image, $method], $args);
|
||||
|
||||
foreach ($this->alternatives as $ratio => $medium) {
|
||||
foreach ($this->alternatives as $medium) {
|
||||
if (!$medium->image) {
|
||||
$medium->image();
|
||||
}
|
||||
|
||||
$args_copy = $args;
|
||||
|
||||
// regular image: resize 400x400 -> 200x200
|
||||
@@ -471,7 +524,7 @@ class ImageMedium extends Medium
|
||||
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;
|
||||
$args_copy[$param] *= $medium->get('ratio');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -494,14 +547,14 @@ class ImageMedium extends Medium
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
$file = $this->get('filepath');
|
||||
$cacheDir = $locator->findResource('cache://images', true);
|
||||
|
||||
// Use existing cache folder or if it doesn't exist, create it.
|
||||
$cacheDir = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
|
||||
|
||||
$this->image = ImageFile::open($file)
|
||||
->setCacheDir($cacheDir)
|
||||
->setActualCacheDir($cacheDir)
|
||||
->setPrettyName(basename($this->get('basename')));
|
||||
|
||||
$this->filter();
|
||||
->setPrettyName($this->getImagePrettyName());
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -517,6 +570,8 @@ class ImageMedium extends Medium
|
||||
return parent::path(false);
|
||||
}
|
||||
|
||||
$this->filter();
|
||||
|
||||
if (isset($this->result)) {
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
@@ -36,12 +36,13 @@ class Link implements RenderableInterface
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param string $id
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true)
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
|
||||
{
|
||||
$innerElement = $this->source->parsedownElement($title, $alt, $class, $reset);
|
||||
$innerElement = $this->source->parsedownElement($title, $alt, $class, $id, $reset);
|
||||
|
||||
return [
|
||||
'name' => 'a',
|
||||
|
||||
@@ -100,7 +100,9 @@ class Medium extends Data implements RenderableInterface
|
||||
}
|
||||
|
||||
$alternative->set('ratio', $ratio);
|
||||
$this->alternatives[(float) $ratio] = $alternative;
|
||||
$width = $alternative->get('width');
|
||||
|
||||
$this->alternatives[$width] = $alternative;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,8 +150,8 @@ class Medium extends Data implements RenderableInterface
|
||||
/**
|
||||
* Get/set querystring for the file's url
|
||||
*
|
||||
* @param string $hash
|
||||
* @param boolean $withHash
|
||||
* @param string $querystring
|
||||
* @param boolean $withQuestionmark
|
||||
* @return string
|
||||
*/
|
||||
public function querystring($querystring = null, $withQuestionmark = true)
|
||||
@@ -199,10 +201,11 @@ class Medium extends Data implements RenderableInterface
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param string $id
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true)
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
|
||||
{
|
||||
$attributes = $this->attributes;
|
||||
|
||||
@@ -241,6 +244,14 @@ class Medium extends Data implements RenderableInterface
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($attributes['id'])) {
|
||||
if (!empty($id)) {
|
||||
$attributes['id'] = $id;
|
||||
} elseif (!empty($this->items['id'])) {
|
||||
$attributes['id'] = $this->items['id'];
|
||||
}
|
||||
}
|
||||
|
||||
switch ($this->mode) {
|
||||
case 'text':
|
||||
$element = $this->textParsedownElement($attributes, false);
|
||||
|
||||
@@ -119,23 +119,27 @@ class MediumFactory
|
||||
}
|
||||
|
||||
$ratio = $to / $from;
|
||||
$width = (int) ($medium->get('width') * $ratio);
|
||||
$height = (int) ($medium->get('height') * $ratio);
|
||||
$width = $medium->get('width') * $ratio;
|
||||
$height = $medium->get('height') * $ratio;
|
||||
|
||||
$basename = $medium->get('basename');
|
||||
$basename = str_replace('@'.$from.'x', '@'.$to.'x', $basename);
|
||||
$prev_basename = $medium->get('basename');
|
||||
$basename = str_replace('@'.$from.'x', '@'.$to.'x', $prev_basename);
|
||||
|
||||
$debug = $medium->get('debug');
|
||||
$medium->set('debug', false);
|
||||
$medium->setImagePrettyName($basename);
|
||||
|
||||
$file = $medium->resize($width, $height)->path();
|
||||
|
||||
$medium->set('debug', $debug);
|
||||
$medium->setImagePrettyName($prev_basename);
|
||||
|
||||
$size = filesize($file);
|
||||
|
||||
$medium = self::fromFile($file);
|
||||
$medium->set('size', $size);
|
||||
if ($medium) {
|
||||
$medium->set('size', $size);
|
||||
}
|
||||
|
||||
return ['file' => $medium, 'size' => $size];
|
||||
}
|
||||
|
||||
@@ -21,13 +21,15 @@ trait ParsedownHtmlTrait
|
||||
* Return HTML markup from the medium.
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param string $id
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $alt = null, $class = null, $reset = true)
|
||||
public function html($title = null, $alt = null, $class = null, $id = null, $reset = true)
|
||||
{
|
||||
$element = $this->parsedownElement($title, $alt, $class, $reset);
|
||||
$element = $this->parsedownElement($title, $alt, $class, $id, $reset);
|
||||
|
||||
if (!$this->parsedown) {
|
||||
$this->parsedown = new Parsedown(null, null);
|
||||
|
||||
@@ -27,8 +27,9 @@ interface RenderableInterface
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param string $id
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true);
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,6 @@ class StaticImageMedium extends Medium
|
||||
{
|
||||
empty($attributes['src']) && $attributes['src'] = $this->url($reset);
|
||||
|
||||
return [ 'name' => 'image', 'attributes' => $attributes ];
|
||||
return [ 'name' => 'img', 'attributes' => $attributes ];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,15 +34,16 @@ class ThumbnailImageMedium extends ImageMedium
|
||||
/**
|
||||
* Get an element (is array) that can be rendered by the Parsedown engine
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param string $id
|
||||
* @param boolean $reset
|
||||
* @return array
|
||||
*/
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $reset = true)
|
||||
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
|
||||
{
|
||||
return $this->bubble('parsedownElement', [$title, $alt, $class, $reset]);
|
||||
return $this->bubble('parsedownElement', [$title, $alt, $class, $id, $reset]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,12 +52,13 @@ class ThumbnailImageMedium extends ImageMedium
|
||||
* @param string $title
|
||||
* @param string $alt
|
||||
* @param string $class
|
||||
* @param string $id
|
||||
* @param bool $reset
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $alt = null, $class = null, $reset = true)
|
||||
public function html($title = null, $alt = null, $class = null, $id = null, $reset = true)
|
||||
{
|
||||
return $this->bubble('html', [$title, $alt, $class, $reset]);
|
||||
return $this->bubble('html', [$title, $alt, $class, $id, $reset]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,6 +54,7 @@ class Page
|
||||
protected $routable;
|
||||
protected $modified;
|
||||
protected $redirect;
|
||||
protected $external_url;
|
||||
protected $items;
|
||||
protected $header;
|
||||
protected $frontmatter;
|
||||
@@ -84,6 +85,7 @@ class Page
|
||||
protected $home_route;
|
||||
protected $hide_home_route;
|
||||
protected $ssl;
|
||||
protected $template_format;
|
||||
|
||||
/**
|
||||
* @var Page Unmodified (original) version of the page. Used for copying and moving the page.
|
||||
@@ -162,8 +164,8 @@ class Page
|
||||
unset($process_fields[$field]);
|
||||
}
|
||||
}
|
||||
$text_header = Grav::instance()['twig']->processString(json_encode($process_fields), ['page'=>$this]);
|
||||
$this->header((object) (json_decode($text_header, true) + $ignored_fields));
|
||||
$text_header = Grav::instance()['twig']->processString(json_encode($process_fields), ['page' => $this]);
|
||||
$this->header((object)(json_decode($text_header, true) + $ignored_fields));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,6 +366,9 @@ class Page
|
||||
if (isset($this->header->redirect)) {
|
||||
$this->redirect = trim($this->header->redirect);
|
||||
}
|
||||
if (isset($this->header->external_url)) {
|
||||
$this->external_url = trim($this->header->external_url);
|
||||
}
|
||||
if (isset($this->header->order_dir)) {
|
||||
$this->order_dir = trim($this->header->order_dir);
|
||||
}
|
||||
@@ -414,7 +419,10 @@ class Page
|
||||
$this->last_modified = (bool)$this->header->last_modified;
|
||||
}
|
||||
if (isset($this->header->ssl)) {
|
||||
$this->ssl = (bool) $this->header->ssl;
|
||||
$this->ssl = (bool)$this->header->ssl;
|
||||
}
|
||||
if (isset($this->header->template_format)) {
|
||||
$this->template_format = $this->header->template_format;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,7 +506,9 @@ class Page
|
||||
$size = 300;
|
||||
}
|
||||
|
||||
return html_entity_decode(Utils::truncateHTML($content, $size));
|
||||
$summary = Utils::truncateHTML($content, $size);
|
||||
|
||||
return html_entity_decode($summary);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -562,16 +572,16 @@ class Page
|
||||
$twig_first = isset($this->header->twig_first) ? $this->header->twig_first : $config->get('system.pages.twig_first',
|
||||
true);
|
||||
|
||||
// never cache twig means it's always run after content
|
||||
$never_cache_twig = isset($this->header->never_cache_twig) ? $this->header->never_cache_twig : $config->get('system.pages.never_cache_twig',
|
||||
false);
|
||||
|
||||
// if no cached-content run everything
|
||||
if ($this->content === false || $cache_enable === false) {
|
||||
$this->content = $this->raw_content;
|
||||
Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this]));
|
||||
if ($never_cache_twig) {
|
||||
if ($this->content === false || $cache_enable === false) {
|
||||
$this->content = $this->raw_content;
|
||||
Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this]));
|
||||
|
||||
if ($twig_first) {
|
||||
if ($process_twig) {
|
||||
$this->processTwig();
|
||||
}
|
||||
if ($process_markdown) {
|
||||
$this->processMarkdown();
|
||||
}
|
||||
@@ -579,21 +589,47 @@ class Page
|
||||
// Content Processed but not cached yet
|
||||
Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
|
||||
|
||||
} else {
|
||||
if ($process_markdown) {
|
||||
$this->processMarkdown();
|
||||
}
|
||||
|
||||
// Content Processed but not cached yet
|
||||
Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
|
||||
|
||||
if ($process_twig) {
|
||||
$this->processTwig();
|
||||
if ($cache_enable) {
|
||||
$this->cachePageContent();
|
||||
}
|
||||
}
|
||||
|
||||
if ($cache_enable) {
|
||||
$this->cachePageContent();
|
||||
if ($process_twig) {
|
||||
$this->processTwig();
|
||||
}
|
||||
|
||||
} else {
|
||||
if ($this->content === false || $cache_enable === false) {
|
||||
$this->content = $this->raw_content;
|
||||
Grav::instance()->fireEvent('onPageContentRaw', new Event(['page' => $this]));
|
||||
|
||||
if ($twig_first) {
|
||||
if ($process_twig) {
|
||||
$this->processTwig();
|
||||
}
|
||||
if ($process_markdown) {
|
||||
$this->processMarkdown();
|
||||
}
|
||||
|
||||
// Content Processed but not cached yet
|
||||
Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
|
||||
|
||||
} else {
|
||||
if ($process_markdown) {
|
||||
$this->processMarkdown();
|
||||
}
|
||||
|
||||
// Content Processed but not cached yet
|
||||
Grav::instance()->fireEvent('onPageContentProcessed', new Event(['page' => $this]));
|
||||
|
||||
if ($process_twig) {
|
||||
$this->processTwig();
|
||||
}
|
||||
}
|
||||
|
||||
if ($cache_enable) {
|
||||
$this->cachePageContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,6 +656,7 @@ class Page
|
||||
if ($this->content === null) {
|
||||
$this->content();
|
||||
}
|
||||
|
||||
return $this->getContentMeta();
|
||||
}
|
||||
|
||||
@@ -638,18 +675,20 @@ class Page
|
||||
* Return the whole contentMeta array as it currently stands
|
||||
*
|
||||
* @param null $name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getContentMeta($name = null)
|
||||
{
|
||||
if ($name) {
|
||||
if(isset($this->content_meta[$name])) {
|
||||
if (isset($this->content_meta[$name])) {
|
||||
return $this->content_meta[$name];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->content_meta;
|
||||
}
|
||||
|
||||
@@ -657,6 +696,7 @@ class Page
|
||||
* Sets the whole content meta array in one shot
|
||||
*
|
||||
* @param $content_meta
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setContentMeta($content_meta)
|
||||
@@ -757,9 +797,7 @@ class Page
|
||||
return (bool)$this->order();
|
||||
}
|
||||
if ($name == 'folder') {
|
||||
$regex = '/^[0-9]+\./u';
|
||||
|
||||
return preg_replace($regex, '', $this->folder);
|
||||
return preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder);
|
||||
}
|
||||
if ($name == 'name') {
|
||||
$language = $this->language() ? '.' . $this->language() : '';
|
||||
@@ -890,6 +928,8 @@ class Page
|
||||
$this->route(Grav::instance()['pages']->root()->route() . '/' . $this->slug());
|
||||
}
|
||||
|
||||
$this->raw_route = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -1094,6 +1134,27 @@ class Page
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a page to override the output render format, usually the extension provided
|
||||
* in the URL. (e.g. `html`, `json`, `xml`, etc).
|
||||
*
|
||||
* @param null $var
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function templateFormat($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->template_format = $var;
|
||||
}
|
||||
|
||||
if (empty($this->template_format)) {
|
||||
$this->template_format = Grav::instance()['uri']->extension('html');
|
||||
}
|
||||
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the extension field.
|
||||
*
|
||||
@@ -1127,7 +1188,8 @@ class Page
|
||||
|
||||
// if not set in the page get the value from system config
|
||||
if (empty($this->url_extension)) {
|
||||
$this->url_extension = trim(isset($this->header->append_url_extension) ? $this->header->append_url_extension : Grav::instance()['config']->get('system.pages.append_url_extension', false));
|
||||
$this->url_extension = trim(isset($this->header->append_url_extension) ? $this->header->append_url_extension : Grav::instance()['config']->get('system.pages.append_url_extension',
|
||||
false));
|
||||
}
|
||||
|
||||
return $this->url_extension;
|
||||
@@ -1204,8 +1266,7 @@ class Page
|
||||
if ($this->visible === null) {
|
||||
// Set item visibility in menu if folder is different from slug
|
||||
// eg folder = 01.Home and slug = Home
|
||||
$regex = '/^[0-9]+\./u';
|
||||
if (preg_match($regex, $this->folder)) {
|
||||
if (preg_match(PAGE_ORDER_PREFIX_REGEX, $this->folder)) {
|
||||
$this->visible = true;
|
||||
} else {
|
||||
$this->visible = false;
|
||||
@@ -1289,7 +1350,7 @@ class Page
|
||||
public function ssl($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->ssl = (bool) $var;
|
||||
$this->ssl = (bool)$var;
|
||||
}
|
||||
|
||||
return $this->ssl;
|
||||
@@ -1350,21 +1411,28 @@ class Page
|
||||
// Backward compatibility for nested arrays in metas
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $property => $prop_value) {
|
||||
$prop_key = $key . ":" . $property;
|
||||
$this->metadata[$prop_key] = ['name' => $prop_key, 'property' => $prop_key, 'content' => htmlspecialchars($prop_value, ENT_QUOTES, 'UTF-8')];
|
||||
$prop_key = $key . ":" . $property;
|
||||
$this->metadata[$prop_key] = [
|
||||
'name' => $prop_key,
|
||||
'property' => $prop_key,
|
||||
'content' => htmlspecialchars($prop_value, ENT_QUOTES, 'UTF-8')
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// If it this is a standard meta data type
|
||||
if ($value) {
|
||||
if (in_array($key, $header_tag_http_equivs)) {
|
||||
$this->metadata[$key] = ['http_equiv' => $key, 'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')];
|
||||
$this->metadata[$key] = [
|
||||
'http_equiv' => $key,
|
||||
'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
|
||||
];
|
||||
} elseif ($key == 'charset') {
|
||||
$this->metadata[$key] = ['charset' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')];
|
||||
} else {
|
||||
// if it's a social metadata with separator, render as property
|
||||
$separator = strpos($key, ':');
|
||||
$separator = strpos($key, ':');
|
||||
$hasSeparator = $separator && $separator < strlen($key) - 1;
|
||||
$entry = ['name' => $key, 'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')];
|
||||
$entry = ['name' => $key, 'content' => htmlspecialchars($value, ENT_QUOTES, 'UTF-8')];
|
||||
|
||||
if ($hasSeparator) {
|
||||
$entry['property'] = $key;
|
||||
@@ -1392,8 +1460,8 @@ class Page
|
||||
{
|
||||
if ($var !== null && $var !== "") {
|
||||
$this->slug = $var;
|
||||
if(!preg_match('/^[a-z0-9][-a-z0-9]*$/', $this->slug)){
|
||||
Grav::instance()['log']->notice("Invalid slug set in YAML frontmatter: " . $this->rawRoute() . " => ". $this->slug);
|
||||
if (!preg_match('/^[a-z0-9][-a-z0-9]*$/', $this->slug)) {
|
||||
Grav::instance()['log']->notice("Invalid slug set in YAML frontmatter: " . $this->rawRoute() . " => " . $this->slug);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1402,7 +1470,6 @@ class Page
|
||||
}
|
||||
|
||||
|
||||
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
@@ -1417,22 +1484,16 @@ class Page
|
||||
{
|
||||
if ($var !== null) {
|
||||
$order = !empty($var) ? sprintf('%02d.', (int)$var) : '';
|
||||
$this->folder($order . $this->slug());
|
||||
$this->folder($order . preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder));
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
preg_match(PAGE_ORDER_PREFIX_REGEX, $this->folder, $order);
|
||||
|
||||
return isset($order[0]) ? $order[0] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL with host information, aka Permalink.
|
||||
* @return string The permalink.
|
||||
*/
|
||||
public function permalink()
|
||||
{
|
||||
return $this->url(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL for a page - alias of url().
|
||||
*
|
||||
@@ -1445,16 +1506,36 @@ class Page
|
||||
return $this->url($include_host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL with host information, aka Permalink.
|
||||
* @return string The permalink.
|
||||
*/
|
||||
public function permalink()
|
||||
{
|
||||
return $this->url(true, false, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical URL for a page
|
||||
*
|
||||
* @param bool $include_lang
|
||||
* @return string
|
||||
*/
|
||||
public function canonical($include_lang = true)
|
||||
{
|
||||
return $this->url(true, true, $include_lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the url for the Page.
|
||||
*
|
||||
* @param bool $include_host Defaults false, but true would include http://yourhost.com
|
||||
* @param bool $canonical true to return the canonical URL
|
||||
* @param bool $canonical true to return the canonical URL
|
||||
* @param bool $include_lang
|
||||
*
|
||||
* @param bool $raw_route
|
||||
* @return string The url.
|
||||
*/
|
||||
public function url($include_host = false, $canonical = false, $include_lang = true)
|
||||
public function url($include_host = false, $canonical = false, $include_lang = true, $raw_route = false)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
@@ -1470,6 +1551,11 @@ class Page
|
||||
/** @var Uri $uri */
|
||||
$uri = $grav['uri'];
|
||||
|
||||
// Override any URL when external_url is set
|
||||
if (isset($this->external_url)) {
|
||||
return $this->external_url;
|
||||
}
|
||||
|
||||
// get pre-route
|
||||
if ($include_lang && $language->enabled()) {
|
||||
$pre_route = $language->getLanguageURLPrefix();
|
||||
@@ -1485,6 +1571,8 @@ class Page
|
||||
// get canonical route if requested
|
||||
if ($canonical) {
|
||||
$route = $pre_route . $this->routeCanonical();
|
||||
} elseif ($raw_route) {
|
||||
$route = $pre_route . $this->rawRoute();
|
||||
} else {
|
||||
$route = $pre_route . $this->route();
|
||||
}
|
||||
@@ -1566,8 +1654,7 @@ class Page
|
||||
if (empty($this->raw_route)) {
|
||||
$baseRoute = $this->parent ? (string)$this->parent()->rawRoute() : null;
|
||||
|
||||
$regex = '/^[0-9]+\./u';
|
||||
$slug = preg_replace($regex, '', $this->folder);
|
||||
$slug = preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder);
|
||||
|
||||
$this->raw_route = isset($baseRoute) ? $baseRoute . '/' . $slug : null;
|
||||
}
|
||||
@@ -1626,7 +1713,10 @@ class Page
|
||||
public function id($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->id = $var;
|
||||
// store unique per language
|
||||
$active_lang = Grav::instance()['language']->getLanguage() ?: '';
|
||||
$id = $active_lang . $var;
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
return $this->id;
|
||||
@@ -2144,7 +2234,10 @@ class Page
|
||||
*/
|
||||
public function home()
|
||||
{
|
||||
return $this->find('/') == $this;
|
||||
$home = Grav::instance()['config']->get('system.home.alias');
|
||||
$is_home = ($this->route() == $home || $this->rawRoute() == $home);
|
||||
|
||||
return $is_home;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2195,7 +2288,7 @@ class Page
|
||||
}
|
||||
|
||||
if (!isset($params['items'])) {
|
||||
return [];
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
$collection = $this->evaluate($params['items']);
|
||||
@@ -2223,10 +2316,11 @@ class Page
|
||||
continue;
|
||||
}
|
||||
foreach ($items as $item) {
|
||||
$item = rawurldecode($item);
|
||||
if (empty($page->taxonomy[$taxonomy]) || !in_array(htmlspecialchars_decode($item,
|
||||
ENT_QUOTES), $page->taxonomy[$taxonomy])
|
||||
) {
|
||||
$collection->remove();
|
||||
$collection->remove($page->path());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2245,7 +2339,16 @@ class Page
|
||||
$by = isset($params['order']['by']) ? $params['order']['by'] : 'default';
|
||||
$dir = isset($params['order']['dir']) ? $params['order']['dir'] : 'asc';
|
||||
$custom = isset($params['order']['custom']) ? $params['order']['custom'] : null;
|
||||
$collection->order($by, $dir, $custom);
|
||||
$sort_flags = isset($params['order']['sort_flags']) ? $params['order']['sort_flags'] : null;
|
||||
|
||||
if (is_array($sort_flags)) {
|
||||
$sort_flags = array_map('constant', $sort_flags); //transform strings to constant value
|
||||
$sort_flags = array_reduce($sort_flags, function ($a, $b) {
|
||||
return $a | $b;
|
||||
}, 0); //merge constant values using bit or
|
||||
}
|
||||
|
||||
$collection->order($by, $dir, $custom, $sort_flags);
|
||||
}
|
||||
|
||||
/** @var Grav $grav */
|
||||
@@ -2300,11 +2403,6 @@ class Page
|
||||
return new Collection($result);
|
||||
}
|
||||
|
||||
// We only evaluate commands which start with @
|
||||
if (empty($cmd) || $cmd[0] != '@') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
@@ -2315,6 +2413,7 @@ class Page
|
||||
$results = new Collection();
|
||||
|
||||
switch ($current) {
|
||||
case 'self@':
|
||||
case '@self':
|
||||
if (!empty($parts)) {
|
||||
switch ($parts[0]) {
|
||||
@@ -2337,6 +2436,9 @@ class Page
|
||||
$results = $collection->addPage($this->parent());
|
||||
break;
|
||||
case 'siblings':
|
||||
if (!$this->parent()) {
|
||||
return new Collection();
|
||||
}
|
||||
$results = $this->parent()->children()->remove($this->path());
|
||||
break;
|
||||
case 'descendants':
|
||||
@@ -2348,6 +2450,7 @@ class Page
|
||||
$results = $results->published();
|
||||
break;
|
||||
|
||||
case 'page@':
|
||||
case '@page':
|
||||
$page = null;
|
||||
|
||||
@@ -2363,27 +2466,36 @@ class Page
|
||||
// Handle a @page.descendants
|
||||
if (!empty($parts)) {
|
||||
switch ($parts[0]) {
|
||||
case 'modular':
|
||||
$results = new Collection();
|
||||
foreach ($page->children() as $child) {
|
||||
$results = $results->addPage($child);
|
||||
}
|
||||
$results->modular();
|
||||
break;
|
||||
case 'page':
|
||||
case 'self':
|
||||
$results = new Collection();
|
||||
$results = $results->addPage($page);
|
||||
$results = $results->addPage($page)->nonModular();
|
||||
break;
|
||||
|
||||
case 'descendants':
|
||||
$results = $pages->all($page)->remove($page->path());
|
||||
$results = $pages->all($page)->remove($page->path())->nonModular();
|
||||
break;
|
||||
|
||||
case 'children':
|
||||
$results = $page->children();
|
||||
$results = $page->children()->nonModular();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$results = $page->children();
|
||||
$results = $page->children()->nonModular();
|
||||
}
|
||||
|
||||
$results = $results->nonModular()->published();
|
||||
$results = $results->published();
|
||||
|
||||
break;
|
||||
|
||||
case 'root@':
|
||||
case '@root':
|
||||
if (!empty($parts) && $parts[0] == 'descendants') {
|
||||
$results = $pages->all($pages->root())->nonModular()->published();
|
||||
@@ -2392,7 +2504,7 @@ class Page
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'taxonomy@':
|
||||
case '@taxonomy':
|
||||
// Gets a collection of pages by using one of the following formats:
|
||||
// @taxonomy.category: blog
|
||||
@@ -2545,7 +2657,7 @@ class Page
|
||||
|
||||
protected function setPublishState()
|
||||
{
|
||||
// Handle publishing dates if no explict published option set
|
||||
// Handle publishing dates if no explicit published option set
|
||||
if (Grav::instance()['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()) {
|
||||
|
||||
@@ -21,6 +21,7 @@ use Grav\Plugin\Admin;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Whoops\Exception\ErrorException;
|
||||
use Collator as Collator;
|
||||
|
||||
class Pages
|
||||
{
|
||||
@@ -197,7 +198,7 @@ class Pages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sort(Page $page, $order_by = null, $order_dir = null)
|
||||
public function sort(Page $page, $order_by = null, $order_dir = null, $sort_flags = null)
|
||||
{
|
||||
if ($order_by === null) {
|
||||
$order_by = $page->orderBy();
|
||||
@@ -214,7 +215,7 @@ class Pages
|
||||
}
|
||||
|
||||
if (!isset($this->sort[$path][$order_by])) {
|
||||
$this->buildSort($path, $children, $order_by, $page->orderManual());
|
||||
$this->buildSort($path, $children, $order_by, $page->orderManual(), $sort_flags);
|
||||
}
|
||||
|
||||
$sort = $this->sort[$path][$order_by];
|
||||
@@ -235,7 +236,7 @@ class Pages
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null)
|
||||
public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null, $sort_flags = null)
|
||||
{
|
||||
$items = $collection->toArray();
|
||||
if (!$items) {
|
||||
@@ -244,7 +245,7 @@ class Pages
|
||||
|
||||
$lookup = md5(json_encode($items) . json_encode($orderManual) . $orderBy . $orderDir);
|
||||
if (!isset($this->sort[$lookup][$orderBy])) {
|
||||
$this->buildSort($lookup, $items, $orderBy, $orderManual);
|
||||
$this->buildSort($lookup, $items, $orderBy, $orderManual, $sort_flags);
|
||||
}
|
||||
|
||||
$sort = $this->sort[$lookup][$orderBy];
|
||||
@@ -342,13 +343,19 @@ class Pages
|
||||
$page = $this->dispatch($route, $all);
|
||||
} else {
|
||||
// Try Regex style redirects
|
||||
$source_url = $url;
|
||||
$extension = $this->grav['uri']->extension();
|
||||
if (isset($extension)) {
|
||||
$source_url.= '.' . $extension;
|
||||
}
|
||||
|
||||
$site_redirects = $config->get("site.redirects");
|
||||
if (is_array($site_redirects)) {
|
||||
foreach ((array)$site_redirects as $pattern => $replace) {
|
||||
$pattern = '#' . $pattern . '#';
|
||||
try {
|
||||
$found = preg_replace($pattern, $replace, $url);
|
||||
if ($found != $url) {
|
||||
$found = preg_replace($pattern, $replace, $source_url);
|
||||
if ($found != $source_url) {
|
||||
$this->grav->redirectLangSafe($found);
|
||||
}
|
||||
} catch (ErrorException $e) {
|
||||
@@ -363,8 +370,8 @@ class Pages
|
||||
foreach ((array)$site_routes as $pattern => $replace) {
|
||||
$pattern = '#' . $pattern . '#';
|
||||
try {
|
||||
$found = preg_replace($pattern, $replace, $url);
|
||||
if ($found != $url) {
|
||||
$found = preg_replace($pattern, $replace, $source_url);
|
||||
if ($found != $source_url) {
|
||||
$page = $this->dispatch($found, $all);
|
||||
}
|
||||
} catch (ErrorException $e) {
|
||||
@@ -564,17 +571,21 @@ class Pages
|
||||
*/
|
||||
public static function pageTypes()
|
||||
{
|
||||
/** @var Admin $admin */
|
||||
$admin = Grav::instance()['admin'];
|
||||
if (isset(Grav::instance()['admin'])) {
|
||||
/** @var Admin $admin */
|
||||
$admin = Grav::instance()['admin'];
|
||||
|
||||
/** @var Page $page */
|
||||
$page = $admin->getPage($admin->route);
|
||||
/** @var Page $page */
|
||||
$page = $admin->getPage($admin->route);
|
||||
|
||||
if ($page && $page->modular()) {
|
||||
return static::modularTypes();
|
||||
if ($page && $page->modular()) {
|
||||
return static::modularTypes();
|
||||
}
|
||||
|
||||
return static::types();
|
||||
}
|
||||
|
||||
return static::types();
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -647,11 +658,12 @@ class Pages
|
||||
|
||||
$parents = $pages->getList(null, 0, $rawRoutes);
|
||||
|
||||
/** @var Admin $admin */
|
||||
$admin = $grav['admin'];
|
||||
if (isset($grav['admin'])) {
|
||||
// Remove current route from parents
|
||||
|
||||
/** @var Admin $admin */
|
||||
$admin = $grav['admin'];
|
||||
|
||||
// Remove current route from parents
|
||||
if (isset($admin)) {
|
||||
$page = $admin->getPage($admin->route);
|
||||
$page_route = $page->route();
|
||||
if (isset($parents[$page_route])) {
|
||||
@@ -745,16 +757,19 @@ class Pages
|
||||
switch (strtolower($config->get('system.cache.check.method', 'file'))) {
|
||||
case 'none':
|
||||
case 'off':
|
||||
$last_modified = 0;
|
||||
$hash = 0;
|
||||
break;
|
||||
case 'folder':
|
||||
$last_modified = Folder::lastModifiedFolder($pages_dir);
|
||||
$hash = Folder::lastModifiedFolder($pages_dir);
|
||||
break;
|
||||
case 'hash':
|
||||
$hash = Folder::hashAllFiles($pages_dir);
|
||||
break;
|
||||
default:
|
||||
$last_modified = Folder::lastModifiedFile($pages_dir);
|
||||
$hash = Folder::lastModifiedFile($pages_dir);
|
||||
}
|
||||
|
||||
$page_cache_id = md5($pages_dir . $last_modified . $language->getActive() . $config->checksum());
|
||||
$page_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum());
|
||||
|
||||
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
|
||||
if (!$this->instances) {
|
||||
@@ -846,14 +861,22 @@ class Pages
|
||||
}
|
||||
|
||||
$content_exists = false;
|
||||
$pages_found = glob($directory . '/*' . CONTENT_EXT);
|
||||
$pages_found = new \GlobIterator($directory . '/*' . CONTENT_EXT);
|
||||
$page_found = null;
|
||||
|
||||
$page_extension = '';
|
||||
|
||||
if ($pages_found) {
|
||||
if ($pages_found && count($pages_found) > 0) {
|
||||
|
||||
$page_extensions = $language->getFallbackPageExtensions();
|
||||
|
||||
foreach ($page_extensions as $extension) {
|
||||
foreach ($pages_found as $found) {
|
||||
if (preg_match('/^.*\/[0-9A-Za-z\-\_]+(' . $extension . ')$/', $found)) {
|
||||
if ($found->isDir()) {
|
||||
continue;
|
||||
}
|
||||
$regex = '/^[^\.]*' . preg_quote($extension) . '$/';
|
||||
if (preg_match($regex, $found->getFilename())) {
|
||||
$page_found = $found;
|
||||
$page_extension = $extension;
|
||||
break 2;
|
||||
@@ -863,8 +886,7 @@ class Pages
|
||||
}
|
||||
|
||||
if ($parent && !empty($page_found)) {
|
||||
$file = new \SplFileInfo($page_found);
|
||||
$page->init($file, $page_extension);
|
||||
$page->init($page_found, $page_extension);
|
||||
|
||||
$content_exists = true;
|
||||
|
||||
@@ -895,6 +917,12 @@ class Pages
|
||||
$last_modified = $modified;
|
||||
}
|
||||
} elseif ($file->isDir() && !in_array($file->getFilename(), $this->ignore_folders)) {
|
||||
|
||||
// if folder contains separator, continue
|
||||
if (Utils::contains($file->getFilename(), $config->get('system.param_sep', ':'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$page->path()) {
|
||||
$page->path($file->getPath());
|
||||
}
|
||||
@@ -1002,12 +1030,11 @@ class Pages
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function buildSort($path, array $pages, $order_by = 'default', $manual = null)
|
||||
protected function buildSort($path, array $pages, $order_by = 'default', $manual = null, $sort_flags = null)
|
||||
{
|
||||
$list = [];
|
||||
$header_default = null;
|
||||
$header_query = null;
|
||||
$sort_flags = SORT_NATURAL | SORT_FLAG_CASE;
|
||||
|
||||
// do this header query work only once
|
||||
if (strpos($order_by, 'header.') === 0) {
|
||||
@@ -1035,6 +1062,14 @@ class Pages
|
||||
$list[$key] = $child->modified();
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'publish_date':
|
||||
$list[$key] = $child->publishDate();
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'unpublish_date':
|
||||
$list[$key] = $child->unpublishDate();
|
||||
$sort_flags = SORT_REGULAR;
|
||||
break;
|
||||
case 'slug':
|
||||
$list[$key] = $child->slug();
|
||||
break;
|
||||
@@ -1049,24 +1084,33 @@ class Pages
|
||||
} else {
|
||||
$list[$key] = $header_default ?: $key;
|
||||
}
|
||||
$sort_flags = SORT_REGULAR;
|
||||
$sort_flags = $sort_flags ?: SORT_REGULAR;
|
||||
break;
|
||||
case 'manual':
|
||||
case 'default':
|
||||
default:
|
||||
$list[$key] = $key;
|
||||
$sort_flags = SORT_REGULAR;
|
||||
$sort_flags = $sort_flags ?: SORT_REGULAR;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$sort_flags) {
|
||||
$sort_flags = SORT_NATURAL | SORT_FLAG_CASE;
|
||||
}
|
||||
|
||||
// handle special case when order_by is random
|
||||
if ($order_by == 'random') {
|
||||
$list = $this->arrayShuffle($list);
|
||||
} else {
|
||||
// else just sort the list according to specified key
|
||||
if (extension_loaded('intl')) {
|
||||
$col = new \Collator(setlocale(LC_COLLATE, 0)); //`setlocale` with a 0 param returns the current locale set
|
||||
$col->asort($list, $sort_flags);
|
||||
$locale = setlocale(LC_COLLATE, 0); //`setlocale` with a 0 param returns the current locale set
|
||||
$col = Collator::create($locale);
|
||||
if ($col) {
|
||||
$col->asort($list, $sort_flags);
|
||||
} else {
|
||||
asort($list, $sort_flags);
|
||||
}
|
||||
} else {
|
||||
asort($list, $sort_flags);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
|
||||
// Register default by default.
|
||||
$this->register('default');
|
||||
|
||||
$this->register('external');
|
||||
}
|
||||
|
||||
foreach ($this->findBlueprints($uri) as $type => $blueprint) {
|
||||
@@ -80,8 +82,10 @@ class Types implements \ArrayAccess, \Iterator, \Countable
|
||||
}
|
||||
|
||||
$modular_uri = rtrim($uri, '/') . '/modular';
|
||||
foreach (Folder::all($modular_uri, $options) as $type) {
|
||||
$this->register('modular/' . $type);
|
||||
if (is_dir($modular_uri)) {
|
||||
foreach (Folder::all($modular_uri, $options) as $type) {
|
||||
$this->register('modular/' . $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this is running under the admin
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAdmin()
|
||||
@@ -105,6 +107,27 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
|
||||
return Utils::isAdminPlugin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this route is in Admin and active for the plugin
|
||||
*
|
||||
* @param $plugin_route
|
||||
* @return bool
|
||||
*/
|
||||
protected function isPluginActiveAdmin($plugin_route)
|
||||
{
|
||||
$should_run = false;
|
||||
|
||||
$uri = $this->grav['uri'];
|
||||
|
||||
if (strpos($uri->path(), $this->config->get('plugins.admin.route') . '/' . $plugin_route) === false) {
|
||||
$should_run = false;
|
||||
} elseif (isset($uri->paths()[1]) && $uri->paths()[1] == $plugin_route) {
|
||||
$should_run = true;
|
||||
}
|
||||
|
||||
return $should_run;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $events
|
||||
*/
|
||||
@@ -223,21 +246,23 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
|
||||
/**
|
||||
* Merge global and page configurations.
|
||||
*
|
||||
* @param Page $page The page to merge the configurations with the
|
||||
* @param Page $page The page to merge the configurations with the
|
||||
* plugin settings.
|
||||
* @param bool $deep Should you use deep or shallow merging
|
||||
* @param array $params Array of additional configuration options to
|
||||
* @param mixed $deep false = shallow|true = recursive|merge = recursive+unique
|
||||
* @param array $params Array of additional configuration options to
|
||||
* merge with the plugin settings.
|
||||
* @param string $type Is this 'plugins' or 'themes'
|
||||
*
|
||||
* @return \Grav\Common\Data\Data
|
||||
* @return Data
|
||||
*/
|
||||
protected function mergeConfig(Page $page, $deep = false, $params = [])
|
||||
protected function mergeConfig(Page $page, $deep = false, $params = [], $type = 'plugins')
|
||||
{
|
||||
$class_name = $this->name;
|
||||
$class_name_merged = $class_name . '.merged';
|
||||
$defaults = $this->config->get('plugins.' . $class_name, []);
|
||||
$defaults = $this->config->get($type . '.' . $class_name, []);
|
||||
$page_header = $page->header();
|
||||
$header = [];
|
||||
|
||||
if (!isset($page_header->$class_name_merged) && isset($page_header->$class_name)) {
|
||||
// Get default plugin configurations and retrieve page header configuration
|
||||
$config = $page_header->$class_name;
|
||||
@@ -246,11 +271,8 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
|
||||
$config = ['enabled' => $config];
|
||||
}
|
||||
// Merge page header settings using deep or shallow merging technique
|
||||
if ($deep) {
|
||||
$header = array_replace_recursive($defaults, $config);
|
||||
} else {
|
||||
$header = array_merge($defaults, $config);
|
||||
}
|
||||
$header = $this->mergeArrays($deep, $defaults, $config);
|
||||
|
||||
// Create new config object and set it on the page object so it's cached for next time
|
||||
$page->modifyHeader($class_name_merged, new Data($header));
|
||||
} else if (isset($page_header->$class_name_merged)) {
|
||||
@@ -261,16 +283,31 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
|
||||
$header = $defaults;
|
||||
}
|
||||
// Merge additional parameter with configuration options
|
||||
if ($deep) {
|
||||
$header = array_replace_recursive($header, $params);
|
||||
} else {
|
||||
$header = array_merge($header, $params);
|
||||
}
|
||||
$header = $this->mergeArrays($deep, $header, $params);
|
||||
|
||||
// Return configurations as a new data config class
|
||||
return new Data($header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge arrays based on deepness
|
||||
*
|
||||
* @param bool $deep
|
||||
* @param $array1
|
||||
* @param $array2
|
||||
* @return array|mixed
|
||||
*/
|
||||
private function mergeArrays($deep = false, $array1, $array2)
|
||||
{
|
||||
if ($deep == 'merge') {
|
||||
return Utils::arrayMergeRecursiveUnique($array1, $array2);
|
||||
} elseif ($deep == true) {
|
||||
return array_replace_recursive($array1, $array2);
|
||||
} else {
|
||||
return array_merge($array1, $array2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists to disk the plugin parameters currently stored in the Grav Config object
|
||||
*
|
||||
|
||||
@@ -8,15 +8,38 @@
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
class PagesProcessor extends ProcessorBase implements ProcessorInterface {
|
||||
use Grav\Common\Page\Page;
|
||||
|
||||
class PagesProcessor extends ProcessorBase implements ProcessorInterface
|
||||
{
|
||||
|
||||
public $id = 'pages';
|
||||
public $title = 'Pages';
|
||||
|
||||
public function process() {
|
||||
$this->container['pages']->init();
|
||||
$this->container->fireEvent('onPagesInitialized');
|
||||
$this->container->fireEvent('onPageInitialized');
|
||||
public function process()
|
||||
{
|
||||
// Dump Cache state
|
||||
$this->container['debugger']->addMessage($this->container['cache']->getCacheStatus());
|
||||
|
||||
$this->container['pages']->init();
|
||||
$this->container->fireEvent('onPagesInitialized');
|
||||
$this->container->fireEvent('onPageInitialized');
|
||||
|
||||
/** @var Page $page */
|
||||
$page = $this->container['page'];
|
||||
|
||||
if (!$page->routable()) {
|
||||
// If no page found, fire event
|
||||
$event = $this->container->fireEvent('onPageNotFound');
|
||||
|
||||
if (isset($event->page)) {
|
||||
unset ($this->container['page']);
|
||||
$this->container['page'] = $event->page;
|
||||
} else {
|
||||
throw new \RuntimeException('Page Not Found', 404);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
30
system/src/Grav/Common/Service/MessagesServiceProvider.php
Normal file
30
system/src/Grav/Common/Service/MessagesServiceProvider.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.Service
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use RocketTheme\Toolbox\Session\Message;
|
||||
|
||||
class MessagesServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container)
|
||||
{
|
||||
// Define session message service.
|
||||
$container['messages'] = function ($c) {
|
||||
$session = $c['session'];
|
||||
|
||||
if (!isset($session->messages)) {
|
||||
$session->messages = new Message;
|
||||
}
|
||||
|
||||
return $session->messages;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,7 @@ class OutputServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container) {
|
||||
$container['output'] = function ($c) {
|
||||
/** @var Grav $c */
|
||||
return $c['twig']->processSite($c['uri']->extension());
|
||||
return $c['twig']->processSite($c['page']->templateFormat());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Grav\Common\Page\Page;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
|
||||
@@ -33,6 +34,12 @@ class PageServiceProvider implements ServiceProviderInterface
|
||||
|
||||
// Redirection tests
|
||||
if ($page) {
|
||||
if ($c['config']->get('system.force_ssl')) {
|
||||
if (!isset($_SERVER['HTTPS']) || $_SERVER["HTTPS"] != "on") {
|
||||
$url = "https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
|
||||
$c->redirect($url);
|
||||
}
|
||||
}
|
||||
|
||||
$url = $page->route();
|
||||
|
||||
@@ -71,14 +78,13 @@ class PageServiceProvider implements ServiceProviderInterface
|
||||
// Try fallback URL stuff...
|
||||
$c->fallbackUrl($path);
|
||||
|
||||
// If no page found, fire event
|
||||
$event = $c->fireEvent('onPageNotFound');
|
||||
|
||||
if (isset($event->page)) {
|
||||
$page = $event->page;
|
||||
} else {
|
||||
throw new \RuntimeException('Page Not Found', 404);
|
||||
if (!$page) {
|
||||
$path = $c['locator']->findResource('system://pages/notfound.md');
|
||||
$page = new Page();
|
||||
$page->init(new \SplFileInfo($path));
|
||||
$page->routable(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $page;
|
||||
|
||||
@@ -38,7 +38,10 @@ class Session extends BaseSession
|
||||
$base_url = $uri->rootUrl(false);
|
||||
|
||||
$session_timeout = $config->get('system.session.timeout', 1800);
|
||||
$session_path = $config->get('system.session.path', '/' . ltrim($base_url, '/'));
|
||||
$session_path = $config->get('system.session.path');
|
||||
if (!$session_path) {
|
||||
$session_path = '/' . ltrim($base_url, '/');
|
||||
}
|
||||
|
||||
// Activate admin if we're inside the admin path.
|
||||
if ($config->get('plugins.admin.enabled')) {
|
||||
@@ -56,18 +59,30 @@ class Session extends BaseSession
|
||||
}
|
||||
|
||||
if ($config->get('system.session.enabled') || $is_admin) {
|
||||
// Define session service.
|
||||
parent::__construct($session_timeout, $session_path);
|
||||
|
||||
$domain = $uri->host();
|
||||
if ($domain === 'localhost') {
|
||||
$domain = '';
|
||||
}
|
||||
|
||||
// Fix for HUGE session timeouts
|
||||
if ($session_timeout > 99999999999) {
|
||||
$session_timeout = 9999999999;
|
||||
}
|
||||
|
||||
// Define session service.
|
||||
parent::__construct($session_timeout, $session_path, $domain);
|
||||
|
||||
$secure = $config->get('system.session.secure', false);
|
||||
$httponly = $config->get('system.session.httponly', true);
|
||||
|
||||
$unique_identifier = GRAV_ROOT;
|
||||
$this->setName($config->get('system.session.name', 'grav_site') . '-' . substr(md5($unique_identifier), 0, 7) . ($is_admin ? '-admin' : ''));
|
||||
$inflector = new Inflector();
|
||||
$session_name = $inflector->hyphenize($config->get('system.session.name', 'grav_site')) . '-' . substr(md5($unique_identifier), 0, 7);
|
||||
$split_session = $config->get('system.session.split', true);
|
||||
if ($is_admin && $split_session) {
|
||||
$session_name .= '-admin';
|
||||
}
|
||||
$this->setName($session_name);
|
||||
$this->start();
|
||||
setcookie(session_name(), session_id(), time() + $session_timeout, $session_path, $domain, $secure, $httponly);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,8 @@ class Taxonomy
|
||||
foreach ((array)$items as $item) {
|
||||
if (isset($this->taxonomy_map[$taxonomy][$item])) {
|
||||
$matches[] = $this->taxonomy_map[$taxonomy][$item];
|
||||
} else {
|
||||
$matches[] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,4 +128,20 @@ class Taxonomy
|
||||
|
||||
return $this->taxonomy_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets item keys per taxonomy
|
||||
*
|
||||
* @param string $taxonomy taxonomy name
|
||||
*
|
||||
* @return array keys of this taxonomy
|
||||
*/
|
||||
public function getTaxonomyItemKeys($taxonomy) {
|
||||
if (isset($this->taxonomy_map[$taxonomy])) {
|
||||
|
||||
$results = array_keys($this->taxonomy_map[$taxonomy]);
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Config\Config;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
|
||||
@@ -59,6 +60,13 @@ class Theme extends Plugin
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the mergeConfig method to work for themes
|
||||
*/
|
||||
protected function mergeConfig(Page $page, $deep = 'merge', $params = [], $type = 'themes') {
|
||||
return parent::mergeConfig($page, $deep, $params, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simpler getter for the theme blueprint
|
||||
*
|
||||
|
||||
@@ -83,9 +83,9 @@ class Twig
|
||||
|
||||
$active_language = $language->getActive();
|
||||
|
||||
$language_append = '';
|
||||
$path_append = rtrim($this->grav['pages']->base(), '/');
|
||||
if ($language->getDefault() != $active_language || $config->get('system.languages.include_default_lang') === true) {
|
||||
$language_append = $active_language ? '/' . $active_language : '';
|
||||
$path_append .= $active_language ? '/' . $active_language : '';
|
||||
}
|
||||
|
||||
// handle language templates if available
|
||||
@@ -152,21 +152,28 @@ class Twig
|
||||
|
||||
$this->grav->fireEvent('onTwigExtensions');
|
||||
|
||||
$base_url = $this->grav['base_url'] . $path_append;
|
||||
|
||||
// Set some standard variables for twig
|
||||
$this->twig_vars = $this->twig_vars + [
|
||||
'config' => $config,
|
||||
'uri' => $this->grav['uri'],
|
||||
'base_dir' => rtrim(ROOT_DIR, '/'),
|
||||
'base_url' => $this->grav['base_url'] . $language_append,
|
||||
'base_url_simple' => $this->grav['base_url'],
|
||||
'base_url_absolute' => $this->grav['base_url_absolute'] . $language_append,
|
||||
'base_url_relative' => $this->grav['base_url_relative'] . $language_append,
|
||||
'theme_dir' => $locator->findResource('theme://'),
|
||||
'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false),
|
||||
'system' => $config->get('system'),
|
||||
'theme' => $config->get('theme'),
|
||||
'site' => $config->get('site'),
|
||||
'uri' => $this->grav['uri'],
|
||||
'assets' => $this->grav['assets'],
|
||||
'taxonomy' => $this->grav['taxonomy'],
|
||||
'browser' => $this->grav['browser'],
|
||||
'base_dir' => rtrim(ROOT_DIR, '/'),
|
||||
'base_url' => $base_url,
|
||||
'base_url_simple' => $this->grav['base_url'],
|
||||
'base_url_absolute' => $this->grav['base_url_absolute'] . $path_append,
|
||||
'base_url_relative' => $this->grav['base_url_relative'] . $path_append,
|
||||
'home_url' => $base_url == '' ? '/' : $base_url,
|
||||
'theme_dir' => $locator->findResource('theme://'),
|
||||
'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false),
|
||||
'html_lang' => $this->grav['language']->getActive() ?: $config->get('site.default_lang', 'en'),
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -320,6 +327,7 @@ class Twig
|
||||
|
||||
$twig_vars = $this->twig_vars;
|
||||
|
||||
$twig_vars['theme'] = $this->grav['config']->get('theme');
|
||||
$twig_vars['pages'] = $pages->root();
|
||||
$twig_vars['page'] = $page;
|
||||
$twig_vars['header'] = $page->header();
|
||||
@@ -343,6 +351,7 @@ class Twig
|
||||
// Try html version of this template if initial template was NOT html
|
||||
if ($ext != '.html' . TWIG_EXT) {
|
||||
try {
|
||||
$page->templateFormat('html');
|
||||
$output = $this->twig->render($page->template() . '.html' . TWIG_EXT, $twig_vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
throw new \RuntimeException($error_msg, 400, $e);
|
||||
|
||||
@@ -87,6 +87,7 @@ class TwigExtension extends \Twig_Extension
|
||||
new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
|
||||
new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
|
||||
new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']),
|
||||
new \Twig_SimpleFilter('array_unique', 'array_unique'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -115,6 +116,8 @@ class TwigExtension extends \Twig_Extension
|
||||
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
|
||||
new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']),
|
||||
new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
|
||||
new \Twig_SimpleFunction('redirect_me', [$this, 'redirectFunc']),
|
||||
new \Twig_SimpleFunction('range', [$this, 'rangeFunc']),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -142,12 +145,16 @@ class TwigExtension extends \Twig_Extension
|
||||
public function safeEmailFilter($str)
|
||||
{
|
||||
$email = '';
|
||||
$str_len = strlen($str);
|
||||
for ($i = 0; $i < $str_len; $i++) {
|
||||
$email .= "&#" . ord($str[$i]) . ";";
|
||||
for ( $i = 0, $len = strlen( $str ); $i < $len; $i++ ) {
|
||||
$j = rand( 0, 1);
|
||||
if ( $j == 0 ) {
|
||||
$email .= '&#' . ord( $str[$i] ) . ';';
|
||||
} elseif ( $j == 1 ) {
|
||||
$email .= $str[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return $email;
|
||||
return str_replace( '@', '@', $email );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -729,11 +736,15 @@ class TwigExtension extends \Twig_Extension
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize an action. Returns true if the user is logged in and has the right to execute $action.
|
||||
* Authorize an action. Returns true if the user is logged in and
|
||||
* has the right to execute $action.
|
||||
*
|
||||
* @param string $action
|
||||
*
|
||||
* @return bool
|
||||
* @param string|array $action An action or a list of actions. Each
|
||||
* entry can be a string like 'group.action'
|
||||
* or without dot notation an associative
|
||||
* array.
|
||||
* @return bool Returns TRUE if the user is authorized to
|
||||
* perform the action, FALSE otherwise.
|
||||
*/
|
||||
public function authorize($action)
|
||||
{
|
||||
@@ -741,11 +752,14 @@ class TwigExtension extends \Twig_Extension
|
||||
return false;
|
||||
}
|
||||
|
||||
$action = (array)$action;
|
||||
|
||||
foreach ($action as $a) {
|
||||
if ($this->grav['user']->authorize($a)) {
|
||||
return true;
|
||||
$action = (array) $action;
|
||||
foreach ($action as $key => $perms) {
|
||||
$prefix = is_int($key) ? '' : $key . '.';
|
||||
$perms = $prefix ? (array) $perms : [$perms => true];
|
||||
foreach ($perms as $action => $authenticated) {
|
||||
if ($this->grav['user']->authorize($prefix . $action)) {
|
||||
return $authenticated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,4 +823,30 @@ class TwigExtension extends \Twig_Extension
|
||||
{
|
||||
return preg_replace($pattern, $replace, $subject, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* redirect browser from twig
|
||||
*
|
||||
* @param string $url the url to redirect to
|
||||
* @param int $statusCode statusCode, default 303
|
||||
*/
|
||||
public function redirectFunc($url, $statusCode = 303)
|
||||
{
|
||||
header('Location: ' . $url, true, $statusCode);
|
||||
die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array containing a range of elements, optionally stepped
|
||||
*
|
||||
* @param int $start Minimum number, default 0
|
||||
* @param int $end Maximum number, default `getrandmax()`
|
||||
* @param int $step Increment between elements in the sequence, default 1
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rangeFunc($start = 0, $end = 100, $step = 1)
|
||||
{
|
||||
return range($start, $end, $step);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Page;
|
||||
|
||||
class Uri
|
||||
@@ -207,6 +208,7 @@ class Uri
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
$uri_bits = Uri::parseUrl($url);
|
||||
@@ -275,15 +277,12 @@ class Uri
|
||||
}
|
||||
|
||||
// Set some defaults
|
||||
$this->root = $this->base . $this->root_path;
|
||||
$this->root = $grav['config']->get('system.custom_base_url') ?: $this->base . $this->root_path;
|
||||
$this->url = $this->base . $this->uri;
|
||||
|
||||
// get any params and remove them
|
||||
$uri = str_replace($this->root, '', $this->url);
|
||||
|
||||
// remove double slashes
|
||||
$uri = preg_replace('#/{2,}#', '/', $uri);
|
||||
|
||||
// remove the setup.php based base if set:
|
||||
$setup_base = $grav['pages']->base();
|
||||
if ($setup_base) {
|
||||
@@ -292,7 +291,7 @@ class Uri
|
||||
|
||||
// If configured to, redirect trailing slash URI's with a 301 redirect
|
||||
if ($config->get('system.pages.redirect_trailing_slash', false) && $uri != '/' && Utils::endsWith($uri, '/')) {
|
||||
$grav->redirect(rtrim($uri, '/'), 301);
|
||||
$grav->redirect(str_replace($this->root, '', rtrim($uri, '/')), 301);
|
||||
}
|
||||
|
||||
// process params
|
||||
@@ -305,11 +304,10 @@ class Uri
|
||||
$bits = parse_url($uri);
|
||||
|
||||
// process query string
|
||||
if (isset($bits['query']) && isset($bits['path'])) {
|
||||
if (isset($bits['query'])) {
|
||||
if (!$this->query) {
|
||||
$this->query = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
|
||||
}
|
||||
$uri = $bits['path'];
|
||||
}
|
||||
|
||||
//process fragment
|
||||
@@ -317,8 +315,11 @@ class Uri
|
||||
$this->fragment = $bits['fragment'];
|
||||
}
|
||||
|
||||
// Get the path. If there's no path, make sure pathinfo() still returns dirname variable
|
||||
$path = isset($bits['path']) ? $bits['path'] : '/';
|
||||
|
||||
// remove the extension if there is one set
|
||||
$parts = pathinfo($uri);
|
||||
$parts = pathinfo($path);
|
||||
|
||||
// set the original basename
|
||||
$this->basename = $parts['basename'];
|
||||
@@ -332,19 +333,19 @@ class Uri
|
||||
|
||||
// Strip the file extension for valid page types
|
||||
if (preg_match('/\.(' . $valid_page_types . ')$/', $parts['basename'])) {
|
||||
$uri = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS) . '/' . $parts['filename'];
|
||||
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS) . '/' . $parts['filename'];
|
||||
}
|
||||
|
||||
// set the new url
|
||||
$this->url = $this->root . $uri;
|
||||
$this->path = $uri;
|
||||
$this->url = $this->root . $path;
|
||||
$this->path = $path;
|
||||
$this->content_path = trim(str_replace($this->base, '', $this->path), '/');
|
||||
if ($this->content_path != '') {
|
||||
$this->paths = explode('/', $this->content_path);
|
||||
}
|
||||
|
||||
// Set some Grav stuff
|
||||
$grav['base_url_absolute'] = $this->rootUrl(true);
|
||||
$grav['base_url_absolute'] = $grav['config']->get('system.custom_base_url') ?: $this->rootUrl(true);
|
||||
$grav['base_url_relative'] = $this->rootUrl(false);
|
||||
$grav['base_url'] = $grav['config']->get('system.absolute_urls') ? $grav['base_url_absolute'] : $grav['base_url_relative'];
|
||||
}
|
||||
@@ -767,14 +768,14 @@ class Uri
|
||||
/**
|
||||
* Converts links from absolute '/' or relative (../..) to a Grav friendly format
|
||||
*
|
||||
* @param Page $page the current page to use as reference
|
||||
* @param Page $page the current page to use as reference
|
||||
* @param string $url the URL as it was written in the markdown
|
||||
* @param string $type the type of URL, image | link
|
||||
* @param bool $absolute if null, will use system default, if true will use absolute links internally
|
||||
*
|
||||
* @param string $type the type of URL, image | link
|
||||
* @param bool $absolute if null, will use system default, if true will use absolute links internally
|
||||
* @param bool $route_only only return the route, not full URL path
|
||||
* @return string the more friendly formatted url
|
||||
*/
|
||||
public static function convertUrl(Page $page, $url, $type = 'link', $absolute = false)
|
||||
public static function convertUrl(Page $page, $url, $type = 'link', $absolute = false, $route_only = false)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
@@ -923,6 +924,10 @@ class Uri
|
||||
$url = $url_path;
|
||||
}
|
||||
|
||||
if ($route_only) {
|
||||
$url = str_replace($base_url, '', $url);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,13 +83,13 @@ class Group extends Data
|
||||
foreach ($fields as $field) {
|
||||
if ($field['type'] == 'text') {
|
||||
$value = $field['name'];
|
||||
if (isset($this->items[$value])) {
|
||||
$config->set("groups.$this->groupname.$value", $this->items[$value]);
|
||||
if (isset($this->items['data'][$value])) {
|
||||
$config->set("groups.$this->groupname.$value", $this->items['data'][$value]);
|
||||
}
|
||||
}
|
||||
if ($field['type'] == 'array') {
|
||||
if ($field['type'] == 'array' || $field['type'] == 'permissions') {
|
||||
$value = $field['name'];
|
||||
$arrayValues = Utils::getDotNotation($this->items, $field['name']);
|
||||
$arrayValues = Utils::getDotNotation($this->items['data'], $field['name']);
|
||||
|
||||
if ($arrayValues) {
|
||||
foreach ($arrayValues as $arrayIndex => $arrayValue) {
|
||||
|
||||
@@ -54,6 +54,42 @@ class User extends Data
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a user by username, email, etc
|
||||
*
|
||||
* @param $query the query to search for
|
||||
* @param array $fields the fields to search
|
||||
* @return User
|
||||
*/
|
||||
public static function find($query, $fields = ['username', 'email'])
|
||||
{
|
||||
$account_dir = Grav::instance()['locator']->findResource('account://');
|
||||
$files = array_diff(scandir($account_dir), ['.', '..']);
|
||||
|
||||
// Try with username first, you never know!
|
||||
if (in_array('username', $fields)) {
|
||||
$user = User::load($query);
|
||||
unset($fields[array_search('username', $fields)]);
|
||||
} else {
|
||||
$user = User::load('');
|
||||
}
|
||||
|
||||
// If not found, try the fields
|
||||
if (!$user->exists()) {
|
||||
foreach ($files as $file) {
|
||||
if (Utils::endsWith($file, YAML_EXT)) {
|
||||
$find_user = User::load(trim(pathinfo($file, PATHINFO_FILENAME)));
|
||||
foreach ($fields as $field) {
|
||||
if ($find_user[$field] == $query) {
|
||||
return $find_user;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove user account.
|
||||
*
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
namespace Grav\Common;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Helpers\Truncator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
@@ -111,6 +109,26 @@ abstract class Utils
|
||||
return (object)array_merge((array)$obj1, (array)$obj2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive Merge with uniqueness
|
||||
*
|
||||
* @param $array1
|
||||
* @param $array2
|
||||
* @return mixed
|
||||
*/
|
||||
public static function arrayMergeRecursiveUnique($array1, $array2)
|
||||
{
|
||||
if (empty($array1)) return $array2; //optimize the base case
|
||||
|
||||
foreach ($array2 as $key => $value) {
|
||||
if (is_array($value) && is_array(@$array1[$key])) {
|
||||
$value = static::arrayMergeRecursiveUnique($array1[$key], $value);
|
||||
}
|
||||
$array1[$key] = $value;
|
||||
}
|
||||
return $array1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Grav date formats allowed
|
||||
*
|
||||
@@ -183,26 +201,32 @@ abstract class Utils
|
||||
* Truncate HTML by number of characters. not "word-safe"!
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $length
|
||||
* @param int $length in characters
|
||||
* @param string $ellipsis
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function truncateHtml($text, $length = 100)
|
||||
public static function truncateHtml($text, $length = 100, $ellipsis = '...')
|
||||
{
|
||||
return Truncator::truncate($text, $length, ['length_in_chars' => true]);
|
||||
if (mb_strlen($text) <= $length) {
|
||||
return $text;
|
||||
} else {
|
||||
return Truncator::truncateLetters($text, $length, $ellipsis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate HTML by number of characters in a "word-safe" manor.
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $length
|
||||
* @param int $length in words
|
||||
* @param string $ellipsis
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function safeTruncateHtml($text, $length = 100)
|
||||
public static function safeTruncateHtml($text, $length = 25, $ellipsis = '...')
|
||||
{
|
||||
return Truncator::truncate($text, $length, ['length_in_chars' => true, 'word_safe' => true]);
|
||||
return Truncator::truncateWords($text, $length, $ellipsis);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,7 +257,7 @@ abstract class Utils
|
||||
Grav::instance()->fireEvent('onBeforeDownload', new Event(['file' => $file]));
|
||||
|
||||
$file_parts = pathinfo($file);
|
||||
$mimetype = Utils::getMimeType($file_parts['extension']);
|
||||
$mimetype = Utils::getMimeByExtension($file_parts['extension']);
|
||||
$size = filesize($file); // File size
|
||||
|
||||
// clean all buffers
|
||||
@@ -319,22 +343,84 @@ abstract class Utils
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mimetype based on filename
|
||||
* Return the mimetype based on filename extension
|
||||
*
|
||||
* @param string $extension Extension of file (eg "txt")
|
||||
* @param string $default
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMimeType($extension)
|
||||
public static function getMimeByExtension($extension, $default = 'application/octet-stream')
|
||||
{
|
||||
$extension = strtolower($extension);
|
||||
$config = Grav::instance()['config']->get('media.types');
|
||||
|
||||
if (isset($config[$extension])) {
|
||||
return $config[$extension]['mime'];
|
||||
// look for some standard types
|
||||
switch ($extension) {
|
||||
case null:
|
||||
return $default;
|
||||
case 'json':
|
||||
return 'application/json';
|
||||
case 'html':
|
||||
return 'text/html';
|
||||
case 'atom':
|
||||
return 'application/atom+xml';
|
||||
case 'rss':
|
||||
return 'application/rss+xml';
|
||||
case 'xml':
|
||||
return 'application/xml';
|
||||
}
|
||||
|
||||
return 'application/octet-stream';
|
||||
$media_types = Grav::instance()['config']->get('media.types');
|
||||
|
||||
if (isset($media_types[$extension])) {
|
||||
if (isset($media_types[$extension]['mime'])) {
|
||||
return $media_types[$extension]['mime'];
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mimetype based on filename extension
|
||||
*
|
||||
* @param string $mime mime type (eg "text/html")
|
||||
* @param string $default default value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getExtensionByMime($mime, $default = 'html')
|
||||
{
|
||||
$mime = strtolower($mime);
|
||||
|
||||
// look for some standard mime types
|
||||
switch ($mime) {
|
||||
case '*/*':
|
||||
case 'text/*':
|
||||
case 'text/html':
|
||||
return 'html';
|
||||
case 'application/json':
|
||||
return 'json';
|
||||
case 'application/atom+xml':
|
||||
return 'atom';
|
||||
case 'application/rss+xml':
|
||||
return 'rss';
|
||||
case 'application/xml':
|
||||
return 'xml';
|
||||
}
|
||||
|
||||
$media_types = Grav::instance()['config']->get('media.types');
|
||||
|
||||
foreach ($media_types as $extension => $type) {
|
||||
if ($extension == 'defaults') {
|
||||
continue;
|
||||
}
|
||||
if (isset($type['mime']) && $type['mime'] == $mime) {
|
||||
return $extension;
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,6 +518,27 @@ abstract class Utils
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten an array
|
||||
*
|
||||
* @param $array
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayFlatten($array)
|
||||
{
|
||||
$flatten = array();
|
||||
foreach ($array as $key => $inner){
|
||||
if (is_array($inner)) {
|
||||
foreach ($inner as $inner_key => $value) {
|
||||
$flatten[$inner_key] = $value;
|
||||
}
|
||||
} else {
|
||||
$flatten[$key] = $inner;
|
||||
}
|
||||
}
|
||||
return $flatten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed path contains the language code prefix
|
||||
*
|
||||
@@ -690,12 +797,14 @@ abstract class Utils
|
||||
* Set portion of array (passed by reference) for a dot-notation key
|
||||
* and set the value
|
||||
*
|
||||
* @param $array
|
||||
* @param $key
|
||||
* @param $value
|
||||
* @param $array
|
||||
* @param $key
|
||||
* @param $value
|
||||
* @param bool $merge
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function setDotNotation(&$array, $key, $value)
|
||||
public static function setDotNotation(&$array, $key, $value, $merge = false)
|
||||
{
|
||||
if (is_null($key)) return $array = $value;
|
||||
|
||||
@@ -713,8 +822,24 @@ abstract class Utils
|
||||
$array =& $array[$key];
|
||||
}
|
||||
|
||||
$array[array_shift($keys)] = $value;
|
||||
$key = array_shift($keys);
|
||||
|
||||
if (!$merge || !isset($array[$key])) {
|
||||
$array[$key] = $value;
|
||||
} else {
|
||||
$array[$key] = array_merge($array[$key], $value);
|
||||
}
|
||||
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to determine if the current OS is Windows
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isWindows() {
|
||||
return strncasecmp(PHP_OS, 'WIN', 3) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ class CleanCommand extends Command
|
||||
'vendor/ircmaxell/password-compat/version-test.php',
|
||||
'vendor/ircmaxell/password-compat/.travis.yml',
|
||||
'vendor/ircmaxell/password-compat/test',
|
||||
'vendor/league/climate/composer.json',
|
||||
'vendor/matthiasmullie/minify/bin',
|
||||
'vendor/matthiasmullie/minify/composer.json',
|
||||
'vendor/matthiasmullie/minify/CONTRIBUTING.md',
|
||||
@@ -124,6 +125,7 @@ class CleanCommand extends Command
|
||||
'vendor/rockettheme/toolbox/.travis.yml',
|
||||
'vendor/rockettheme/toolbox/composer.json',
|
||||
'vendor/rockettheme/toolbox/phpunit.xml',
|
||||
'vendor/seld/cli-prompt/composer.json',
|
||||
'vendor/symfony/console/composer.json',
|
||||
'vendor/symfony/console/phpunit.xml.dist',
|
||||
'vendor/symfony/console/.gitignore',
|
||||
|
||||
@@ -27,6 +27,7 @@ class ClearCacheCommand extends ConsoleCommand
|
||||
->addOption('assets-only', null, InputOption::VALUE_NONE, 'If set will remove only assets/*')
|
||||
->addOption('images-only', null, InputOption::VALUE_NONE, 'If set will remove only images/*')
|
||||
->addOption('cache-only', null, InputOption::VALUE_NONE, 'If set will remove only cache/*')
|
||||
->addOption('tmp-only', null, InputOption::VALUE_NONE, 'If set will remove only tmp/*')
|
||||
->setHelp('The <info>clear-cache</info> deletes all cache files');
|
||||
}
|
||||
|
||||
@@ -55,6 +56,8 @@ class ClearCacheCommand extends ConsoleCommand
|
||||
$remove = 'images-only';
|
||||
} elseif ($this->input->getOption('cache-only')) {
|
||||
$remove = 'cache-only';
|
||||
} elseif ($this->input->getOption('tmp-only')) {
|
||||
$remove = 'tmp-only';
|
||||
} else {
|
||||
$remove = 'standard';
|
||||
}
|
||||
|
||||
@@ -65,20 +65,15 @@ class InstallCommand extends ConsoleCommand
|
||||
// fix trailing slash
|
||||
$this->destination = rtrim($this->destination, DS) . DS;
|
||||
$this->user_path = $this->destination . USER_PATH;
|
||||
|
||||
if (false === $this->isWindows()) {
|
||||
$local_config_file = exec('eval echo ~/.grav/config');
|
||||
if (file_exists($local_config_file)) {
|
||||
$this->local_config = Yaml::parse($local_config_file);
|
||||
$this->output->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
|
||||
}
|
||||
if ($local_config_file = $this->loadLocalConfig()) {
|
||||
$this->output->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
|
||||
}
|
||||
|
||||
// Look for dependencies file in ROOT and USER dir
|
||||
if (file_exists($this->user_path . $dependencies_file)) {
|
||||
$this->config = Yaml::parse($this->user_path . $dependencies_file);
|
||||
$this->config = Yaml::parse(file_get_contents($this->user_path . $dependencies_file));
|
||||
} elseif (file_exists($this->destination . $dependencies_file)) {
|
||||
$this->config = Yaml::parse($this->destination . $dependencies_file);
|
||||
$this->config = Yaml::parse(file_get_contents($this->destination . $dependencies_file));
|
||||
} else {
|
||||
$this->output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
|
||||
}
|
||||
|
||||
@@ -19,15 +19,16 @@ class SandboxCommand extends ConsoleCommand
|
||||
* @var array
|
||||
*/
|
||||
protected $directories = [
|
||||
'/assets',
|
||||
'/backup',
|
||||
'/cache',
|
||||
'/logs',
|
||||
'/images',
|
||||
'/assets',
|
||||
'/logs',
|
||||
'/tmp',
|
||||
'/user/accounts',
|
||||
'/user/config',
|
||||
'/user/pages',
|
||||
'/user/data',
|
||||
'/user/pages',
|
||||
'/user/plugins',
|
||||
'/user/themes',
|
||||
];
|
||||
@@ -46,7 +47,6 @@ class SandboxCommand extends ConsoleCommand
|
||||
* @var array
|
||||
*/
|
||||
protected $mappings = [
|
||||
'/.editorconfig' => '/.editorconfig',
|
||||
'/.gitignore' => '/.gitignore',
|
||||
'/CHANGELOG.md' => '/CHANGELOG.md',
|
||||
'/LICENSE.txt' => '/LICENSE.txt',
|
||||
@@ -58,7 +58,6 @@ class SandboxCommand extends ConsoleCommand
|
||||
'/system' => '/system',
|
||||
'/vendor' => '/vendor',
|
||||
'/webserver-configs' => '/webserver-configs',
|
||||
'/codeception.yml' => '/codeception.yml',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
trait ConsoleTrait
|
||||
{
|
||||
@@ -113,19 +114,20 @@ trait ConsoleTrait
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if the system is based on windows or not.
|
||||
* Load the local config file
|
||||
*
|
||||
* @return bool
|
||||
* @return mixed string the local config file name. false if local config does not exist
|
||||
*/
|
||||
public function isWindows()
|
||||
public function loadLocalConfig()
|
||||
{
|
||||
$keys = [
|
||||
'CYGWIN_NT-5.1',
|
||||
'WIN32',
|
||||
'WINNT',
|
||||
'Windows'
|
||||
];
|
||||
$home_folder = getenv('HOME') ?: getenv('HOMEDRIVE') . getenv('HOMEPATH');
|
||||
$local_config_file = $home_folder . '/.grav/config';
|
||||
|
||||
return array_key_exists(PHP_OS, $keys);
|
||||
if (file_exists($local_config_file)) {
|
||||
$this->local_config = Yaml::parse(file_get_contents($local_config_file));
|
||||
return $local_config_file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
378
system/src/Grav/Console/Gpm/DirectInstallCommand.php
Normal file
378
system/src/Grav/Console/Gpm/DirectInstallCommand.php
Normal file
@@ -0,0 +1,378 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Console
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Console\Gpm;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\GPM\Installer;
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class DirectInstallCommand extends ConsoleCommand
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName("direct-install")
|
||||
->setAliases(['directinstall'])
|
||||
->addArgument(
|
||||
'package-file',
|
||||
InputArgument::REQUIRED,
|
||||
'The local location or remote URL to an installable package file'
|
||||
)
|
||||
->setDescription("Installs Grav, plugin, or theme directly from a file or a URL")
|
||||
->setHelp('The <info>direct-install</info> command installs Grav, plugin, or theme directly from a file or a URL');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null|void
|
||||
*/
|
||||
protected function serve()
|
||||
{
|
||||
$package_file = $this->input->getArgument('package-file');
|
||||
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion('Are you sure you want to direct-install <cyan>'.$package_file.'</cyan> [y|N] ', false);
|
||||
|
||||
$answer = $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln("exiting...");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
|
||||
$tmp_zip = $tmp_dir . '/Grav-' . uniqid();
|
||||
|
||||
$this->output->writeln("");
|
||||
$this->output->writeln("Preparing to install <cyan>" . $package_file . "</cyan>");
|
||||
|
||||
|
||||
if ($this->isRemote($package_file)) {
|
||||
$zip = $this->downloadPackage($package_file, $tmp_zip);
|
||||
} else {
|
||||
$zip = $this->copyPackage($package_file, $tmp_zip);
|
||||
}
|
||||
|
||||
if (file_exists($zip)) {
|
||||
$tmp_source = $tmp_dir . '/Grav-' . uniqid();
|
||||
|
||||
$this->output->write(" |- Extracting package... ");
|
||||
$extracted = Installer::unZip($zip, $tmp_source);
|
||||
|
||||
if (!$extracted) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Extracting package... <red>failed</red>");
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Extracting package... <green>ok</green>");
|
||||
|
||||
|
||||
$type = $this->getPackageType($extracted);
|
||||
|
||||
if (!$type) {
|
||||
$this->output->writeln(" '- <red>ERROR: Not a valid Grav package</red>");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
$blueprint = $this->getBlueprints($extracted);
|
||||
if ($blueprint) {
|
||||
if (isset($blueprint['dependencies'])) {
|
||||
$depencencies = [];
|
||||
foreach ($blueprint['dependencies'] as $dependency) {
|
||||
if (is_array($dependency) && isset($dependency['name'])) {
|
||||
$depencencies[] = $dependency['name'];
|
||||
} else {
|
||||
$depencencies[] = $dependency;
|
||||
}
|
||||
}
|
||||
$this->output->writeln(" |- Dependencies found... <cyan>[" . implode(',', $depencencies) . "]</cyan>");
|
||||
|
||||
|
||||
|
||||
$question = new ConfirmationQuestion(" | '- Dependencies will not be satisfied. Continue ? [y|N] ", false);
|
||||
$answer = $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln("exiting...");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($type == 'grav') {
|
||||
|
||||
$this->output->write(" |- Checking destination... ");
|
||||
Installer::isValidDestination(GRAV_ROOT . '/system');
|
||||
if (Installer::IS_LINK === Installer::lastErrorCode()) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
|
||||
$this->output->writeln(" '- <red>ERROR: symlinks found...</red> <yellow>" . GRAV_ROOT."</yellow>");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <green>ok</green>");
|
||||
|
||||
$this->output->write(" |- Installing package... ");
|
||||
Installer::install($zip, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true], $extracted);
|
||||
} else {
|
||||
$name = $this->getPackageName($extracted);
|
||||
|
||||
if (!$name) {
|
||||
$this->output->writeln("<red>ERROR: Name could not be determined.</red> Please specify with --name|-n");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
$install_path = $this->getInstallPath($type, $name);
|
||||
$is_update = file_exists($install_path);
|
||||
|
||||
$this->output->write(" |- Checking destination... ");
|
||||
|
||||
Installer::isValidDestination(GRAV_ROOT . DS . $install_path);
|
||||
if (Installer::lastErrorCode() == Installer::IS_LINK) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
|
||||
$this->output->writeln(" '- <red>ERROR: symlink found...</red> <yellow>" . GRAV_ROOT . DS . $install_path . '</yellow>');
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
|
||||
} else {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <green>ok</green>");
|
||||
}
|
||||
|
||||
$this->output->write(" |- Installing package... ");
|
||||
|
||||
Installer::install($zip, GRAV_ROOT, ['install_path' => $install_path, 'theme' => (($type == 'theme')), 'is_update' => $is_update], $extracted);
|
||||
}
|
||||
|
||||
Folder::delete($tmp_source);
|
||||
|
||||
$this->output->write("\x0D");
|
||||
|
||||
if(Installer::lastErrorCode()) {
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$this->output->writeln(" |- Installing package... <green>ok</green>");
|
||||
$this->output->writeln(" '- <green>Success!</green> ");
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->output->writeln(" '- <red>ERROR: ZIP package could not be found</red>");
|
||||
}
|
||||
|
||||
Folder::delete($tmp_zip);
|
||||
|
||||
// clear cache after successful upgrade
|
||||
$this->clearCache();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the install path for a name and a particular type of package
|
||||
*
|
||||
* @param $type
|
||||
* @param $name
|
||||
* @return string
|
||||
*/
|
||||
protected function getInstallPath($type, $name)
|
||||
{
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if ($type == 'theme') {
|
||||
$install_path = $locator->findResource('themes://', false) . DS . $name;
|
||||
} else {
|
||||
$install_path = $locator->findResource('plugins://', false) . DS . $name;
|
||||
}
|
||||
return $install_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to guess the package name from the source files
|
||||
*
|
||||
* @param $source
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function getPackageName($source)
|
||||
{
|
||||
foreach (glob($source . "*.yaml") as $filename) {
|
||||
$name = strtolower(basename($filename, '.yaml'));
|
||||
if ($name == 'blueprints') {
|
||||
continue;
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to guess the package type from the source files
|
||||
*
|
||||
* @param $source
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function getPackageType($source)
|
||||
{
|
||||
$plugin_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Plugin/m';
|
||||
$theme_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Theme/m';
|
||||
|
||||
if (
|
||||
file_exists($source . 'system/defines.php') &&
|
||||
file_exists($source . 'system/config/system.yaml')
|
||||
) {
|
||||
return 'grav';
|
||||
} else {
|
||||
// must have a blueprint
|
||||
if (!file_exists($source . 'blueprints.yaml')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// either theme or plugin
|
||||
$name = basename($source);
|
||||
if (Utils::contains($name, 'theme')) {
|
||||
return 'theme';
|
||||
} elseif (Utils::contains($name, 'plugin')) {
|
||||
return 'plugin';
|
||||
}
|
||||
foreach (glob($source . "*.php") as $filename) {
|
||||
$contents = file_get_contents($filename);
|
||||
if (preg_match($theme_regex, $contents)) {
|
||||
return 'theme';
|
||||
} elseif (preg_match($plugin_regex, $contents)) {
|
||||
return 'plugin';
|
||||
}
|
||||
}
|
||||
|
||||
// Assume it's a theme
|
||||
return 'theme';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this is a local or a remote file
|
||||
*
|
||||
* @param $file
|
||||
* @return bool
|
||||
*/
|
||||
protected function isRemote($file)
|
||||
{
|
||||
return (bool) filter_var($file, FILTER_VALIDATE_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find/Parse the blueprint file
|
||||
*
|
||||
* @param $source
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function getBlueprints($source)
|
||||
{
|
||||
$blueprint_file = $source . 'blueprints.yaml';
|
||||
if (!file_exists($blueprint_file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$blueprint = (array)Yaml::parse(file_get_contents($blueprint_file));
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the zip package via the URL
|
||||
*
|
||||
* @param $package_file
|
||||
* @param $tmp
|
||||
* @return null|string
|
||||
*/
|
||||
private function downloadPackage($package_file, $tmp)
|
||||
{
|
||||
$this->output->write(" |- Downloading package... 0%");
|
||||
|
||||
$package = parse_url($package_file);
|
||||
|
||||
|
||||
$filename = basename($package['path']);
|
||||
$output = Response::get($package_file, [], [$this, 'progress']);
|
||||
|
||||
if ($output) {
|
||||
Folder::mkdir($tmp);
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Downloading package... 100%");
|
||||
$this->output->writeln('');
|
||||
|
||||
file_put_contents($tmp . DS . $filename, $output);
|
||||
|
||||
return $tmp . DS . $filename;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the local zip package to tmp
|
||||
*
|
||||
* @param $package_file
|
||||
* @param $tmp
|
||||
* @return null|string
|
||||
*/
|
||||
private function copyPackage($package_file, $tmp)
|
||||
{
|
||||
$this->output->write(" |- Copying package... 0%");
|
||||
|
||||
$package_file = realpath($package_file);
|
||||
|
||||
if (file_exists($package_file)) {
|
||||
$filename = basename($package_file);
|
||||
|
||||
Folder::mkdir($tmp);
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Copying package... 100%");
|
||||
$this->output->writeln('');
|
||||
|
||||
copy(realpath($package_file), $tmp . DS . $filename);
|
||||
|
||||
return $tmp . DS . $filename;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $progress
|
||||
*/
|
||||
public function progress($progress)
|
||||
{
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Downloading package... " . str_pad($progress['percent'], 5, " ",
|
||||
STR_PAD_LEFT) . '%');
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,9 @@
|
||||
namespace Grav\Console\Gpm;
|
||||
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use League\CLImate\CLImate;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class IndexCommand extends ConsoleCommand
|
||||
@@ -100,31 +102,47 @@ class IndexCommand extends ConsoleCommand
|
||||
protected function serve()
|
||||
{
|
||||
$this->options = $this->input->getOptions();
|
||||
|
||||
$this->gpm = new GPM($this->options['force']);
|
||||
|
||||
$this->displayGPMRelease();
|
||||
|
||||
$this->data = $this->gpm->getRepository();
|
||||
|
||||
$data = $this->filter($this->data);
|
||||
|
||||
foreach ($data as $type => $packages) {
|
||||
$this->output->writeln("<green>" . ucfirst($type) . "</green> [ " . count($packages) . " ]");
|
||||
$climate = new CLImate;
|
||||
$climate->extend('Grav\Console\TerminalObjects\Table');
|
||||
|
||||
$index = 0;
|
||||
if (!$data) {
|
||||
$this->output->writeln('No data was found in the GPM repository stored locally.');
|
||||
$this->output->writeln('Please try clearing cache and running the <green>bin/gpm index -f</green> command again');
|
||||
$this->output->writeln('If this doesn\'t work try tweaking your GPM system settings.');
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('For more help go to:');
|
||||
$this->output->writeln(' -> <yellow>https://learn.getgrav.org/troubleshooting/common-problems#cannot-connect-to-the-gpm</yellow>');
|
||||
|
||||
die;
|
||||
}
|
||||
|
||||
foreach ($data as $type => $packages) {
|
||||
$this->output->writeln("<green>" . strtoupper($type) . "</green> [ " . count($packages) . " ]");
|
||||
$packages = $this->sort($packages);
|
||||
foreach ($packages as $slug => $package) {
|
||||
$this->output->writeln(
|
||||
// index
|
||||
str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . ". " .
|
||||
// package name
|
||||
"<cyan>" . str_pad($package->name, 20) . "</cyan> " .
|
||||
// slug
|
||||
"[" . str_pad($slug, 20, ' ', STR_PAD_BOTH) . "] " .
|
||||
// version details
|
||||
$this->versionDetails($package)
|
||||
);
|
||||
|
||||
if (!empty($packages)) {
|
||||
|
||||
$table = [];
|
||||
$index = 0;
|
||||
|
||||
foreach ($packages as $slug => $package) {
|
||||
$row = [
|
||||
'Count' => $index++ + 1,
|
||||
'Name' => "<cyan>" . Utils::truncate($package->name, 20, false, ' ', '...') . "</cyan> ",
|
||||
'Slug' => $slug,
|
||||
'Version'=> $this->version($package),
|
||||
'Installed' => $this->installed($package)
|
||||
];
|
||||
$table[] = $row;
|
||||
}
|
||||
|
||||
$climate->table($table);
|
||||
}
|
||||
|
||||
$this->output->writeln('');
|
||||
@@ -143,7 +161,7 @@ class IndexCommand extends ConsoleCommand
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function versionDetails($package)
|
||||
private function version($package)
|
||||
{
|
||||
$list = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}();
|
||||
$package = isset($list[$package->slug]) ? $list[$package->slug] : $package;
|
||||
@@ -154,21 +172,30 @@ class IndexCommand extends ConsoleCommand
|
||||
|
||||
if (!$installed || !$updatable) {
|
||||
$version = $installed ? $local->version : $package->version;
|
||||
$installed = !$installed ? ' (<magenta>not installed</magenta>)' : ' (<cyan>installed</cyan>)';
|
||||
|
||||
return str_pad(" [v<green>" . $version . "</green>]", 35) . $installed;
|
||||
return "v<green>" . $version . "</green>";
|
||||
}
|
||||
|
||||
if ($updatable) {
|
||||
$installed = !$installed ? ' (<magenta>not installed</magenta>)' : ' (<cyan>installed</cyan>)';
|
||||
|
||||
return str_pad(" [v<red>" . $package->version . "</red> <cyan>➜</cyan> v<green>" . $package->available . "</green>]",
|
||||
61) . $installed;
|
||||
return "v<red>" . $package->version . "</red> <cyan>-></cyan> v<green>" . $package->available . "</green>";
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $package
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function installed($package)
|
||||
{
|
||||
$package = isset($list[$package->slug]) ? $list[$package->slug] : $package;
|
||||
$type = ucfirst(preg_replace("/s$/", '', $package->package_type));
|
||||
$installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug);
|
||||
|
||||
return !$installed ? '<magenta>not installed</magenta>' : '<cyan>installed</cyan>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
|
||||
@@ -25,6 +25,8 @@ class InfoCommand extends ConsoleCommand
|
||||
*/
|
||||
protected $gpm;
|
||||
|
||||
protected $all_yes;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -60,6 +62,8 @@ class InfoCommand extends ConsoleCommand
|
||||
{
|
||||
$this->gpm = new GPM($this->input->getOption('force'));
|
||||
|
||||
$this->all_yes = $this->input->getOption('all-yes');
|
||||
|
||||
$this->displayGPMRelease();
|
||||
|
||||
$foundPackage = $this->gpm->findPackage($this->input->getArgument('package'));
|
||||
@@ -133,38 +137,34 @@ class InfoCommand extends ConsoleCommand
|
||||
|
||||
// display changelog information
|
||||
$questionHelper = $this->getHelper('question');
|
||||
$skipPrompt = $this->input->getOption('all-yes');
|
||||
$question = new ConfirmationQuestion("Would you like to read the changelog? [y|N] ",
|
||||
false);
|
||||
$answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$skipPrompt) {
|
||||
$question = new ConfirmationQuestion("Would you like to read the changelog? [y|N] ",
|
||||
false);
|
||||
$answer = $questionHelper->ask($this->input, $this->output, $question);
|
||||
if ($answer) {
|
||||
$changelog = $foundPackage->changelog;
|
||||
|
||||
if ($answer) {
|
||||
$changelog = $foundPackage->changelog;
|
||||
$this->output->writeln("");
|
||||
foreach ($changelog as $version => $log) {
|
||||
$title = $version . ' [' . $log['date'] . ']';
|
||||
$content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) {
|
||||
return "\n" . ucfirst($match[1]) . ":";
|
||||
}, $log['content']);
|
||||
|
||||
$this->output->writeln('<cyan>'.$title.'</cyan>');
|
||||
$this->output->writeln(str_repeat('-', strlen($title)));
|
||||
$this->output->writeln($content);
|
||||
$this->output->writeln("");
|
||||
foreach ($changelog as $version => $log) {
|
||||
$title = $version . ' [' . $log['date'] . ']';
|
||||
$content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) {
|
||||
return "\n" . ucfirst($match[1]) . ":";
|
||||
}, $log['content']);
|
||||
|
||||
$this->output->writeln('<cyan>'.$title.'</cyan>');
|
||||
$this->output->writeln(str_repeat('-', strlen($title)));
|
||||
$this->output->writeln($content);
|
||||
$this->output->writeln("");
|
||||
|
||||
$question = new ConfirmationQuestion("Press [ENTER] to continue or [q] to quit ", true);
|
||||
if (!$questionHelper->ask($this->input, $this->output, $question)) {
|
||||
break;
|
||||
}
|
||||
$this->output->writeln("");
|
||||
$question = new ConfirmationQuestion("Press [ENTER] to continue or [q] to quit ", true);
|
||||
$answer = $this->all_yes ? false : $questionHelper->ask($this->input, $this->output, $question);
|
||||
if (!$answer) {
|
||||
break;
|
||||
}
|
||||
$this->output->writeln("");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->output->writeln('');
|
||||
|
||||
if ($installed && $updatable) {
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Grav\Console\Gpm;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\GPM\Installer;
|
||||
use Grav\Common\GPM\Licenses;
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Common\GPM\Remote\Package as Package;
|
||||
use Grav\Common\Grav;
|
||||
@@ -19,7 +20,6 @@ use Grav\Console\ConsoleCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/(.*)/');
|
||||
|
||||
@@ -49,6 +49,8 @@ class InstallCommand extends ConsoleCommand
|
||||
/** @var array */
|
||||
protected $demo_processing = [];
|
||||
|
||||
protected $all_yes;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -101,19 +103,15 @@ class InstallCommand extends ConsoleCommand
|
||||
{
|
||||
$this->gpm = new GPM($this->input->getOption('force'));
|
||||
|
||||
$this->all_yes = $this->input->getOption('all-yes');
|
||||
|
||||
$this->displayGPMRelease();
|
||||
|
||||
$this->destination = realpath($this->input->getOption('destination'));
|
||||
|
||||
$packages = array_map('strtolower', $this->input->getArgument('package'));
|
||||
$this->data = $this->gpm->findPackages($packages);
|
||||
|
||||
if (false === $this->isWindows() && @is_file(getenv("HOME") . '/.grav/config')) {
|
||||
$local_config_file = exec('eval echo ~/.grav/config');
|
||||
if (file_exists($local_config_file)) {
|
||||
$this->local_config = Yaml::parse($local_config_file);
|
||||
}
|
||||
}
|
||||
$this->loadLocalConfig();
|
||||
|
||||
if (
|
||||
!Installer::isGravInstance($this->destination) ||
|
||||
@@ -139,16 +137,20 @@ class InstallCommand extends ConsoleCommand
|
||||
unset($this->data['not_found']);
|
||||
unset($this->data['total']);
|
||||
|
||||
|
||||
if (isset($this->local_config)) {
|
||||
// Symlinks available, ask if Grav should use them
|
||||
|
||||
$this->use_symlinks = false;
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion('Should Grav use the symlinks if available? [y|N] ', false);
|
||||
|
||||
if ($helper->ask($this->input, $this->output, $question)) {
|
||||
$answer = $this->all_yes ? false : $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if ($answer) {
|
||||
$this->use_symlinks = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
$this->output->writeln('');
|
||||
@@ -199,8 +201,9 @@ class InstallCommand extends ConsoleCommand
|
||||
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion("The package <cyan>$package_name</cyan> is already installed, overwrite? [y|N] ", false);
|
||||
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if ($helper->ask($this->input, $this->output, $question)) {
|
||||
if ($answer) {
|
||||
$is_update = true;
|
||||
$this->processPackage($package, true, $is_update);
|
||||
} else {
|
||||
@@ -208,8 +211,8 @@ class InstallCommand extends ConsoleCommand
|
||||
}
|
||||
} else {
|
||||
if (Installer::lastErrorCode() == Installer::IS_LINK) {
|
||||
$this->output->writeln("<red>Cannot overwrite existing symlink</red>");
|
||||
return false;
|
||||
$this->output->writeln("<red>Cannot overwrite existing symlink for </red><cyan>$package_name</cyan>");
|
||||
$this->output->writeln("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,6 +247,11 @@ class InstallCommand extends ConsoleCommand
|
||||
$major_version_changed = explode('.', $new_version)[0] !== explode('.', $old_version)[0];
|
||||
|
||||
if ($major_version_changed) {
|
||||
if ($this->all_yes) {
|
||||
$this->output->writeln("The package <cyan>$package_name</cyan> will be updated to a new major version <green>$new_version</green>, from <magenta>$old_version</magenta>");
|
||||
return;
|
||||
}
|
||||
|
||||
$question = new ConfirmationQuestion("The package <cyan>$package_name</cyan> will be updated to a new major version <green>$new_version</green>, from <magenta>$old_version</magenta>. Be sure to read what changed with the new major release. Continue? [y|N] ", false);
|
||||
|
||||
if (!$helper->ask($this->input, $this->output, $question)) {
|
||||
@@ -297,9 +305,10 @@ class InstallCommand extends ConsoleCommand
|
||||
$questionNoun = 'packages';
|
||||
}
|
||||
|
||||
$question = new ConfirmationQuestion("$questionAction $questionArticle $questionNoun? [y|N] ", false);
|
||||
$question = new ConfirmationQuestion("$questionAction $questionArticle $questionNoun? [Y|n] ", true);
|
||||
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if ($helper->ask($this->input, $this->output, $question)) {
|
||||
if ($answer) {
|
||||
foreach ($packages as $dependencyName => $dependencyVersion) {
|
||||
$package = $this->gpm->findPackage($dependencyName);
|
||||
$this->processPackage($package, true, ($type == 'update') ? true : false);
|
||||
@@ -315,10 +324,9 @@ class InstallCommand extends ConsoleCommand
|
||||
|
||||
/**
|
||||
* @param $package
|
||||
* @param bool $skip_prompt
|
||||
* @param bool $update True if the package is an update
|
||||
* @param bool $is_update True if the package is an update
|
||||
*/
|
||||
private function processPackage($package, $skip_prompt = false, $is_update = false)
|
||||
private function processPackage($package, $is_update = false)
|
||||
{
|
||||
if (!$package) {
|
||||
$this->output->writeln("<red>Package not found on the GPM!</red> ");
|
||||
@@ -333,7 +341,7 @@ class InstallCommand extends ConsoleCommand
|
||||
}
|
||||
}
|
||||
|
||||
$symlink ? $this->processSymlink($package, $skip_prompt) : $this->processGpm($package, $skip_prompt, $is_update);
|
||||
$symlink ? $this->processSymlink($package) : $this->processGpm($package, $is_update);
|
||||
|
||||
$this->processDemo($package);
|
||||
}
|
||||
@@ -369,7 +377,9 @@ class InstallCommand extends ConsoleCommand
|
||||
$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)) {
|
||||
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln(" '- <red>Skipped!</red> ");
|
||||
$this->output->writeln('');
|
||||
|
||||
@@ -380,8 +390,9 @@ class InstallCommand extends ConsoleCommand
|
||||
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);
|
||||
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$helper->ask($this->input, $this->output, $question)) {
|
||||
if (!$answer) {
|
||||
$this->output->writeln(" '- <red>Skipped!</red> ");
|
||||
$this->output->writeln('');
|
||||
|
||||
@@ -452,9 +463,8 @@ class InstallCommand extends ConsoleCommand
|
||||
|
||||
/**
|
||||
* @param $package
|
||||
* @param bool $skip_prompt
|
||||
*/
|
||||
private function processSymlink($package, $skip_prompt = false)
|
||||
private function processSymlink($package)
|
||||
{
|
||||
|
||||
exec('cd ' . $this->destination);
|
||||
@@ -469,7 +479,7 @@ class InstallCommand extends ConsoleCommand
|
||||
$this->output->writeln("<green>ok</green>");
|
||||
|
||||
$this->output->write(" |- Checking destination... ");
|
||||
$checks = $this->checkDestination($package, $skip_prompt);
|
||||
$checks = $this->checkDestination($package);
|
||||
|
||||
if (!$checks) {
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
@@ -497,19 +507,29 @@ class InstallCommand extends ConsoleCommand
|
||||
|
||||
/**
|
||||
* @param $package
|
||||
* @param bool $skip_prompt
|
||||
* @param bool $is_update
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function processGpm($package, $skip_prompt = false, $is_update = false)
|
||||
private function processGpm($package, $is_update = false)
|
||||
{
|
||||
$version = isset($package->available) ? $package->available : $package->version;
|
||||
$license = Licenses::get($package->slug);
|
||||
|
||||
$this->output->writeln("Preparing to install <cyan>" . $package->name . "</cyan> [v" . $version . "]");
|
||||
|
||||
$this->output->write(" |- Downloading package... 0%");
|
||||
$this->file = $this->downloadPackage($package);
|
||||
$this->file = $this->downloadPackage($package, $license);
|
||||
|
||||
if (!$this->file) {
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
$this->output->writeln('');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->output->write(" |- Checking destination... ");
|
||||
$checks = $this->checkDestination($package, $skip_prompt);
|
||||
$checks = $this->checkDestination($package);
|
||||
|
||||
if (!$checks) {
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
@@ -530,14 +550,41 @@ class InstallCommand extends ConsoleCommand
|
||||
/**
|
||||
* @param Package $package
|
||||
*
|
||||
* @param string $license
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function downloadPackage($package)
|
||||
private function downloadPackage($package, $license = null)
|
||||
{
|
||||
$cache_dir = Grav::instance()['locator']->findResource('cache://', true);
|
||||
$this->tmp = $cache_dir . DS . 'tmp/Grav-' . uniqid();
|
||||
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
|
||||
$this->tmp = $tmp_dir . '/Grav-' . uniqid();
|
||||
$filename = $package->slug . basename($package->zipball_url);
|
||||
$output = Response::get($package->zipball_url, [], [$this, 'progress']);
|
||||
$query = '';
|
||||
|
||||
if ($package->premium) {
|
||||
$query = \json_encode(array_merge(
|
||||
$package->premium,
|
||||
[
|
||||
'slug' => $package->slug,
|
||||
'filename' => $package->premium['filename'],
|
||||
'license_key' => $license
|
||||
]
|
||||
));
|
||||
|
||||
$query = '?d=' . base64_encode($query);
|
||||
}
|
||||
|
||||
try {
|
||||
$output = Response::get($package->zipball_url . $query, [], [$this, 'progress']);
|
||||
} catch (\Exception $e) {
|
||||
$error = str_replace("\n", "\n | '- ", $e->getMessage());
|
||||
$this->output->write("\x0D");
|
||||
// extra white spaces to clear out the buffer properly
|
||||
$this->output->writeln(" |- Downloading package... <red>error</red> ");
|
||||
$this->output->writeln(" | '- " . $error);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Folder::mkdir($this->tmp);
|
||||
|
||||
@@ -553,42 +600,19 @@ class InstallCommand extends ConsoleCommand
|
||||
/**
|
||||
* @param $package
|
||||
*
|
||||
* @param bool $skip_prompt
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function checkDestination($package, $skip_prompt = false)
|
||||
private function checkDestination($package)
|
||||
{
|
||||
$question_helper = $this->getHelper('question');
|
||||
|
||||
if (!$skip_prompt) {
|
||||
$skip_prompt = $this->input->getOption('all-yes');
|
||||
}
|
||||
|
||||
Installer::isValidDestination($this->destination . DS . $package->install_path);
|
||||
|
||||
if (Installer::lastErrorCode() == Installer::EXISTS) {
|
||||
if (!$skip_prompt) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <yellow>exists</yellow>");
|
||||
|
||||
$question = new ConfirmationQuestion(" | '- The package is already installed, do you want to overwrite it? [y|N] ",
|
||||
false);
|
||||
$answer = $question_helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln(" | '- <red>You decided to not overwrite the already installed package.</red>");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Installer::lastErrorCode() == Installer::IS_LINK) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
|
||||
|
||||
if ($skip_prompt) {
|
||||
if ($this->all_yes) {
|
||||
$this->output->writeln(" | '- <yellow>Skipped automatically.</yellow>");
|
||||
|
||||
return false;
|
||||
|
||||
@@ -49,6 +49,9 @@ class SelfupgradeCommand extends ConsoleCommand
|
||||
*/
|
||||
private $upgrader;
|
||||
|
||||
protected $all_yes;
|
||||
protected $overwrite;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -56,7 +59,7 @@ class SelfupgradeCommand extends ConsoleCommand
|
||||
{
|
||||
$this
|
||||
->setName("self-upgrade")
|
||||
->setAliases(['selfupgrade'])
|
||||
->setAliases(['selfupgrade', 'selfupdate'])
|
||||
->addOption(
|
||||
'force',
|
||||
'f',
|
||||
@@ -69,6 +72,12 @@ class SelfupgradeCommand extends ConsoleCommand
|
||||
InputOption::VALUE_NONE,
|
||||
'Assumes yes (or best approach) instead of prompting'
|
||||
)
|
||||
->addOption(
|
||||
'overwrite',
|
||||
'o',
|
||||
InputOption::VALUE_NONE,
|
||||
'Option to overwrite packages if they already exist'
|
||||
)
|
||||
->setDescription("Detects and performs an update of Grav itself when available")
|
||||
->setHelp('The <info>update</info> command updates Grav itself when a new version is available');
|
||||
}
|
||||
@@ -79,6 +88,8 @@ class SelfupgradeCommand extends ConsoleCommand
|
||||
protected function serve()
|
||||
{
|
||||
$this->upgrader = new Upgrader($this->input->getOption('force'));
|
||||
$this->all_yes = $this->input->getOption('all-yes');
|
||||
$this->overwrite = $this->input->getOption('overwrite');
|
||||
|
||||
$this->displayGPMRelease();
|
||||
|
||||
@@ -89,7 +100,6 @@ class SelfupgradeCommand extends ConsoleCommand
|
||||
$release = strftime('%c', strtotime($this->upgrader->getReleaseDate()));
|
||||
|
||||
if (!$this->upgrader->meetsRequirements()) {
|
||||
$this->output->writeln("");
|
||||
$this->output->writeln("<red>ATTENTION:</red>");
|
||||
$this->output->writeln(" Grav has increased the minimum PHP requirement.");
|
||||
$this->output->writeln(" You are currently running PHP <red>" . PHP_VERSION . "</red>, but PHP <green>" . GRAV_PHP_MIN . "</green> is required.");
|
||||
@@ -100,21 +110,29 @@ class SelfupgradeCommand extends ConsoleCommand
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$this->upgrader->isUpgradable()) {
|
||||
if (!$this->overwrite && !$this->upgrader->isUpgradable()) {
|
||||
$this->output->writeln("You are already running the latest version of Grav (v" . $local . ") released on " . $release);
|
||||
exit;
|
||||
}
|
||||
|
||||
Installer::isValidDestination(GRAV_ROOT . '/system');
|
||||
if (Installer::IS_LINK === Installer::lastErrorCode()) {
|
||||
$this->output->writeln("<red>ATTENTION:</red> Grav is symlinked, cannot upgrade, aborting...");
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln("You are currently running a symbolically linked Grav v" . $local . ". Latest available is v". $remote . ".");
|
||||
exit;
|
||||
}
|
||||
|
||||
// not used but preloaded just in case!
|
||||
new ArrayInput([]);
|
||||
|
||||
$questionHelper = $this->getHelper('question');
|
||||
$skipPrompt = $this->input->getOption('all-yes');
|
||||
|
||||
|
||||
$this->output->writeln("Grav v<cyan>$remote</cyan> is now available [release date: $release].");
|
||||
$this->output->writeln("You are currently using v<cyan>" . GRAV_VERSION . "</cyan>.");
|
||||
|
||||
if (!$skipPrompt) {
|
||||
if (!$this->all_yes) {
|
||||
$question = new ConfirmationQuestion("Would you like to read the changelog before proceeding? [y|N] ",
|
||||
false);
|
||||
$answer = $questionHelper->ask($this->input, $this->output, $question);
|
||||
@@ -177,8 +195,8 @@ class SelfupgradeCommand extends ConsoleCommand
|
||||
*/
|
||||
private function download($package)
|
||||
{
|
||||
$cache_dir = Grav::instance()['locator']->findResource('cache://', true);
|
||||
$this->tmp = $cache_dir . DS . 'tmp/Grav-' . uniqid();
|
||||
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
|
||||
$this->tmp = $tmp_dir . '/Grav-' . uniqid();
|
||||
$output = Response::get($package['download'], [], [$this, 'progress']);
|
||||
|
||||
Folder::mkdir($this->tmp);
|
||||
|
||||
@@ -39,6 +39,10 @@ class UninstallCommand extends ConsoleCommand
|
||||
*/
|
||||
protected $tmp;
|
||||
|
||||
protected $dependencies= [];
|
||||
|
||||
protected $all_yes;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -68,6 +72,8 @@ class UninstallCommand extends ConsoleCommand
|
||||
{
|
||||
$this->gpm = new GPM();
|
||||
|
||||
$this->all_yes = $this->input->getOption('all-yes');
|
||||
|
||||
$packages = array_map('strtolower', $this->input->getArgument('package'));
|
||||
$this->data = ['total' => 0, 'not_found' => []];
|
||||
|
||||
@@ -108,15 +114,12 @@ class UninstallCommand extends ConsoleCommand
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$this->output->write(" |- Uninstalling package... ");
|
||||
$uninstall = $this->uninstallPackage($slug, $package);
|
||||
|
||||
if (!$uninstall) {
|
||||
$this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$this->output->writeln(" '- <green>Success!</green> ");
|
||||
$this->output->writeln('');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,36 +159,29 @@ class UninstallCommand extends ConsoleCommand
|
||||
return false;
|
||||
}
|
||||
|
||||
$locator = Grav::instance()['locator'];
|
||||
$path = $locator->findResource($package->package_type . '://' . $slug);
|
||||
Installer::uninstall($path);
|
||||
$errorCode = Installer::lastErrorCode();
|
||||
|
||||
if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) {
|
||||
$this->output->write("\x0D");
|
||||
// extra white spaces to clear out the buffer properly
|
||||
$this->output->writeln(" |- Uninstalling package... <red>error</red> ");
|
||||
$this->output->writeln(" | '- " . Installer::lastErrorMsg());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$message = Installer::getMessage();
|
||||
if ($message) {
|
||||
$this->output->write("\x0D");
|
||||
// extra white spaces to clear out the buffer properly
|
||||
$this->output->writeln(" |- " . $message);
|
||||
}
|
||||
|
||||
$this->output->write("\x0D");
|
||||
// extra white spaces to clear out the buffer properly
|
||||
$this->output->writeln(" |- Uninstalling package... <green>ok</green> ");
|
||||
|
||||
|
||||
if (isset($package->dependencies)) {
|
||||
|
||||
$dependencies = $package->dependencies;
|
||||
|
||||
if ($is_dependency) {
|
||||
foreach ($dependencies as $key => $dependency) {
|
||||
if (in_array($dependency['name'], $this->dependencies)) {
|
||||
unset($dependencies[$key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (count($dependencies) > 0) {
|
||||
$this->output->writeln(' `- Dependencies found...');
|
||||
$this->output->writeln('');
|
||||
}
|
||||
}
|
||||
|
||||
$questionHelper = $this->getHelper('question');
|
||||
|
||||
foreach($package->dependencies as $dependency) {
|
||||
foreach ($dependencies as $dependency) {
|
||||
|
||||
$this->dependencies[] = $dependency['name'];
|
||||
|
||||
if (is_array($dependency)) {
|
||||
$dependency = $dependency['name'];
|
||||
}
|
||||
@@ -194,25 +190,59 @@ class UninstallCommand extends ConsoleCommand
|
||||
}
|
||||
|
||||
$dependencyPackage = $this->gpm->findPackage($dependency);
|
||||
$question = new ConfirmationQuestion(" | '- Delete dependency <cyan>" . $dependency . "</cyan> too? [y|N] ", false);
|
||||
$answer = $questionHelper->ask($this->input, $this->output, $question);
|
||||
|
||||
if ($answer) {
|
||||
$this->output->writeln(" | '- <red>You decided to delete " . $dependency . ".</red>");
|
||||
$dependency_exists = $this->packageExists($dependency, $dependencyPackage);
|
||||
|
||||
$uninstall = $this->uninstallPackage($dependency, $dependencyPackage, true);
|
||||
if ($dependency_exists == Installer::EXISTS) {
|
||||
$this->output->writeln("A dependency on <cyan>" . $dependencyPackage->name . "</cyan> [v" . $dependencyPackage->version . "] was found");
|
||||
|
||||
if (!$uninstall) {
|
||||
$this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
|
||||
$question = new ConfirmationQuestion(" |- Uninstall <cyan>" . $dependencyPackage->name . "</cyan>? [y|N] ", false);
|
||||
$answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
|
||||
|
||||
if ($answer) {
|
||||
$uninstall = $this->uninstallPackage($dependency, $dependencyPackage, true);
|
||||
|
||||
if (!$uninstall) {
|
||||
$this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
|
||||
} else {
|
||||
$this->output->writeln(" '- <green>Success!</green> ");
|
||||
|
||||
}
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$this->output->writeln(" '- <green>Success!</green> ");
|
||||
$this->output->writeln(" '- <yellow>You decided not to uninstall " . $dependencyPackage->name . ".</yellow>");
|
||||
$this->output->writeln('');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$locator = Grav::instance()['locator'];
|
||||
$path = $locator->findResource($package->package_type . '://' . $slug);
|
||||
Installer::uninstall($path);
|
||||
$errorCode = Installer::lastErrorCode();
|
||||
|
||||
if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) {
|
||||
$this->output->writeln(" |- Uninstalling " . $package->name . " package... <red>error</red> ");
|
||||
$this->output->writeln(" | '- <yellow>" . Installer::lastErrorMsg()."</yellow>");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$message = Installer::getMessage();
|
||||
if ($message) {
|
||||
$this->output->writeln(" |- " . $message);
|
||||
}
|
||||
|
||||
if (!$is_dependency && $this->dependencies) {
|
||||
$this->output->writeln("Finishing up uninstalling <cyan>" . $package->name . "</cyan>");
|
||||
}
|
||||
$this->output->writeln(" |- Uninstalling " . $package->name . " package... <green>ok</green> ");
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -225,17 +255,15 @@ class UninstallCommand extends ConsoleCommand
|
||||
|
||||
private function checkDestination($slug, $package)
|
||||
{
|
||||
$path = Grav::instance()['locator']->findResource($package->package_type . '://' . $slug);
|
||||
$questionHelper = $this->getHelper('question');
|
||||
$skipPrompt = $this->input->getOption('all-yes');
|
||||
|
||||
Installer::isValidDestination($path);
|
||||
$exists = $this->packageExists($slug, $package);
|
||||
|
||||
if (Installer::lastErrorCode() == Installer::IS_LINK) {
|
||||
if ($exists == Installer::IS_LINK) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
|
||||
|
||||
if ($skipPrompt) {
|
||||
if ($this->all_yes) {
|
||||
$this->output->writeln(" | '- <yellow>Skipped automatically.</yellow>");
|
||||
|
||||
return false;
|
||||
@@ -243,10 +271,10 @@ class UninstallCommand extends ConsoleCommand
|
||||
|
||||
$question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ",
|
||||
false);
|
||||
$answer = $questionHelper->ask($this->input, $this->output, $question);
|
||||
$answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln(" | '- <red>You decided to not delete the symlink automatically.</red>");
|
||||
$this->output->writeln(" | '- <red>You decided not to delete the symlink automatically.</red>");
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -257,4 +285,18 @@ class UninstallCommand extends ConsoleCommand
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if package exists
|
||||
*
|
||||
* @param $slug
|
||||
* @param $package
|
||||
* @return int
|
||||
*/
|
||||
private function packageExists($slug, $package)
|
||||
{
|
||||
$path = Grav::instance()['locator']->findResource($package->package_type . '://' . $slug);
|
||||
Installer::isValidDestination($path);
|
||||
return Installer::lastErrorCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Grav\Console\Gpm;
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\GPM\Installer;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Grav\Common\GPM\Upgrader;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@@ -47,6 +48,10 @@ class UpdateCommand extends ConsoleCommand
|
||||
*/
|
||||
protected $gpm;
|
||||
|
||||
protected $all_yes;
|
||||
|
||||
protected $overwrite;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -73,6 +78,12 @@ class UpdateCommand extends ConsoleCommand
|
||||
InputOption::VALUE_NONE,
|
||||
'Assumes yes (or best approach) instead of prompting'
|
||||
)
|
||||
->addOption(
|
||||
'overwrite',
|
||||
'o',
|
||||
InputOption::VALUE_NONE,
|
||||
'Option to overwrite packages if they already exist'
|
||||
)
|
||||
->addOption(
|
||||
'plugins',
|
||||
'p',
|
||||
@@ -99,33 +110,58 @@ class UpdateCommand extends ConsoleCommand
|
||||
*/
|
||||
protected function serve()
|
||||
{
|
||||
$this->upgrader = new Upgrader($this->input->getOption('force'));
|
||||
$local = $this->upgrader->getLocalVersion();
|
||||
$remote = $this->upgrader->getRemoteVersion();
|
||||
if ($local !== $remote) {
|
||||
$this->output->writeln("<yellow>WARNING</yellow>: A new version of Grav is available. You should update Grav before updating plugins and themes. If you continue without updating Grav, some plugins or themes may stop working.");
|
||||
$this->output->writeln("");
|
||||
$questionHelper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion("Continue with the update process? [Y|n] ", true);
|
||||
$answer = $questionHelper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln("<red>Update aborted. Exiting...</red>");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$this->gpm = new GPM($this->input->getOption('force'));
|
||||
|
||||
$this->all_yes = $this->input->getOption('all-yes');
|
||||
$this->overwrite = $this->input->getOption('overwrite');
|
||||
|
||||
$this->displayGPMRelease();
|
||||
|
||||
$this->destination = realpath($this->input->getOption('destination'));
|
||||
$skip_prompt = $this->input->getOption('all-yes');
|
||||
|
||||
if (!Installer::isGravInstance($this->destination)) {
|
||||
$this->output->writeln("<red>ERROR</red>: " . Installer::lastErrorMsg());
|
||||
exit;
|
||||
}
|
||||
if ($this->input->getOption('plugins') === false and $this->input->getOption('themes') === false) {
|
||||
$list_type_update = ['plugins' => true, 'themes' => true];
|
||||
if ($this->input->getOption('plugins') === false && $this->input->getOption('themes') === false) {
|
||||
$list_type = ['plugins' => true, 'themes' => true];
|
||||
} else {
|
||||
$list_type_update['plugins'] = $this->input->getOption('plugins');
|
||||
$list_type_update['themes'] = $this->input->getOption('themes');
|
||||
$list_type['plugins'] = $this->input->getOption('plugins');
|
||||
$list_type['themes'] = $this->input->getOption('themes');
|
||||
}
|
||||
|
||||
if ($this->overwrite) {
|
||||
$this->data = $this->gpm->getInstallable($list_type);
|
||||
$description = " can be overwritten";
|
||||
} else {
|
||||
$this->data = $this->gpm->getUpdatable($list_type);
|
||||
$description = " need updating";
|
||||
}
|
||||
|
||||
$this->data = $this->gpm->getUpdatable($list_type_update);
|
||||
$only_packages = array_map('strtolower', $this->input->getArgument('package'));
|
||||
|
||||
if (!$this->data['total']) {
|
||||
if (!$this->overwrite && !$this->data['total']) {
|
||||
$this->output->writeln("Nothing to update.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->output->write("Found <green>" . $this->gpm->countInstalled() . "</green> extensions installed of which <magenta>" . $this->data['total'] . "</magenta> need updating");
|
||||
$this->output->write("Found <green>" . $this->gpm->countInstalled() . "</green> packages installed of which <magenta>" . $this->data['total'] . "</magenta>" . $description);
|
||||
|
||||
$limit_to = $this->userInputPackages($only_packages);
|
||||
|
||||
@@ -145,19 +181,23 @@ class UpdateCommand extends ConsoleCommand
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$package->available) {
|
||||
$package->available = $package->version;
|
||||
}
|
||||
|
||||
$this->output->writeln(
|
||||
// index
|
||||
str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . ". " .
|
||||
// name
|
||||
"<cyan>" . str_pad($package->name, 15) . "</cyan> " .
|
||||
// version
|
||||
"[v<magenta>" . $package->version . "</magenta> ➜ v<green>" . $package->available . "</green>]"
|
||||
"[v<magenta>" . $package->version . "</magenta> -> v<green>" . $package->available . "</green>]"
|
||||
);
|
||||
$slugs[] = $slug;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$skip_prompt) {
|
||||
if (!$this->all_yes) {
|
||||
// prompt to continue
|
||||
$this->output->writeln("");
|
||||
$questionHelper = $this->getHelper('question');
|
||||
@@ -165,7 +205,7 @@ class UpdateCommand extends ConsoleCommand
|
||||
$answer = $questionHelper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln("Update aborted. Exiting...");
|
||||
$this->output->writeln("<red>Update aborted. Exiting...</red>");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -183,7 +223,7 @@ class UpdateCommand extends ConsoleCommand
|
||||
$command_exec = $install_command->run($args, $this->output);
|
||||
|
||||
if ($command_exec != 0) {
|
||||
$this->output->writeln("<red>Error:</red> An error occurred while trying to install the extensions");
|
||||
$this->output->writeln("<red>Error:</red> An error occurred while trying to install the packages");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -204,7 +244,7 @@ class UpdateCommand extends ConsoleCommand
|
||||
foreach ($only_packages as $only_package) {
|
||||
$find = $this->gpm->findPackage($only_package);
|
||||
|
||||
if (!$find || !$this->gpm->isUpdatable($find->slug)) {
|
||||
if (!$find || (!$this->overwrite && !$this->gpm->isUpdatable($find->slug))) {
|
||||
$name = isset($find->slug) ? $find->slug : $only_package;
|
||||
$ignore[$name] = $name;
|
||||
} else {
|
||||
|
||||
29
system/src/Grav/Console/TerminalObjects/Table.php
Normal file
29
system/src/Grav/Console/TerminalObjects/Table.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Console
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2016 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Console\TerminalObjects;
|
||||
|
||||
class Table extends \League\CLImate\TerminalObject\Basic\Table
|
||||
{
|
||||
public function result()
|
||||
{
|
||||
$this->column_widths = $this->getColumnWidths();
|
||||
$this->table_width = $this->getWidth();
|
||||
$this->border = $this->getBorder();
|
||||
|
||||
$this->buildHeaderRow();
|
||||
|
||||
foreach ($this->data as $key => $columns) {
|
||||
$this->rows[] = $this->buildRow($columns);
|
||||
}
|
||||
|
||||
$this->rows[] = $this->border;
|
||||
|
||||
return $this->rows;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$array = $this->assets->getCss();
|
||||
$this->assertSame([
|
||||
'asset' => '/test.css',
|
||||
'remote' => false,
|
||||
'priority' => 10,
|
||||
'order' => 0,
|
||||
'pipeline' => true,
|
||||
@@ -51,6 +52,7 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$array = $this->assets->getCss();
|
||||
$this->assertSame([
|
||||
'asset' => '/test.css',
|
||||
'remote' => false,
|
||||
'priority' => 10,
|
||||
'order' => 0,
|
||||
'pipeline' => true,
|
||||
@@ -67,6 +69,24 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$array = $this->assets->getCss();
|
||||
$this->assertSame([
|
||||
'asset' => '/test.css',
|
||||
'remote' => false,
|
||||
'priority' => 10,
|
||||
'order' => 0,
|
||||
'pipeline' => true,
|
||||
'group' => 'head',
|
||||
'modified' => false
|
||||
], reset($array));
|
||||
|
||||
//test addCss(). Testing with remote URL
|
||||
$this->assets->reset();
|
||||
$this->assets->addCSS('http://www.somesite.com/test.css');
|
||||
$css = $this->assets->css();
|
||||
$this->assertSame('<link href="http://www.somesite.com/test.css" type="text/css" rel="stylesheet" />' . PHP_EOL, $css);
|
||||
|
||||
$array = $this->assets->getCss();
|
||||
$this->assertSame([
|
||||
'asset' => 'http://www.somesite.com/test.css',
|
||||
'remote' => true,
|
||||
'priority' => 10,
|
||||
'order' => 0,
|
||||
'pipeline' => true,
|
||||
@@ -89,6 +109,7 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$array = $this->assets->getJs();
|
||||
$this->assertSame([
|
||||
'asset' => '/test.js',
|
||||
'remote' => false,
|
||||
'priority' => 10,
|
||||
'order' => 0,
|
||||
'pipeline' => true,
|
||||
@@ -108,6 +129,7 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$array = $this->assets->getCss();
|
||||
$this->assertSame([
|
||||
'asset' => '/test.css',
|
||||
'remote' => false,
|
||||
'priority' => 10,
|
||||
'order' => 0,
|
||||
'pipeline' => true,
|
||||
@@ -126,6 +148,7 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$array = $this->assets->getJs();
|
||||
$this->assertSame([
|
||||
'asset' => '/test.js',
|
||||
'remote' => false,
|
||||
'priority' => 10,
|
||||
'order' => 0,
|
||||
'pipeline' => true,
|
||||
@@ -143,6 +166,7 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$array = $this->assets->getJs();
|
||||
$this->assertSame([
|
||||
'asset' => '/test.js',
|
||||
'remote' => false,
|
||||
'priority' => 10,
|
||||
'order' => 0,
|
||||
'pipeline' => true,
|
||||
@@ -159,6 +183,7 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$array = $this->assets->getJs();
|
||||
$this->assertSame([
|
||||
'asset' => '/test.js',
|
||||
'remote' => false,
|
||||
'priority' => 10,
|
||||
'order' => 0,
|
||||
'pipeline' => true,
|
||||
@@ -298,6 +323,19 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$this->assertContains('type="text/css" rel="stylesheet" />', $css);
|
||||
}
|
||||
|
||||
public function testPipelineWithTimestamp()
|
||||
{
|
||||
$this->assets->reset();
|
||||
$this->assets->setTimestamp('foo');
|
||||
|
||||
//Add a core Grav CSS file, which is found. Pipeline will now return a file
|
||||
$this->assets->add('/system/assets/debugger.css', null, true);
|
||||
$css = $this->assets->css();
|
||||
$this->assertContains('<link href=', $css);
|
||||
$this->assertContains('type="text/css" rel="stylesheet" />', $css);
|
||||
$this->assertContains($this->assets->getTimestamp(), $css);
|
||||
}
|
||||
|
||||
public function testAddAsyncJs()
|
||||
{
|
||||
$this->assets->reset();
|
||||
@@ -314,6 +352,65 @@ class AssetsTest extends \Codeception\TestCase\Test
|
||||
$this->assertSame('<script src="/system/assets/jquery/jquery-2.x.min.js" type="text/javascript" defer></script>' . PHP_EOL, $js);
|
||||
}
|
||||
|
||||
public function testTimestamps()
|
||||
{
|
||||
// local CSS nothing extra
|
||||
$this->assets->reset();
|
||||
$this->assets->setTimestamp('foo');
|
||||
$this->assets->addCSS('test.css');
|
||||
$css = $this->assets->css();
|
||||
$this->assertSame('<link href="/test.css?foo" type="text/css" rel="stylesheet" />' . PHP_EOL, $css);
|
||||
|
||||
// local CSS already with param
|
||||
$this->assets->reset();
|
||||
$this->assets->setTimestamp('foo');
|
||||
$this->assets->addCSS('test.css?bar');
|
||||
$css = $this->assets->css();
|
||||
$this->assertSame('<link href="/test.css?bar&foo" type="text/css" rel="stylesheet" />' . PHP_EOL, $css);
|
||||
|
||||
// external CSS already
|
||||
$this->assets->reset();
|
||||
$this->assets->setTimestamp('foo');
|
||||
$this->assets->addCSS('http://somesite.com/test.css');
|
||||
$css = $this->assets->css();
|
||||
$this->assertSame('<link href="http://somesite.com/test.css" type="text/css" rel="stylesheet" />' . PHP_EOL, $css);
|
||||
|
||||
// external CSS already with param
|
||||
$this->assets->reset();
|
||||
$this->assets->setTimestamp('foo');
|
||||
$this->assets->addCSS('http://somesite.com/test.css?bar');
|
||||
$css = $this->assets->css();
|
||||
$this->assertSame('<link href="http://somesite.com/test.css?bar" type="text/css" rel="stylesheet" />' . PHP_EOL, $css);
|
||||
|
||||
// local JS nothing extra
|
||||
$this->assets->reset();
|
||||
$this->assets->setTimestamp('foo');
|
||||
$this->assets->addJs('test.js');
|
||||
$css = $this->assets->js();
|
||||
$this->assertSame('<script src="/test.js?foo" type="text/javascript" ></script>' . PHP_EOL, $css);
|
||||
|
||||
// local JS already with param
|
||||
$this->assets->reset();
|
||||
$this->assets->setTimestamp('foo');
|
||||
$this->assets->addJs('test.js?bar');
|
||||
$css = $this->assets->js();
|
||||
$this->assertSame('<script src="/test.js?bar&foo" type="text/javascript" ></script>' . PHP_EOL, $css);
|
||||
|
||||
// external JS already
|
||||
$this->assets->reset();
|
||||
$this->assets->setTimestamp('foo');
|
||||
$this->assets->addJs('http://somesite.com/test.js');
|
||||
$css = $this->assets->js();
|
||||
$this->assertSame('<script src="http://somesite.com/test.js" type="text/javascript" ></script>' . PHP_EOL, $css);
|
||||
|
||||
// external JS already with param
|
||||
$this->assets->reset();
|
||||
$this->assets->setTimestamp('foo');
|
||||
$this->assets->addJs('http://somesite.com/test.js?bar');
|
||||
$css = $this->assets->js();
|
||||
$this->assertSame('<script src="http://somesite.com/test.js?bar" type="text/javascript" ></script>' . PHP_EOL, $css);
|
||||
}
|
||||
|
||||
public function testAddInlineCss()
|
||||
{
|
||||
$this->assets->reset();
|
||||
|
||||
85
tests/unit/Grav/Common/Helpers/ExcerptsTest.php
Normal file
85
tests/unit/Grav/Common/Helpers/ExcerptsTest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
use Codeception\Util\Fixtures;
|
||||
use Grav\Common\Helpers\Excerpts;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Language\Language;
|
||||
|
||||
/**
|
||||
* Class ExcerptsTest
|
||||
*/
|
||||
class ExcerptsTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
/** @var Parsedown $parsedown */
|
||||
protected $parsedown;
|
||||
|
||||
/** @var Grav $grav */
|
||||
protected $grav;
|
||||
|
||||
/** @var Page $page */
|
||||
protected $page;
|
||||
|
||||
/** @var Pages $pages */
|
||||
protected $pages;
|
||||
|
||||
/** @var Config $config */
|
||||
protected $config;
|
||||
|
||||
/** @var Uri $uri */
|
||||
protected $uri;
|
||||
|
||||
/** @var Language $language */
|
||||
protected $language;
|
||||
|
||||
protected $old_home;
|
||||
|
||||
protected function _before()
|
||||
{
|
||||
$grav = Fixtures::get('grav');
|
||||
$this->grav = $grav();
|
||||
$this->pages = $this->grav['pages'];
|
||||
$this->config = $this->grav['config'];
|
||||
$this->uri = $this->grav['uri'];
|
||||
$this->language = $this->grav['language'];
|
||||
$this->old_home = $this->config->get('system.home.alias');
|
||||
$this->config->set('system.home.alias', '/item1');
|
||||
$this->config->set('system.absolute_urls', false);
|
||||
$this->config->set('system.languages.supported', []);
|
||||
|
||||
unset($this->grav['language']);
|
||||
$this->grav['language'] = new Language($this->grav);
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
$locator->addPath('page', '', 'tests/fake/nested-site/user/pages', false);
|
||||
$this->pages->init();
|
||||
|
||||
$defaults = [
|
||||
'extra' => false,
|
||||
'auto_line_breaks' => false,
|
||||
'auto_url_links' => false,
|
||||
'escape_markup' => false,
|
||||
'special_chars' => ['>' => 'gt', '<' => 'lt'],
|
||||
];
|
||||
$this->page = $this->pages->dispatch('/item2/item2-2');
|
||||
$this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init();
|
||||
}
|
||||
|
||||
protected function _after()
|
||||
{
|
||||
$this->config->set('system.home.alias', $this->old_home);
|
||||
}
|
||||
|
||||
|
||||
public function testProcessImageHtml()
|
||||
{
|
||||
$this->assertRegexp('|<img alt="Sample Image" src="\/images\/.*-sample-image.jpe?g\" data-src="sample-image\.jpg\?cropZoom=300,300" \/>|',
|
||||
Excerpts::processImageHtml('<img src="sample-image.jpg?cropZoom=300,300" alt="Sample Image" />', $this->page));
|
||||
$this->assertRegexp('|<img alt="Sample Image" class="foo" src="\/images\/.*-sample-image.jpe?g\" data-src="sample-image\.jpg\?classes=foo" \/>|',
|
||||
Excerpts::processImageHtml('<img src="sample-image.jpg?classes=foo" alt="Sample Image" />', $this->page));
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user