Compare commits

...

403 Commits

Author SHA1 Message Date
Andy Miller
7cb678bdb6 fixed changelog dates 2019-11-06 17:42:41 -07:00
Andy Miller
14110d5475 prepare for rc.1 release 2019-11-06 16:09:01 -07:00
Andy Miller
c742eee1cc Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-11-06 16:08:42 -07:00
Andy Miller
86c87929ec Merge tag '1.6.17' into develop
Release v1.6.17
2019-11-06 16:07:07 -07:00
Andy Miller
ffeb5648c6 Merge branch 'release/1.6.17' 2019-11-06 16:07:07 -07:00
Andy Miller
e0e92b843c prepare for release 2019-11-06 16:06:45 -07:00
Matias Griese
a996137317 Fixed bug where Flex index file couldn't be disabled 2019-11-06 11:52:00 +02:00
Matias Griese
9fb55d8d0f Flex pages: Use translated titles / routes in admin list 2019-10-31 12:05:13 +02:00
Matias Griese
861f53c5dc Grav 1.7: Fixed Flex Pages routing if using translated slugs or system.hide_in_urls setting 2019-10-31 10:52:15 +02:00
Matias Griese
a588395ba9 Attempt to work around composer 1.7 bug 2019-10-31 08:45:10 +02:00
Andy Miller
e80b7fe19e updated JQuery 2019-10-30 21:50:20 -06:00
Matias Griese
4e23f07fcd Composer update 2019-10-29 20:57:30 +02:00
Matias Griese
a571f42e1b Merge remote-tracking branch 'origin/1.7' into 1.7
# Conflicts:
#	composer.lock
2019-10-29 20:56:50 +02:00
Matias Griese
2fd5492286 Updated to Symfony 4.3, use Symfony EventDispatcher directly and not rockettheme/toolbox wrapper 2019-10-29 20:56:31 +02:00
Andy Miller
4e227fcae5 minor vendor updates 2019-10-28 15:40:17 -06:00
Andy Miller
99c6a78134 ixed exception caused by missing template type based on Accept: header #2705 2019-10-28 15:40:06 -06:00
Matias Griese
a93ef3f752 Initial support for admin filtering support 2019-10-28 15:51:04 +02:00
Matias Griese
0814d5e3bb Merge branch '1.7' of github.com:getgrav/grav into 1.7 2019-10-25 11:27:47 +03:00
Andy Miller
2e8be3c67f Cron improvements 2019-10-24 15:29:25 -06:00
Matias Griese
191bf8730a Optimize PageObject::getLevelListing() 2019-10-24 18:12:38 +03:00
Matias Griese
09e8dfdbfd Fixed no pages in admin 2019-10-24 14:19:37 +03:00
Matias Griese
a1ea841034 Fixed fatal error in multi-site setups 2019-10-24 13:59:00 +03:00
Matias Griese
f9f836959c Reverting (git) moved folders leave empty folders which break Flex Pages 2019-10-24 12:36:14 +03:00
Matias Griese
c4ce2d1648 Misc minor code quality fixes 2019-10-24 11:26:04 +03:00
Andy Miller
0f66032c9b Merge branch 'develop' into 1.7 2019-10-23 18:16:37 -06:00
Keith Bentrup
8678f22f6b do NOT ignore "." dirs OR ignore "." dirs and all children (#2581)
If you ignore any "files" beginning with "." including directories, then the all() method will exclude .somedir, but not .somedir/somefile. Subsequently, when trying to copy all files returned from all(), it will fail when the method tries to copy a file into a directory that has not yet been created because .somedir was omitted from the return array of all().
I found this bug when trying to install the admin plugin and ./tmp was a mount and thus rename() failed and self:copy() was invoked instead (line 365).
2019-10-23 15:39:05 -06:00
Andy Miller
4ca1f1f4ca updated libs 2019-10-23 15:32:40 -06:00
Matias Griese
913efdbd6a Revert multiple changes as many of those break Grav functionality [#2699] 2019-10-22 15:20:35 +03:00
Matias Griese
9c123f7d3d Merge remote-tracking branch 'rlerdorf/1.7' into 1.7 2019-10-22 12:35:27 +03:00
Andy Miller
2b00e93f22 Merge branch 'develop' into 1.7
# Conflicts:
#	composer.json
2019-10-17 10:02:36 -06:00
Jérôme Nadaud
8322a0cfa3 Make script name more explicit (#2637) 2019-10-17 05:57:25 -06:00
Jérôme Nadaud
ab6b82eaaa Fix cache image generation when using cropresize (#2639) 2019-10-17 05:56:46 -06:00
buzatuAda
b16e8066ca fix exception array_merge() when $this->header->metadata is not array (#2701)
Exception gets thrown when $this->header->metadata is not an array, added extra verification in order to make sure it is and array before doing array_merge()
2019-10-17 05:55:27 -06:00
Ole Vik
09e10d16ea [RFR] Iterable Taxonomy (#2690)
* Allow naive iterables in Taxonomy

Currently, Taxonomy only supports one-level arrays in a Page's Taxonomy, so this works:

```
---yaml
title: XMark Test
taxonomy:
  categories: academic
  tags: [xmark, link]
  author:
    - "Ole Vik"
    - "Ole Vik"
    - "Ole Vik"
---
```

But this does not:

```yaml
---
title: XMark Test
taxonomy:
  categories: academic
  tags: [xmark, link]
  author:
    - "Ole Vik"
    - "Ole Vik"
    - "Ole Vik"
    - name: "Ole Vik"
      email: "git@olevik.net"
      url: "https://olevik.me"
---
```

The change allows another level, to accommodate cases where the Taxonomy contains arrays of strings or hashes. This could potentially be expanded to recursively allow any amount of nesting of Taxonomies.

In both the naive and expanded case, are there implications for finding and filtering by Taxonomies? I've not checked if that recurses through values as I'm not currently at my desktop, but I imagine more changes would be necessary.

* Recursively iterate taxonomy fields

* Accommodate findTaxonomy() by reducing to dot-notation

* Remove superfluous conditional
2019-10-17 05:52:37 -06:00
Matias Griese
ef8e1c2fdf Added working ETag (304 Not Modified) support based on the final rendered HTML (Grav 1.7 edition) 2019-10-16 23:52:06 +03:00
Matias Griese
e2843e6477 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	system/src/Grav/Framework/Flex/Traits/FlexMediaTrait.php
2019-10-16 23:43:29 +03:00
Matias Griese
bc1dd2a7b4 Added working ETag (304 Not Modified) support based on the final rendered HTML 2019-10-16 23:40:08 +03:00
Djamil Legato
3e52101bf8 Change of Behavior: Inflector::hyphenize will now automatically trim dashes at beginning and end of a string.
# Conflicts:
#	CHANGELOG.md
2019-10-16 11:04:26 -07:00
Djamil Legato
d11772b681 Change of Behavior: Inflector::hyphenize will now automatically trim dashes at beginning and end of a string. 2019-10-16 11:02:44 -07:00
Rasmus Lerdorf
32a9acc62e Add a .phan config file and stubs for Phan static analysis
I fixed some small inconsistencies here and there. There are still
a little over 200 to go. This time against the 1.7 branch.

To try it, checkout this PR and run:
composer require phan/phan
It doesn't have to be in the Grav tree. You can install it anywhere.
Then from the top-level Grav dir:
/vendor/bin/phan -p

You can see the ones that are left to address here:
https://gist.github.com/69eac9b37ced1cadc08ed4be0ee84f40

Also, and the main reason I added it, was to get some nice
dependency graphs from Phan to help me understand the Grav code. Some
examples:
http://pdep.lerdorf.com/?mode=class&node=\Grav\Common\File\CompiledFile&d=3
http://pdep.lerdorf.com/?mode=class&node=\Grav\Common\Filesystem\Folder&d=1
http://pdep.lerdorf.com/?mode=class&node=\Doctrine\Common\Cache\Cache&d=2
http://pdep.lerdorf.com/?mode=class&node=\RocketTheme\Toolbox\ArrayTraits\Export&d=1

Move the slider to change the number of dependency levels shown.
2019-10-16 10:38:08 -07:00
Rasmus Lerdorf
d568ddfa5c Merge pull request #1 from getgrav/1.7
1.7
2019-10-16 09:02:17 -07:00
Matias Griese
1656f0160b Enabled preview for pages 2019-10-16 14:01:48 +03:00
Matias Griese
14b33824b1 Added mime support for file formatters 2019-10-16 14:01:30 +03:00
Matias Griese
7902912269 Added template_from_string() twig function 2019-10-16 14:00:37 +03:00
Matias Griese
2db04e43d1 Hide accounts and pages from flex types page (frontend) 2019-10-16 12:00:43 +03:00
Matias Griese
467c33d3e1 More phpstan issues 2019-10-16 09:48:22 +03:00
Matias Griese
87f17d296d More phpstan issues 2019-10-15 19:40:57 +03:00
Matias Griese
feeee9ef86 Fixed PHP 7.1 bug in Flex 2019-10-15 19:00:25 +03:00
Matias Griese
7ba964f161 One more PHP 7.1 issue 2019-10-15 18:57:15 +03:00
Matias Griese
77205fb3d8 More phpstan fixes 2019-10-15 18:49:24 +03:00
Patric Johansson
4bba284bf5 Update Core to follow PSR-2 standard 2019-10-15 13:33:48 +03:00
Matias Griese
a6e78de197 Fixed multi-language saving issues with default language in Flex Pages 2019-10-14 11:11:00 +03:00
Matias Griese
8fc7755e90 Flex page: fixed filtering where route has been defined but is null 2019-10-14 10:53:46 +03:00
Matias Griese
fdfb5bb8ac Updated clockwork and debugbar 2019-10-14 10:19:50 +03:00
Andy Miller
d0800136b7 Cache tag: key is no longer required + lifetime optional 2019-10-13 17:10:41 -06:00
Andy Miller
d368aeafd4 Removed need for unique ID 2019-10-13 17:02:07 -06:00
Matias Griese
e59c596886 Fixed PageTranslateTrait::getAllLanguages() and getAllLanguages() to include default language 2019-10-11 13:43:14 +03:00
Matias Griese
411656a9d6 Added array_diff() twig function 2019-10-11 13:39:59 +03:00
Matias Griese
8599faaf3b Regression: getFlexFeatures() filtering issues after moving classes 2019-10-11 11:07:56 +03:00
Matias Griese
90e55ead29 Fixed regression in storage filename matching 2019-10-11 10:39:23 +03:00
Matias Griese
f85c94b403 Fixed type hint in SelfUpgradeCommand 2019-10-11 07:41:01 +03:00
Djamil Legato
b4fa7e38b9 Selfupgrade CLI: Fixed broken selfupgrade assets reference (fixes #2681) 2019-10-10 12:25:28 -07:00
Matias Griese
411871f636 Fixed phpstan bugs in Framework 2019-10-10 18:15:27 +03:00
Matias Griese
b01d1baa53 Improved Flex to support custom site template paths 2019-10-10 16:16:54 +03:00
Matias Griese
c3c33e954c Composer update 2019-10-10 16:07:03 +03:00
Matias Griese
edd0bc25a2 Added Utils::simpleTemplate() method for very simple variable templating 2019-10-10 16:06:39 +03:00
Matias Griese
6c57104658 Grav 1.7: Fixed PHP 7.1 compatibility issues 2019-10-10 16:05:21 +03:00
Matias Griese
26d7181eca Fixed fatal error if there are numeric folders in Flex Pages 2019-10-10 14:01:48 +03:00
Matias Griese
1fb611655f Bug fix on page admin being disabled 2019-10-09 15:44:10 +03:00
Matias Griese
b561c2b47c Improved Flex Users: obey blueprints and allow Flex to be used in admin only 2019-10-09 15:29:09 +03:00
Matias Griese
9beefb3162 Allow Flex Pages to be initialized separately in site and admin, remove Flex Objects plugin dependency 2019-10-09 15:24:26 +03:00
Matias Griese
41851b73b9 Fixed issue with duplicate configuration settings in Flex Directory 2019-10-09 15:20:20 +03:00
Matias Griese
c325c24897 Fixed phpstan issues in Framework up to level 6 2019-10-09 10:41:15 +03:00
Matias Griese
efd4e52571 Added Flex Pages classes 2019-10-08 21:42:22 +03:00
Matias Griese
565f26db8e Code quality improvements 2019-10-08 16:58:59 +03:00
Matias Griese
b641b0e442 Fixed Flex Pages not calling onPageProcessed event when cached 2019-10-07 13:45:49 +03:00
Matias Griese
2a6276a941 Fixed Page::untranslatedLanguages() not being symmetrical to Page::translatedLanguages() 2019-10-07 12:36:50 +03:00
Jeremy Gonyea
eb1b9567df Updated for latest ddev version (#2676) 2019-10-03 14:54:05 -06:00
Andy Miller
7dafa2a207 prepare for beta.10 release 2019-10-03 14:31:28 -06:00
Matias Griese
5253aa6aef Fixed user avatar file is not being randomized 2019-10-03 22:40:03 +03:00
Matias Griese
1b34530a4a Merge remote-tracking branch 'origin/1.7' into 1.7 2019-10-03 22:11:24 +03:00
Matias Griese
ae41b6f5ff Fixed Flex User Avatar not fully backwards compatible with old user 2019-10-03 22:11:13 +03:00
Andy Miller
4d09a345a4 Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
2019-10-03 13:06:45 -06:00
Matias Griese
8ac3451fbc Revert chReverted setting language for every page during initialization 2019-10-03 21:41:58 +03:00
Matias Griese
15f1f2a03d Revert chReverted setting language for every page during initialization 2019-10-03 19:17:22 +03:00
Matias Griese
ffa9ef6a7e Grav 1.7: Fixed prev/next page missing pages if pagination was turned on in page header 2019-10-03 15:16:11 +03:00
Matias Griese
a75c0cbe62 Fixed wrong Grav param separator when using Route class 2019-10-03 10:56:05 +03:00
Andy Miller
c795ead402 Updated changelog 2019-10-01 17:52:14 -06:00
Andy Miller
91270c9c66 CSVFormatter null char support 2019-10-01 17:51:29 -06:00
Andy Miller
9259c2f660 Updated changelog 2019-10-01 17:51:17 -06:00
Andy Miller
138d1e93e3 CSVFormatter null char support 2019-10-01 17:51:02 -06:00
Matias Griese
5db9f16174 Flex: Removed extra exists check when creating object (messes up "non-existing" pages) 2019-09-27 13:26:12 +03:00
Andy Miller
342eac1047 Smarter CSV handling 2019-09-26 18:35:25 -06:00
Andy Miller
88c859fde2 move try outside for loop (less memory) 2019-09-26 15:10:35 -06:00
Andy Miller
8b5bce0b6d prepare for b.9 release 2019-09-26 13:37:09 -06:00
Andy Miller
09c4cf66f5 Merge branch '1.7' of github.com:getgrav/grav into 1.7 2019-09-26 13:26:38 -06:00
Andy Miller
46b859b8df better error for badly formatted line in CsvFormatter::decode() 2019-09-26 13:26:33 -06:00
Matias Griese
a7c23c58e5 Merge remote-tracking branch 'origin/1.7' into 1.7 2019-09-26 14:54:01 +03:00
Matias Griese
d206711354 Flex storage changes 2019-09-26 14:53:52 +03:00
Matias Griese
0007100a97 Removed row copy/move logic from Flex object, it belongs to storage 2019-09-26 14:49:45 +03:00
Matias Griese
3d999501a0 Fixed Grav parameter handling in RouteFactory::createFromString() 2019-09-26 14:47:45 +03:00
Andy Miller
52f83775e3 clarification 2019-09-24 18:10:39 -06:00
Andy Miller
a35cf5a830 Added a new {% cache %} tag eliminating need for twigcache extension 2019-09-24 18:10:02 -06:00
Matias Griese
39837d0826 Improved Flex FolderStorage class to better hide storage specific logic 2019-09-20 18:33:07 +03:00
Matias Griese
66bd6a4046 Fixed error when activating Flex Accounts in GRAV system configuration (PHP 7.1) 2019-09-20 12:06:17 +03:00
Matias Griese
4e9d3395e0 Improve blueprint initialization in Flex Objects (fixes content aware fields) 2019-09-20 11:59:13 +03:00
Andy Miller
248c7764f0 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-09-19 16:44:01 -06:00
Andy Miller
f72eb1b002 Merge tag '1.6.16' into develop
Release v1.6.16

# gpg: Signature made Thu Sep 19 16:42:43 2019 MDT
# gpg:                using RSA key 6AA7DB4F23BD1A7280C4CC63E82B8D0EAB94EFB9
# gpg: Good signature from "Andy Miller <rhuk@mac.com>" [ultimate]
2019-09-19 16:42:44 -06:00
Andy Miller
25caa5138a Merge branch 'release/1.6.16' 2019-09-19 16:42:43 -06:00
Andy Miller
dffb227df6 prepare for release 2019-09-19 16:42:21 -06:00
Matias Griese
7c161d5cbc Comment out couple of language debug messages 2019-09-19 22:47:53 +03:00
Matias Griese
7b2313ef0b Fixed initial Flex Object state when creating a new objects in a form 2019-09-19 21:58:28 +03:00
Matias Griese
1ad2a3f212 Make flex to skip missing flex types 2019-09-19 21:06:32 +03:00
Matias Griese
1f2363b623 Minor improvement for the new Route::withLanguate() method 2019-09-18 12:42:05 +03:00
Andy Miller
d1c069c2ee composer updates 2019-09-17 13:21:27 -06:00
Matias Griese
67e772a5ce Merge remote-tracking branch 'origin/1.7' into 1.7 2019-09-17 22:08:10 +03:00
Matias Griese
59bb167bd4 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	CHANGELOG.md
2019-09-17 22:07:32 +03:00
Matias Griese
5c9eb1cdb8 Fixed Badly encoded JSON data warning when uploading files [#2663] 2019-09-17 22:02:21 +03:00
Andy Miller
94a54d2a82 Using phpdebugbar format to log messages (clockwork compatible) 2019-09-17 11:21:05 -06:00
Matias Griese
f201d48112 Fixed Flex Object saving causing the key to be lost for some types 2019-09-17 19:42:28 +03:00
Matias Griese
df51b64b35 Fixed bug in object saving if using streams 2019-09-17 19:03:48 +03:00
Matias Griese
93ff915737 Fixed avatars not being displayed with flex users [#2431] 2019-09-17 15:19:42 +03:00
Matias Griese
e30ab9a043 Fixed Flex user creation if file storage is being used [#2444] 2019-09-17 11:37:09 +03:00
Matias Griese
8e59a08f9e Pages::buildFlexPages(): better compatibility to old pages 2019-09-17 11:18:30 +03:00
Andy Miller
5a874006b4 Merge branch 'develop' into 1.7
# Conflicts:
#	system/src/Grav/Common/Page/Page.php
2019-09-16 16:01:22 -06:00
Matias Griese
5d639cc633 Improved language support for Route class 2019-09-16 15:50:40 +03:00
Matias Griese
ab7038b49e Flex media: set timestamps only when files were uploaded 2019-09-16 12:43:53 +03:00
Newb I the Newbd
651b354d3e Correct non-published collection filter evaluation (#2668) 2019-09-14 10:10:24 -06:00
Matias Griese
90c2079529 Fixed wrong Pages::dispatch() calls (with redirect) when we really meant to call Pages::find() 2019-09-13 22:39:46 +03:00
Matias Griese
fa064301a2 Fixed 404 error when you click to non-routable menu item with children: redirect to the first child instead 2019-09-13 22:27:38 +03:00
Matias Griese
5424047d02 Fixed Page::modular() and Page::modularTwig() returning null for folders and other non-initialized pages 2019-09-13 21:50:06 +03:00
Matias Griese
ce2b80aeb9 Added bin/grav page-system-validator [-r|--record] [-c|--check] to test Flex Pages 2019-09-13 21:45:54 +03:00
Matias Griese
e91ae5542d Made Common\Page\Header class serializable 2019-09-13 11:17:54 +03:00
Andy Miller
922f263005 refactored the code 2019-09-12 11:15:21 -06:00
Andy Miller
01af81a0a6 fixed array types 2019-09-12 10:37:41 -06:00
Andy Miller
bd8c274f38 Support Clockwork and PHPDebugBar for dump() 2019-09-12 10:36:08 -06:00
Andy Miller
05fb69daa7 Fixed broken Twig dump() 2019-09-11 21:34:17 -06:00
Matias Griese
21f95eba53 Added Route::withLanguage() method 2019-09-11 22:36:49 +03:00
Matias Griese
4ef56054ee Minor fix on ControllerResponseTrait::createJsonResponse() 2019-09-10 11:15:47 +03:00
Matias Griese
bec9292a5c Flex folder storage improvements 2019-09-09 14:28:27 +03:00
Matias Griese
0fa6328816 Fix undefined variable when there are no translations 2019-09-09 14:08:36 +03:00
Matias Griese
b30240c340 Improve language fallback logic 2019-09-06 22:14:37 +03:00
Matias Griese
59eb3b4cb2 Flex Pages: Quick and dirty multi-language support in frontend 2019-09-06 21:43:43 +03:00
Matias Griese
5fa047c1b7 Fixed sorting in Flex Pages 2019-09-06 21:00:24 +03:00
Matias Griese
30481da51d Regression: Fixed translations when language code is used for non-language purposes 2019-09-06 15:21:09 +03:00
Matias Griese
beb8f09a9d Flex FolderStorage: Added support for partial delete 2019-09-05 17:20:08 +03:00
Matias Griese
603bb6c878 Make it possible to save multiple variations of the Flex Object 2019-09-05 16:54:59 +03:00
Matias Griese
dac3e57fd4 Fixed error in Session::getFlashObject() if Flex Form is being used 2019-09-05 15:45:29 +03:00
Andy Miller
b601d2c8fd check for file existence 2019-09-03 12:18:32 -06:00
Andy Miller
ab75201f11 Added a new sanitize_svg security config option [default: true] 2019-09-03 12:07:47 -06:00
Andy Miller
45b1b0a2ef Added Security::sanitizeSVG function + update CHANGELOG 2019-09-03 11:53:25 -06:00
Matias Griese
ca2f657c98 Oops, remove print_r() 2019-09-03 14:20:39 +03:00
Matias Griese
5c33882f5b Added extra master key parameter for FlexObjectInterface::getStorageKey() 2019-09-03 13:46:50 +03:00
Matias Griese
0f85214693 Minor fix/improvement on loading Flex objects 2019-09-03 13:41:53 +03:00
Matias Griese
4570c00041 Added FlexTranslateInterface 2019-09-03 13:40:53 +03:00
Matias Griese
4a0c8846e2 Regression: Fixed language fallback 2019-09-03 13:36:19 +03:00
Andy Miller
41e51cd86d Support StaticImageMedium formats for avatars 2019-08-31 23:26:08 -06:00
Andy Miller
e2ed3098a3 prepare for beta.7 release 2019-08-30 12:10:53 -06:00
Matias Griese
4e9ca82a0f FlexForm: Fixed some compatibility issues with Form plugin 2019-08-30 15:42:00 +03:00
Matias Griese
f032f310b5 Improved language support 2019-08-30 10:38:27 +03:00
Andy Miller
6b665b112c prepare for b.6 release 2019-08-29 11:48:57 -06:00
Matias Griese
d9dbe5520d Added version support to Flex index file 2019-08-29 15:04:45 +03:00
Matias Griese
5b8674122a Fixed blueprint values for system.accounts.storage 2019-08-29 14:22:14 +03:00
Matias Griese
2e245cd36f Flex improvements 2019-08-29 12:25:06 +03:00
Matias Griese
e36a2ea1b0 FlexFolderStorage: fixed error on deleting file 2019-08-28 18:09:33 +03:00
Matias Griese
53b7c95b0d Flex: Improve copy logic 2019-08-28 14:12:02 +03:00
Matias Griese
46975cca22 Keep interfaces backwards compatible with Grav 1.6 2019-08-28 10:04:08 +03:00
Matias Griese
a418acc32b Fixed unpublished blog posts being displayed on the front-end [#2650] 2019-08-27 11:52:42 +03:00
Matias Griese
eeb35fc521 Prioritize Accounts menu item in the admin menu 2019-08-27 10:58:11 +03:00
Matias Griese
90ca9f9d49 Fixed/improved Flex caching 2019-08-26 22:51:59 +03:00
Matias Griese
ba267a389e Flex: Implemented copy 2019-08-26 18:27:04 +03:00
Matias Griese
0ab99806db Fixed compatibility regression 2019-08-26 10:51:34 +03:00
Matias Griese
c300a3b8f8 JSON: Display trace information only in debug mode 2019-08-26 10:14:01 +03:00
Matias Griese
8480fb68ac Added custom data and flex handlers to all flex objects 2019-08-26 10:07:39 +03:00
Matias Griese
8e82056afa ControllerResponseTrait: Better error response 2019-08-24 12:31:04 +03:00
Matias Griese
3e34d54b9a Added type parameter to Pages::PageTypes() 2019-08-24 12:29:43 +03:00
Matias Griese
9ad7b208ba Blueprint: Added object support for custom handlers 2019-08-24 12:28:31 +03:00
Matias Griese
1fa62d2bdc FlexIndex: Added option to return non-existing entries in the index 2019-08-23 15:13:39 +03:00
Matias Griese
b659c56aec Added ControllerResponseTrait class 2019-08-23 12:53:18 +03:00
Matias Griese
3bd02b95fe Put back cache clear on flex save 2019-08-22 22:04:43 +03:00
Matias Griese
9e5ad84a48 Fixed another issue with flex keys 2019-08-22 20:28:18 +03:00
Matias Griese
40a6c4bf72 Greatly improve Flex Pages performance, memory use 2019-08-22 19:00:55 +03:00
Matias Griese
22acffac5c Update Pages unit tests 2019-08-22 15:16:27 +03:00
Matias Griese
ede749821d Flex Pages: Added missing page events 2019-08-22 10:36:00 +03:00
Matias Griese
f26e518c03 Work around cache clear (needs better solution) 2019-08-21 17:06:12 +03:00
Matias Griese
0f6a517589 Flex: Clear only index cache on save 2019-08-21 16:08:01 +03:00
Matias Griese
3530f4fdef Further improved speed of loading objects in Flex collections, now with cache turned on 2019-08-21 15:52:20 +03:00
Matias Griese
b92476d40d Greatly improved speed of loading Flex collections multiple times 2019-08-21 14:18:35 +03:00
Matias Griese
0d0bb2c229 Better follow protected Object Property interfaces in traits 2019-08-21 12:28:05 +03:00
Matias Griese
de59bad0f8 Keep storage key and timestamp inside FlexObject (allows diff check against stored object) 2019-08-21 12:25:06 +03:00
Matias Griese
72ad49610c Added FlectIndex::loadIndex() with metadata (including timestamp) 2019-08-21 12:22:59 +03:00
Matias Griese
dcd1f3b10d Added object meta caching in Flex FolderStorage 2019-08-21 12:21:34 +03:00
Matias Griese
52cf554ea2 Added support for not instantiating pages, useful to speed up tasks 2019-08-21 10:47:55 +03:00
Andy Miller
f30f6485ba Merge branch 'develop' into 1.7
# Conflicts:
#	composer.json
#	composer.lock
#	system/defines.php
2019-08-20 17:23:04 -06:00
Andy Miller
dd8b503aa0 Merge tag '1.6.15' into develop
Release v1.6.15
2019-08-20 17:22:26 -06:00
Andy Miller
dab30673e0 Merge branch 'release/1.6.15' 2019-08-20 17:22:25 -06:00
Andy Miller
13689c2065 prepare for release 2019-08-20 17:22:16 -06:00
Andy Miller
6e23627f26 update changelog 2019-08-20 17:21:32 -06:00
Andy Miller
7db85cc79c Force Symfony 4.2 2019-08-20 17:06:19 -06:00
Matias Griese
9b6f218f33 Make Flex Pages to work with Header 2019-08-19 20:58:46 +03:00
Matias Griese
829da9ee3a Improved bin/grav yamllinter CLI command by adding an option to find YAML Linting issues from the whole site or custom folder 2019-08-19 13:18:39 +03:00
Matias Griese
033b54104e Merge branch 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-08-19 10:53:27 +03:00
Matias Griese
e5cedd074b Fixed broken markdown Twig tag [#2635] 2019-08-19 10:46:03 +03:00
Matias Griese
a6741cb761 Fixed broken taxonomies [#2633] 2019-08-19 10:21:24 +03:00
Matias Griese
8cbc2a27cd Fixed enabling PHP Debug Bar causes fatal error in Gantry [#2634] 2019-08-19 10:04:59 +03:00
Matias Griese
5f1639dc63 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-08-19 09:53:55 +03:00
Daithí Seán Ó Foghlú
ed87faad92 Update robots.txt (#2632)
I have found that Bing/Yahoo/DuckDuckGo, Yandex and Google report crawl errors when using the default robots.txt. Specifically their bots will not crawl the the path '/' or any sub-paths. I agree that the current robots.txt should work and properly implements the specification. However it still does not work.

In my experience explicitly permitting the path '/' by adding the directive Allow: / resolves the issue.

More details can be found in a blog post about the issue here: https://www.dfoley.ie/blog/starting-with-the-indieweb
2019-08-18 11:22:33 -06:00
Andy Miller
8d8b803e66 updated with composer 1.9.0 2019-08-18 10:00:37 -06:00
Andy Miller
e4ed00d84a Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
#	system/defines.php
#	system/router.php
2019-08-18 09:54:50 -06:00
Andy Miller
239f34d40c Merge branch 'release/1.6.14' 2019-08-18 09:52:55 -06:00
Andy Miller
20b9ca56fa Merge tag '1.6.14' into develop
Release v1.6.14
2019-08-18 09:52:55 -06:00
Andy Miller
647ae0fda3 prepare for release 2019-08-18 09:52:45 -06:00
Andy Miller
806dbd9ee5 refactor 2019-08-18 09:51:10 -06:00
Andy Miller
1ab8442630 add fix 2019-08-18 09:50:56 -06:00
Andy Miller
040c34d693 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-08-16 09:53:44 -06:00
Andy Miller
a2ea6faf4d Merge tag '1.6.13' into develop
Release v1.6.13
2019-08-16 09:53:21 -06:00
Andy Miller
8942aa8afc Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
2019-08-16 07:46:32 -06:00
Matias Griese
256cbe3f12 Added experimental support for Flex Pages (**Flex Objects** plugin required) 2019-08-16 15:53:18 +03:00
Matias Griese
8b31ee173e Added configuration option for Flex Page, enabled experimental options in Admin Plugin 2019-08-16 15:49:58 +03:00
Andy Miller
182970eb78 Fix for bad check on $grav_basedir 2019-08-15 14:24:40 -06:00
Matias Griese
9ed3da3df2 Fixed $page->summary() always striping HTML tags if the summary was set by $page->setSummary() 2019-08-15 19:37:19 +03:00
Matias Griese
14eaa4d00a Moved setSummary() from PageLegacyInterface to PageContentInterface 2019-08-15 19:23:05 +03:00
Matias Griese
e134e3dbd9 Fixed some docblocks 2019-08-15 13:49:30 +03:00
Andy Miller
5bfb168cd7 Don’t override the system config setting 2019-08-14 16:28:06 -06:00
Andy Miller
5aef09a410 prepare for beta release 2019-08-14 16:21:07 -06:00
Andy Miller
76732ab671 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-08-14 16:20:27 -06:00
Matias Griese
f54d9af758 Fixed regression in non-cached pages showing 404 2019-08-14 23:02:06 +03:00
Matias Griese
f883191d99 FormTrait: better debug messages on what went wrong on form submit 2019-08-14 21:40:47 +03:00
Matias Griese
de5ead78d1 Greatly simplify and generalize Pages::evaluate() method 2019-08-14 14:03:41 +03:00
Matias Griese
44bbdf7e39 Moved collection() and evaluate() logic from Page class into Pages class 2019-08-14 13:22:30 +03:00
Matias Griese
4b4eedf467 FormTrait: better debug messages on what went wrong on form submit 2019-08-14 13:16:33 +03:00
Matias Griese
bb477fd3b1 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-08-14 11:01:46 +03:00
Matias Griese
758e316a65 Merge remote-tracking branch 'origin/1.7' into 1.7 2019-08-14 11:01:17 +03:00
Matias Griese
2c38e24d00 Make FlexForm::setName() more robust 2019-08-14 11:01:07 +03:00
Andy Miller
ca24e63d22 Merge branch 'develop' into 1.7 2019-08-12 16:06:27 -06:00
Matias Griese
f1909d80db Added $grav->close() method to properly terminate the request with a response 2019-08-12 17:11:39 +03:00
Matias Griese
7718dd7e98 Improve Flex forms for better configurability 2019-08-12 11:50:16 +03:00
Matias Griese
cc66070e85 Move Page::templateFormat() content negotiation logic into Utils::getPageFormat() 2019-08-12 11:40:22 +03:00
Matias Griese
bbdc54b406 Create PageFormInterface and PageFormTrait 2019-08-12 11:37:31 +03:00
Matias Griese
c013f63b26 Create PageCollectionInterface and use it 2019-08-12 11:34:42 +03:00
Andy Miller
aa007badb5 Merge branch 'develop' into 1.7
# Conflicts:
#	system/src/Grav/Common/Twig/TwigExtension.php
2019-08-09 18:29:22 -06:00
Andy Miller
bb2e7a720b Merge branch 'develop' into 1.7 2019-08-09 17:16:41 -06:00
Andy Miller
c36e6abd66 Add language codes method 2019-08-09 16:34:08 -06:00
Andy Miller
c2b1142b7a Merge branch 'develop' into 1.7
# Conflicts:
#	composer.lock
2019-08-09 13:35:39 -06:00
Andy Miller
e03fb200a6 update vendor libs 2019-08-09 13:35:25 -06:00
Matias Griese
a964a34a6f Minor fix to make SimpleStorage to update modified time on save (now for real) 2019-07-30 13:59:23 +03:00
Matias Griese
1c28fd4c4c Minor fix to make SimpleStorage to update modified time on save 2019-07-30 13:54:38 +03:00
Matias Griese
e8529e7d0b Make flex storage classes to return storage metadata along with the data 2019-07-30 13:51:15 +03:00
Matias Griese
a6032af594 Add support for {EXT} in Flex FolderStorage pattern 2019-07-30 13:11:33 +03:00
Matias Griese
ea49415e14 Make FlexDirectory::loadObjects() more robust against missing values 2019-07-26 10:15:11 +03:00
Matias Griese
7b26022f9f Added Language::getPageExtensions() to get full list of supported page language extensions 2019-07-26 10:13:24 +03:00
Matias Griese
443fecfeb6 Added Language::getPageExtensions() to get full list of supported page language extensions 2019-07-25 14:40:38 +03:00
Matias Griese
c3324e3702 Added FlexStorage::getMetaData() to get updated object meta information for listed keys, storage improvements 2019-07-25 14:39:06 +03:00
Matias Griese
9e60408769 Flex form: Undo unique id dependency to form name 2019-07-19 16:11:52 +03:00
Matias Griese
3737bc9371 Flex form: Allow custom form layouts in admin, too (page raw mode) 2019-07-19 12:44:51 +03:00
Matias Griese
3d2360c995 Fix bad check in CSV escaping 2019-07-18 20:44:04 +03:00
Matias Griese
08b8505b6d Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-07-18 16:12:18 +03:00
Andy Miller
9607a99a7d Updated clockwork to 4.0.4 2019-07-16 13:04:40 -06:00
Matias Griese
7e63935001 Deprecated FlexDirectory::update() and FlexDirectory::remove() 2019-07-16 17:40:33 +03:00
Matias Griese
3d767a4d25 Flex objects no longer return temporary key if they do not have one; empty key is returned instead 2019-07-16 17:39:58 +03:00
Matias Griese
ee53e1be6e Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-07-16 10:11:47 +03:00
Andy Miller
676924cce5 Merge branch 'develop' into 1.7 2019-07-12 12:02:46 -06:00
Matias Griese
36a3a95ed9 Added getFlexFeatures() method to return all features that FlexObject or FlexCollection implements 2019-07-12 13:45:02 +03:00
Matias Griese
95c58c8361 FlexDirectory::getObject() can now be called without any parameters to create a new object 2019-07-12 12:40:23 +03:00
Matias Griese
66c17a8f53 Added hasFlexFeature() method to test if FlexObject or FlexCollection implements a given feature 2019-07-12 12:39:05 +03:00
Andy Miller
7172da8ed6 fix 2019-07-11 08:49:54 -06:00
Andy Miller
c55ea919ef Merge branch 'develop' into 1.7 2019-07-11 08:40:33 -06:00
Matias Griese
53216631a6 * Fixed FlexForm to allow multiple form instances with non-existing objects 2019-07-11 15:24:35 +03:00
Matias Griese
69d6b52a0e Fixed Form not to use deleted flash object until the end of the request fixing issues with reset 2019-07-11 15:23:50 +03:00
Andy Miller
ea1e0a76c1 vendor updates 2019-07-09 22:00:43 -06:00
Andy Miller
833fe8b729 Added a new bin/grav server CLI command 2019-07-09 15:54:39 -06:00
Matias Griese
23d508b390 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-07-09 14:01:45 +03:00
Andy Miller
e8b24479b9 Option to show/hide sensitive data 2019-07-08 15:53:25 -06:00
Matias Griese
1485c23aba Make Route objects immutable 2019-07-04 13:18:43 +03:00
Matias Griese
d43357f366 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-07-04 11:50:03 +03:00
Matias Griese
c8e5aa05f9 Merge remote-tracking branch 'origin/1.7' into 1.7 2019-07-02 22:01:07 +03:00
Matias Griese
04ccce1f67 Workaround bug in flex forms 2019-07-02 22:00:56 +03:00
Andy Miller
5826821895 Merge branch '1.7' of github.com:getgrav/grav into 1.7 2019-07-02 11:32:15 -06:00
Andy Miller
3ed341304b updated readme 2019-07-02 11:32:10 -06:00
Matias Griese
025e73affd Merge remote-tracking branch 'origin/1.7' into 1.7 2019-07-02 20:29:04 +03:00
Matias Griese
5635ba2bb7 Merge branch 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-07-02 20:28:55 +03:00
Andy Miller
95637a243c Better support for Symfony local server symfony server:start 2019-07-02 11:23:32 -06:00
Andy Miller
cd417a1509 prepare for release 2019-07-01 14:54:49 -06:00
Andy Miller
631ae3d3d5 Merge branch '1.7' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-07-01 13:45:42 -06:00
Andy Miller
7c34224304 Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
2019-07-01 13:45:03 -06:00
Matias Griese
6062e47377 Merge branch 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-07-01 22:26:31 +03:00
Matias Griese
a94abb4fb2 Added configuration option to set fallback content languages individually for every language 2019-07-01 14:46:10 +03:00
Matias Griese
8ab317b49a Merge remote-tracking branch 'origin/1.7' into 1.7 2019-06-29 14:09:57 +03:00
Matias Griese
44dda3d607 Fixed Language::getFallbackPageExtensions() logic to what it should have been 2019-06-29 14:09:46 +03:00
Andy Miller
75ed986437 Updated vendor libs 2019-06-28 14:48:12 -06:00
Andy Miller
3ac785b9ce Fix for changed location of phpdebug CSS 2019-06-28 14:47:13 -06:00
Matias Griese
de367e1558 FIx wrong nesting level in system.yamlL file 2019-06-28 20:52:15 +03:00
Matias Griese
4fead303d1 Changelog update 2019-06-28 15:39:44 +03:00
Matias Griese
41898af46f Merge branch 'feature/multilang' into 1.7
# Conflicts:
#	CHANGELOG.md
#	system/blueprints/config/system.yaml
2019-06-28 15:30:41 +03:00
Matias Griese
fe8833876c Changelog update 2019-06-28 15:27:27 +03:00
Matias Griese
e116998914 Added new system.debugger.censored configuration option to hide potentially sensitive information 2019-06-28 15:26:31 +03:00
Matias Griese
053f96dec1 Make Debugger::addException() to accept \Throwable 2019-06-28 13:39:04 +03:00
Matias Griese
94494c3c96 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-06-28 13:37:39 +03:00
Matias Griese
5afae3c3f2 Merge branch '1.7' of github.com:getgrav/grav into 1.7 2019-06-27 18:11:58 +03:00
Matias Griese
eed3d84a10 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-06-27 18:11:24 +03:00
Matias Griese
e7b996104f Merge branches 'develop' and 'feature/multilang' of github.com:getgrav/grav into feature/multilang
# Conflicts:
#	CHANGELOG.md
2019-06-27 18:10:36 +03:00
Matias Griese
3f176c1924 Changelog update 2019-06-27 17:59:05 +03:00
Matias Griese
91d20d8840 Changelog update 2019-06-27 14:47:05 +03:00
Matias Griese
6998505e3c Merge branch 'develop' of github.com:getgrav/grav into feature/multilang
# Conflicts:
#	CHANGELOG.md
2019-06-27 14:46:28 +03:00
Matias Griese
ffa2e0a6f6 Added new configuration option system.languages.include_default_lang_file_extension to keep default language in .md files if set to false 2019-06-27 14:44:35 +03:00
Matias Griese
b54e4fb71b Better form value handling for page name 2019-06-27 13:49:31 +03:00
Matias Griese
8f391d327a Fixed another bug in Page::untranslatedLanguages() 2019-06-27 13:01:11 +03:00
Matias Griese
e0e29f468b Fixed Language::getFallbackPageExtensions() to append .md file after the default language extension 2019-06-27 12:06:07 +03:00
Andy Miller
89b9cc5367 Merge branch 'develop' into 1.7 2019-06-26 09:06:19 -06:00
Matias Griese
f6c30cbeae Fixed .md page to be assigned to the default language and to be listed in translated/untranslated page list 2019-06-26 10:18:14 +03:00
Matias Griese
cca7b6b1d4 Minor cleanup in InitializeProcessor 2019-06-25 10:45:59 +03:00
Andy Miller
c765787102 vendor updates 2019-06-24 17:43:26 -06:00
Andy Miller
dff3872b43 prepare for release 2019-06-24 17:41:20 -06:00
Andy Miller
3c91cea232 Updated changelog 2019-06-24 16:59:03 -06:00
Andy Miller
a2e9c013ad Fixed Clockwork on Windows machines 2019-06-24 16:58:46 -06:00
Andy Miller
dd82ab45bc updated changelog 2019-06-24 16:28:33 -06:00
Andy Miller
46b710b435 Fix for clockwork in subdomains - Don’t rely on base_uri 2019-06-24 16:26:37 -06:00
Matias Griese
a6d3e1ee8e Merge branches '1.7' and 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-06-24 20:41:28 +03:00
Andy Miller
2b29b17044 Prepare for release 2019-06-21 15:20:27 -06:00
Andy Miller
5316f0f28c Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-06-21 15:19:50 -06:00
Matias Griese
ac4d6cc8d0 Merge branches '1.7' and 'develop' of github.com:getgrav/grav into 1.7 2019-06-21 23:00:14 +03:00
Andy Miller
565947e074 Tweak clockwork message #2558 2019-06-20 21:07:04 -06:00
Andy Miller
25d1767e6c updated changelog 2019-06-20 19:48:04 -06:00
Andy Miller
c079f9b95b Merge branch 'develop' into 1.7 2019-06-20 19:46:26 -06:00
Matias Griese
20cfb45c14 Fixed error if user has no form flashes 2019-06-20 21:18:44 +03:00
Matias Griese
5314558a8e Merge branches '1.7' and 'develop' of github.com:getgrav/grav into 1.7 2019-06-20 21:12:31 +03:00
Matias Griese
02b93d510a Merge branch 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	system/src/Grav/Common/Page/Pages.php
2019-06-20 20:26:49 +03:00
Matias Griese
8dfb0ca993 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-06-20 01:05:07 +03:00
Matias Griese
574df5cf80 Merge branches '1.7' and 'develop' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2019-06-19 21:52:28 +03:00
Andy Miller
34dcd8c346 prepare for beta release 2019-06-14 14:45:29 -06:00
Andy Miller
5079077e1d Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
#	system/defines.php
2019-06-14 14:43:00 -06:00
Andy Miller
1f120a0127 set to beta.1 2019-06-14 13:43:35 -06:00
Andy Miller
fe05c9b87b clockwork debugger by default 2019-06-14 13:43:24 -06:00
Andy Miller
f795a634b5 minor vendor updates 2019-06-14 13:43:13 -06:00
Andy Miller
888b93926c updated changelog 2019-06-14 13:42:47 -06:00
Matias Griese
687f29f912 Profiler: Remove even more noise 2019-06-14 15:17:15 +03:00
Matias Griese
273bc9d970 Profiler: remove some noise 2019-06-14 15:12:48 +03:00
Matias Griese
d593d5a392 Clockwork: Order profiler calls by the time used, some extra filtering 2019-06-14 15:00:17 +03:00
Matias Griese
18fb688cde Clockwork: Added xhprof profiler tab 2019-06-14 14:43:27 +03:00
Matias Griese
d8fccb0edd Clockwork: Added call traces to deprecated messages 2019-06-14 13:48:45 +03:00
Andy Miller
5843347c46 tidy up 2019-06-13 10:04:08 -06:00
Andy Miller
fedb0625b8 z-index fix 2019-06-06 12:09:04 -06:00
Andy Miller
72d012e401 minified 2 2019-06-04 20:11:41 -06:00
Andy Miller
8d77b50055 optimized clockwork css 2019-06-04 20:09:59 -06:00
Andy Miller
42eefbd34c Reworked debugger assets a little 2019-06-04 17:24:56 -06:00
Andy Miller
e5e5bf1bd8 minor twig update 2019-06-04 14:14:16 -06:00
Andy Miller
01ec334127 Merge branch '1.7' into feature/clockwork-twig 2019-06-04 14:07:43 -06:00
Matias Griese
916469a903 Fixed missing timer titles in clockwork 2019-06-04 10:09:57 +03:00
Matias Griese
55f205c801 Composer update 2019-06-04 10:03:54 +03:00
Matias Griese
8a3fc57cd8 Merge branch '1.7' into feature/clockwork-twig
# Conflicts:
#	composer.lock
2019-06-04 10:01:47 +03:00
Andy Miller
6cd1cca4fc Accidentally commited Twig 2 2019-06-03 15:32:28 -06:00
Andy Miller
1c9926ff38 Roll back to Symfony 4.2 until we have some deprecated errors sorted 2019-06-03 15:31:15 -06:00
Andy Miller
638477b06d Roll back to Symfony 4.2 until we have some deprecated errors sorted 2019-06-03 15:31:04 -06:00
Andy Miller
7e3ca73b0e removed unused use 2019-06-03 10:49:25 -06:00
Andy Miller
57a3a20868 Merge branch '1.7' into feature/clockwork-twig
# Conflicts:
#	system/src/Grav/Common/Debugger.php
2019-06-03 10:49:10 -06:00
Matias Griese
4e45c5be95 Properly handle termination in twig redirect and if theme does not exist 2019-06-03 13:05:15 +03:00
Matias Griese
ca89156c4f Added $grav->exit() method to properly terminate the request with a response 2019-06-03 12:10:43 +03:00
Matias Griese
dc5390b3dc Some code cleanup (debugger) 2019-06-03 11:14:22 +03:00
Matias Griese
0e8c7eae99 Added PHP profiling support for redirect responses 2019-06-03 11:08:47 +03:00
Matias Griese
115bdb7e10 Processor cleanup, moved logRequest() and debuggerRequest() into Debugger class 2019-06-03 10:44:10 +03:00
Matias Griese
9738c55633 Clockwork: Added request logging for Grav redirects 2019-06-03 10:29:59 +03:00
Matias Griese
23921c1a35 Some phpstan level 3 fixes 2019-06-03 08:53:25 +03:00
Andy Miller
1f3547b15b Merge branch '1.7' into feature/clockwork-twig 2019-06-02 14:23:40 -06:00
Matias Griese
6974a24669 Some phpstan fixes 2019-06-02 20:30:56 +03:00
Matias Griese
2cf35ec2c7 Composer update, fixed deprecated Twig (1.40 compatible) 2019-06-02 15:19:21 +03:00
Andy Miller
d21bb6b8c7 Merge branch '1.7' into feature/clockwork-twig 2019-05-31 10:59:12 -06:00
Matias Griese
9b8b480c8c Added basic overridable support for the fields 2019-05-31 13:39:23 +03:00
Matias Griese
ac654d56d0 Improved debugger message support 2019-05-31 13:24:06 +03:00
Matias Griese
db9e1a197e Minor fix to support toggleable on required form fields 2019-05-31 13:22:49 +03:00
Matias Griese
19bb9a6966 Regression: Fixed missing timeline name in debugbar 2019-05-30 14:44:02 +03:00
Matias Griese
b7a1c7b72a Added support for Tideways XHProf PHP Extension for profiling requests 2019-05-30 11:59:26 +03:00
Andy Miller
042486bc73 Merge branch 'feature/clockwork-twig' of github.com:getgrav/grav into feature/clockwork-twig 2019-05-30 05:43:48 +02:00
Andy Miller
42cc1c6b01 tweaked arrows 2019-05-30 00:06:45 +02:00
Matias Griese
407d2c8323 Merge branch 'feature/clockwork-twig' of github.com:getgrav/grav into feature/clockwork-twig 2019-05-29 13:54:18 +03:00
Matias Griese
8ab14c0639 Merge branch '1.7' of github.com:getgrav/grav into feature/clockwork-twig 2019-05-29 13:54:07 +03:00
Matias Griese
48c281024f Fixed Twig 2.10 errors 2019-05-29 13:53:29 +03:00
Andy Miller
f7e1bec0cf Very rought first working profiling 2019-05-29 12:33:24 +02:00
Matias Griese
41c7973fc7 Merge branches '1.7' and 'feature/clockwork-twig' of github.com:getgrav/grav into feature/clockwork-twig 2019-05-29 12:07:49 +03:00
Andy Miller
bc93e70d11 Force Twig 2.0 for now (testing and compatibility) 2019-05-29 10:17:59 +02:00
Andy Miller
320ab41435 Initial twig profiler integration..no output yet 2019-05-29 10:16:33 +02:00
Andy Miller
7213c393fd Force Twig 2.0 for now (testing and compatibility) 2019-05-29 10:16:03 +02:00
Andy Miller
6caadc8396 Merge branch '1.7' into feature/clockwork-twig 2019-05-29 09:48:32 +02:00
Andy Miller
e0d1385061 Added default to Utils::param() 2019-05-28 19:52:08 +02:00
Andy Miller
e1eed973a2 field sizes 2019-05-27 19:33:18 +03:00
Andy Miller
f4645fc77e Debugbar/Clockwork Toggle. 2019-05-27 19:20:21 +03:00
Andy Miller
136ec07450 1.7 version in defines 2019-05-27 14:54:16 +03:00
Andy Miller
e01605de55 Merge branch 'feature/clockwork' into feature/clockwork-twig 2019-05-27 13:45:16 +03:00
Andy Miller
5cc48c4e01 initial stuff 2019-05-27 13:45:00 +03:00
Matias Griese
5e2545c606 Improve debugger to have less impact when it's not enabled 2019-05-27 13:42:43 +03:00
Andy Miller
0fd2627cea Merge branch 'feature/clockwork' of github.com:getgrav/grav into feature/clockwork 2019-05-27 11:58:37 +03:00
Andy Miller
cfcd955cc2 Fixed regresssion issue of Utils::Url() not returning false on failure #2524 2019-05-27 11:58:31 +03:00
Matias Griese
7d59b69709 Debugger/Clockwork: add configuration, plugins and streams to the log 2019-05-27 11:26:34 +03:00
Matias Griese
e8eb1d9a44 Debugger: Fixed error if xdebug isn't installed 2019-05-26 17:25:59 +03:00
Andy Miller
2117748f18 updated composer.lock 2019-05-26 17:05:12 +03:00
Matias Griese
e76733e8c3 Optimization: Initialize debugbar only after the configuration has been loaded
Optimization: Combine some early Grav processors into a single one
2019-05-26 16:55:24 +03:00
Matias Griese
2da5f90e9c Merge branch 'develop' of github.com:getgrav/grav into feature/clockwork
# Conflicts:
#	CHANGELOG.md
2019-05-26 16:48:15 +03:00
Matias Griese
81b100cd3b Merge remote-tracking branch 'origin/feature/clockwork' into feature/clockwork 2019-05-26 16:42:56 +03:00
Matias Griese
19be8f5104 Fixed some phpstan level 2 issues 2019-05-26 16:42:36 +03:00
Matias Griese
ad64a9d857 Fixed reutrn type of processMediaActions 2019-05-26 16:24:58 +03:00
Andy Miller
331f416e36 Fixed bitwise operator in TwigExtension::exifFunc() #2518 2019-05-26 11:37:18 +03:00
Matias Griese
3a48c79b21 Merge branch 'develop' of github.com:getgrav/grav into feature/clockwork 2019-05-24 08:39:35 +03:00
Matias Griese
0c66de25c4 Improve clockwork debug messages, make it easier to disable debugbar or clockwork 2019-05-23 23:12:55 +03:00
Matias Griese
0bd227c22d Add basic clockwork support 2019-05-23 15:39:44 +03:00
290 changed files with 22940 additions and 5135 deletions

44
.phan/config.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
return [
"target_php_version" => null,
'pretend_newer_core_functions_exist' => true,
'allow_missing_properties' => false,
'null_casts_as_any_type' => false,
'null_casts_as_array' => false,
'array_casts_as_null' => false,
'strict_method_checking' => true,
'quick_mode' => false,
'simplify_ast' => false,
'directory_list' => [
'.',
],
"exclude_analysis_directory_list" => [
'vendor/'
],
'exclude_file_list' => [
'system/src/Grav/Common/Errors/Resources/layout.html.php',
'tests/_support/AcceptanceTester.php',
'tests/_support/FunctionalTester.php',
'tests/_support/UnitTester.php',
],
'autoload_internal_extension_signatures' => [
'memcached' => '.phan/internal_stubs/memcached.phan_php',
'memcache' => '.phan/internal_stubs/memcache.phan_php',
'redis' => '.phan/internal_stubs/Redis.phan_php',
],
'plugins' => [
'AlwaysReturnPlugin',
'UnreachableCodePlugin',
'DuplicateArrayKeyPlugin',
'PregRegexCheckerPlugin',
'PrintfCheckerPlugin',
],
'suppress_issue_types' => [
'PhanUnreferencedUseNormal',
'PhanTypeObjectUnsetDeclaredProperty',
'PhanTraitParentReference',
'PhanTypeInvalidThrowsIsInterface',
'PhanRequiredTraitNotAdded',
'PhanDeprecatedFunction', // Uncomment this to see all the deprecated calls
]
];

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,460 @@
<?php
// Start of memcache v.3.0.8
class MemcachePool {
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Open memcached server connection
* @link https://php.net/manual/en/memcache.connect.php
* @param string $host <p>
* Point to the host where memcached is listening for connections. This parameter
* may also specify other transports like <em>unix:///path/to/memcached.sock</em>
* to use UNIX domain sockets, in this case <b>port</b> must also
* be set to <em>0</em>.
* </p>
* @param int $port [optional] <p>
* Point to the port where memcached is listening for connections. Set this
* parameter to <em>0</em> when using UNIX domain sockets.
* </p>
* <p>
* Please note: <b>port</b> defaults to
* {@link https://php.net/manual/ru/memcache.ini.php#ini.memcache.default-port memcache.default_port}
* if not specified. For this reason it is wise to specify the port
* explicitly in this method call.
* </p>
* @param int $timeout [optional] <p>Value in seconds which will be used for connecting to the daemon. Think twice before changing the default value of 1 second - you can lose all the advantages of caching if your connection is too slow.</p>
* @return bool <p>Returns <b>TRUE</b> on success or <b>FALSE</b> on failure.</p>
*/
public function connect ($host, $port, $timeout = 1) {}
/**
* (PECL memcache &gt;= 2.0.0)<br/>
* Add a memcached server to connection pool
* @link https://php.net/manual/en/memcache.addserver.php
* @param string $host <p>
* Point to the host where memcached is listening for connections. This parameter
* may also specify other transports like unix:///path/to/memcached.sock
* to use UNIX domain sockets, in this case <i>port</i> must also
* be set to 0.
* </p>
* @param int $port [optional] <p>
* Point to the port where memcached is listening for connections.
* Set this
* parameter to 0 when using UNIX domain sockets.
* </p>
* <p>
* Please note: <i>port</i> defaults to
* memcache.default_port
* if not specified. For this reason it is wise to specify the port
* explicitly in this method call.
* </p>
* @param bool $persistent [optional] <p>
* Controls the use of a persistent connection. Default to <b>TRUE</b>.
* </p>
* @param int $weight [optional] <p>
* Number of buckets to create for this server which in turn control its
* probability of it being selected. The probability is relative to the
* total weight of all servers.
* </p>
* @param int $timeout [optional] <p>
* Value in seconds which will be used for connecting to the daemon. Think
* twice before changing the default value of 1 second - you can lose all
* the advantages of caching if your connection is too slow.
* </p>
* @param int $retry_interval [optional] <p>
* Controls how often a failed server will be retried, the default value
* is 15 seconds. Setting this parameter to -1 disables automatic retry.
* Neither this nor the <i>persistent</i> parameter has any
* effect when the extension is loaded dynamically via <b>dl</b>.
* </p>
* <p>
* Each failed connection struct has its own timeout and before it has expired
* the struct will be skipped when selecting backends to serve a request. Once
* expired the connection will be successfully reconnected or marked as failed
* for another <i>retry_interval</i> seconds. The typical
* effect is that each web server child will retry the connection about every
* <i>retry_interval</i> seconds when serving a page.
* </p>
* @param bool $status [optional] <p>
* Controls if the server should be flagged as online. Setting this parameter
* to <b>FALSE</b> and <i>retry_interval</i> to -1 allows a failed
* server to be kept in the pool so as not to affect the key distribution
* algorithm. Requests for this server will then failover or fail immediately
* depending on the <i>memcache.allow_failover</i> setting.
* Default to <b>TRUE</b>, meaning the server should be considered online.
* </p>
* @param callable $failure_callback [optional] <p>
* Allows the user to specify a callback function to run upon encountering an
* error. The callback is run before failover is attempted. The function takes
* two parameters, the hostname and port of the failed server.
* </p>
* @param int $timeoutms [optional] <p>
* </p>
* @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
*/
public function addServer ($host, $port = 11211, $persistent = true, $weight = null, $timeout = 1, $retry_interval = 15, $status = true, callable $failure_callback = null, $timeoutms = null) {}
/**
* (PECL memcache &gt;= 2.1.0)<br/>
* Changes server parameters and status at runtime
* @link https://secure.php.net/manual/en/memcache.setserverparams.php
* @param string $host <p>Point to the host where memcached is listening for connections.</p.
* @param int $port [optional] <p>
* Point to the port where memcached is listening for connections.
* </p>
* @param int $timeout [optional] <p>
* Value in seconds which will be used for connecting to the daemon. Think twice before changing the default value of 1 second - you can lose all the advantages of caching if your connection is too slow.
* </p>
* @param int $retry_interval [optional] <p>
* Controls how often a failed server will be retried, the default value
* is 15 seconds. Setting this parameter to -1 disables automatic retry.
* Neither this nor the <b>persistent</b> parameter has any
* effect when the extension is loaded dynamically via {@link https://secure.php.net/manual/en/function.dl.php dl()}.
* </p>
* @param bool $status [optional] <p>
* Controls if the server should be flagged as online. Setting this parameter
* to <b>FALSE</b> and <b>retry_interval</b> to -1 allows a failed
* server to be kept in the pool so as not to affect the key distribution
* algorithm. Requests for this server will then failover or fail immediately
* depending on the <b>memcache.allow_failover</b> setting.
* Default to <b>TRUE</b>, meaning the server should be considered online.
* </p>
* @param callable $failure_callback [optional] <p>
* Allows the user to specify a callback function to run upon encountering an error. The callback is run before failover is attempted.
* The function takes two parameters, the hostname and port of the failed server.
* </p>
* @return bool <p>Returns <b>TRUE</b> on success or <b>FALSE</b> on failure.</p>
*/
public function setServerParams ($host, $port = 11211, $timeout = 1, $retry_interval = 15, $status = true, callable $failure_callback = null) {}
/**
*
*/
public function setFailureCallback () {}
/**
* (PECL memcache &gt;= 2.1.0)<br/>
* Returns server status
* @link https://php.net/manual/en/memcache.getserverstatus.php
* @param string $host Point to the host where memcached is listening for connections.
* @param int $port Point to the port where memcached is listening for connections.
* @return int Returns a the servers status. 0 if server is failed, non-zero otherwise
*/
public function getServerStatus ($host, $port = 11211) {}
/**
*
*/
public function findServer () {}
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Return version of the server
* @link https://php.net/manual/en/memcache.getversion.php
* @return string|false Returns a string of server version number or <b>FALSE</b> on failure.
*/
public function getVersion () {}
/**
* (PECL memcache &gt;= 2.0.0)<br/>
* Add an item to the server. If the key already exists, the value will not be added and <b>FALSE</b> will be returned.
* @link https://php.net/manual/en/memcache.add.php
* @param string $key The key that will be associated with the item.
* @param mixed $var The variable to store. Strings and integers are stored as is, other types are stored serialized.
* @param int $flag [optional] <p>
* Use <b>MEMCACHE_COMPRESSED</b> to store the item
* compressed (uses zlib).
* </p>
* @param int $expire [optional] <p>Expiration time of the item.
* If it's equal to zero, the item will never expire.
* You can also use Unix timestamp or a number of seconds starting from current time, but in the latter case the number of seconds may not exceed 2592000 (30 days).</p>
* @return bool Returns <b>TRUE</b> on success or <b>FALSE</b> on failure. Returns <b>FALSE</b> if such key already exist. For the rest Memcache::add() behaves similarly to Memcache::set().
*/
public function add ($key , $var, $flag = null, $expire = null) {}
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Stores an item var with key on the memcached server. Parameter expire is expiration time in seconds.
* If it's 0, the item never expires (but memcached server doesn't guarantee this item to be stored all the time,
* it could be deleted from the cache to make place for other items).
* You can use MEMCACHE_COMPRESSED constant as flag value if you want to use on-the-fly compression (uses zlib).
* @link https://php.net/manual/en/memcache.set.php
* @param string $key The key that will be associated with the item.
* @param mixed $var The variable to store. Strings and integers are stored as is, other types are stored serialized.
* @param int $flag [optional] Use MEMCACHE_COMPRESSED to store the item compressed (uses zlib).
* @param int $expire [optional] Expiration time of the item. If it's equal to zero, the item will never expire. You can also use Unix timestamp or a number of seconds starting from current time, but in the latter case the number of seconds may not exceed 2592000 (30 days).
* @return bool Returns <b>TRUE</b> on success or <b>FALSE</b> on failure.
*/
public function set ($key, $var, $flag = null, $expire = null) {}
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Replace value of the existing item
* @link https://php.net/manual/en/memcache.replace.php
* @param string $key <p>The key that will be associated with the item.</p>
* @param mixed $var <p>The variable to store. Strings and integers are stored as is, other types are stored serialized.</p>
* @param int $flag [optional] <p>Use <b>MEMCACHE_COMPRESSED</b> to store the item compressed (uses zlib).</p>
* @param int $expire [optional] <p>Expiration time of the item. If it's equal to zero, the item will never expire. You can also use Unix timestamp or a number of seconds starting from current time, but in the latter case the number of seconds may not exceed 2592000 (30 days).</p>
* @return bool Returns TRUE on success or FALSE on failure.
*/
public function replace ($key, $var, $flag = null, $expire = null) {}
public function cas () {}
public function append () {}
/**
* @return string
*/
public function prepend () {}
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Retrieve item from the server
* @link https://php.net/manual/en/memcache.get.php
* @param string|array $key <p>
* The key or array of keys to fetch.
* </p>
* @param int|array $flags [optional] <p>
* If present, flags fetched along with the values will be written to this parameter. These
* flags are the same as the ones given to for example {@link https://php.net/manual/en/memcache.set.php Memcache::set()}.
* The lowest byte of the int is reserved for pecl/memcache internal usage (e.g. to indicate
* compression and serialization status).
* </p>
* @return string|array|false <p>
* Returns the string associated with the <b>key</b> or
* an array of found key-value pairs when <b>key</b> is an {@link https://php.net/manual/en/language.types.array.php array}.
* Returns <b>FALSE</b> on failure, <b>key</b> is not found or
* <b>key</b> is an empty {@link https://php.net/manual/en/language.types.array.php array}.
* </p>
*/
public function get ($key, &$flags = null) {}
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Delete item from the server
* https://secure.php.net/manual/ru/memcache.delete.php
* @param $key string The key associated with the item to delete.
* @param $timeout int [optional] This deprecated parameter is not supported, and defaults to 0 seconds. Do not use this parameter.
* @return bool Returns <b>TRUE</b> on success or <b>FALSE</b> on failure.
*/
public function delete ($key, $timeout = 0 ) {}
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Get statistics of the server
* @link https://php.net/manual/ru/memcache.getstats.php
* @param string $type [optional] <p>
* The type of statistics to fetch.
* Valid values are {reset, malloc, maps, cachedump, slabs, items, sizes}.
* According to the memcached protocol spec these additional arguments "are subject to change for the convenience of memcache developers".</p>
* @param int $slabid [optional] <p>
* Used in conjunction with <b>type</b> set to
* cachedump to identify the slab to dump from. The cachedump
* command ties up the server and is strictly to be used for
* debugging purposes.
* </p>
* @param int $limit [optional] <p>
* Used in conjunction with <b>type</b> set to cachedump to limit the number of entries to dump.
* </p>
* @return array|false Returns an associative array of server statistics or <b>FALSE</b> on failure.
*/
public function getStats ($type = null, $slabid = null, $limit = 100) {}
/**
* (PECL memcache &gt;= 2.0.0)<br/>
* Get statistics from all servers in pool
* @link https://php.net/manual/en/memcache.getextendedstats.php
* @param string $type [optional] <p>The type of statistics to fetch. Valid values are {reset, malloc, maps, cachedump, slabs, items, sizes}. According to the memcached protocol spec these additional arguments "are subject to change for the convenience of memcache developers".</p>
* @param int $slabid [optional] <p>
* Used in conjunction with <b>type</b> set to
* cachedump to identify the slab to dump from. The cachedump
* command ties up the server and is strictly to be used for
* debugging purposes.
* </p>
* @param int $limit Used in conjunction with type set to cachedump to limit the number of entries to dump.
* @return array|false Returns a two-dimensional associative array of server statistics or <b>FALSE</b>
* Returns a two-dimensional associative array of server statistics or <b>FALSE</b>
* on failure.
*/
public function getExtendedStats ($type = null, $slabid = null, $limit = 100) {}
/**
* (PECL memcache &gt;= 2.0.0)<br/>
* Enable automatic compression of large values
* @link https://php.net/manual/en/memcache.setcompressthreshold.php
* @param int $thresold <p>Controls the minimum value length before attempting to compress automatically.</p>
* @param float $min_saving [optional] <p>Specifies the minimum amount of savings to actually store the value compressed. The supplied value must be between 0 and 1. Default value is 0.2 giving a minimum 20% compression savings.</p>
* @return bool Returns <b>TRUE</b> on success or <b>FALSE</b> on failure.
*/
public function setCompressThreshold ($thresold, $min_saving = 0.2) {}
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Increment item's value
* @link https://php.net/manual/en/memcache.increment.php
* @param $key string Key of the item to increment.
* @param $value int [optional] increment the item by <b>value</b>
* @return int|false Returns new items value on success or <b>FALSE</b> on failure.
*/
public function increment ($key, $value = 1) {}
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Decrement item's value
* @link https://php.net/manual/en/memcache.decrement.php
* @param $key string Key of the item do decrement.
* @param $value int Decrement the item by <b>value</b>.
* @return int|false Returns item's new value on success or <b>FALSE</b> on failure.
*/
public function decrement ($key, $value = 1) {}
/**
* (PECL memcache &gt;= 0.4.0)<br/>
* Close memcached server connection
* @link https://php.net/manual/en/memcache.close.php
* @return bool Returns <b>TRUE</b> on success or <b>FALSE</b> on failure.
*/
public function close () {}
/**
* (PECL memcache &gt;= 1.0.0)<br/>
* Flush all existing items at the server
* @link https://php.net/manual/en/memcache.flush.php
* @return bool Returns <b>TRUE</b> on success or <b>FALSE</b> on failure.
*/
public function flush () {}
}
/**
* Represents a connection to a set of memcache servers.
* @link https://php.net/manual/en/class.memcache.php
*/
class Memcache extends MemcachePool {
/**
* (PECL memcache &gt;= 0.4.0)<br/>
* Open memcached server persistent connection
* @link https://php.net/manual/en/memcache.pconnect.php
* @param string $host <p>
* Point to the host where memcached is listening for connections. This parameter
* may also specify other transports like unix:///path/to/memcached.sock
* to use UNIX domain sockets, in this case <i>port</i> must also
* be set to 0.
* </p>
* @param int $port [optional] <p>
* Point to the port where memcached is listening for connections. Set this
* parameter to 0 when using UNIX domain sockets.
* </p>
* @param int $timeout [optional] <p>
* Value in seconds which will be used for connecting to the daemon. Think
* twice before changing the default value of 1 second - you can lose all
* the advantages of caching if your connection is too slow.
* </p>
* @return mixed a Memcache object or <b>FALSE</b> on failure.
*/
public function pconnect ($host, $port, $timeout = 1) {}
}
// string $host [, int $port [, int $timeout ]]
/**
* (PECL memcache >= 0.2.0)</br>
* Memcache::connect — Open memcached server connection
* @link https://php.net/manual/en/memcache.connect.php
* @param string $host <p>
* Point to the host where memcached is listening for connections.
* This parameter may also specify other transports like
* unix:///path/to/memcached.sock to use UNIX domain sockets,
* in this case port must also be set to 0.
* </p>
* @param int $port [optional] <p>
* Point to the port where memcached is listening for connections.
* Set this parameter to 0 when using UNIX domain sockets.
* Note: port defaults to memcache.default_port if not specified.
* For this reason it is wise to specify the port explicitly in this method call.
* </p>
* @param int $timeout [optional] <p>
* Value in seconds which will be used for connecting to the daemon.
* </p>
* @return bool Returns <b>TRUE</b> on success or <b>FALSE</b> on failure.
*/
function memcache_connect ($host, $port, $timeout = 1) {}
/**
* (PECL memcache >= 0.4.0)
* Memcache::pconnect — Open memcached server persistent connection
*
* @link https://php.net/manual/en/memcache.pconnect.php#example-5242
* @param $host
* @param null $port
* @param int $timeout
* @return Memcache
*/
function memcache_pconnect ($host, $port=null, $timeout=1) {}
function memcache_add_server () {}
function memcache_set_server_params () {}
function memcache_set_failure_callback () {}
function memcache_get_server_status () {}
function memcache_get_version () {}
function memcache_add () {}
function memcache_set () {}
function memcache_replace () {}
function memcache_cas () {}
function memcache_append () {}
function memcache_prepend () {}
function memcache_get () {}
function memcache_delete () {}
/**
* (PECL memcache &gt;= 0.2.0)<br/>
* Turn debug output on/off
* @link https://php.net/manual/en/function.memcache-debug.php
* @param bool $on_off <p>
* Turns debug output on if equals to <b>TRUE</b>.
* Turns debug output off if equals to <b>FALSE</b>.
* </p>
* @return bool <b>TRUE</b> if PHP was built with --enable-debug option, otherwise
* returns <b>FALSE</b>.
*/
function memcache_debug ($on_off) {}
function memcache_get_stats () {}
function memcache_get_extended_stats () {}
function memcache_set_compress_threshold () {}
function memcache_increment () {}
function memcache_decrement () {}
function memcache_close () {}
function memcache_flush () {}
define ('MEMCACHE_COMPRESSED', 2);
define ('MEMCACHE_USER1', 65536);
define ('MEMCACHE_USER2', 131072);
define ('MEMCACHE_USER3', 262144);
define ('MEMCACHE_USER4', 524288);
define ('MEMCACHE_HAVE_SESSION', 1);
// End of memcache v.3.0.8
?>

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,221 @@
# v1.7.0-rc.1
## 11/06/2019
1. [](#new)
* Added `Flex Pages` to Grav core and removed Flex Objects plugin dependency
* Added `Utils::simpleTemplate()` method for very simple variable templating
* Added `array_diff()` twig function
* Added `template_from_string()` twig function
* Updated Symfony Components to 4.3
1. [](#improved)
* Improved `Scheduler` cron command check and more useful CLI information
* Improved `Flex Users`: obey blueprints and allow Flex to be used in admin only
* Improved `Flex` to support custom site template paths
* Changed Twig `{% cache %}` tag to not need unique key, and `lifetime` is now optional
* Added mime support for file formatters
* Updated built-in `composer.phar` to latest `1.9.0`
* Updated vendor libraries
* Use `Symfony EventDispatcher` directly and not rockettheme/toolbox wrapper
1. [](#bugfix)
* Fixed exception caused by missing template type based on `Accept:` header [#2705](https://github.com/getgrav/grav/issues/2705)
* Fixed `Page::untranslatedLanguages()` not being symmetrical to `Page::translatedLanguages()`
* Fixed `Flex Pages` not calling `onPageProcessed` event when cached
* Fixed phpstan issues in Framework up to level 7
* Fixed issue with duplicate configuration settings in Flex Directory
* Fixed fatal error if there are numeric folders in `Flex Pages`
* Fixed error on missing `Flex` templates in if `Flex Objects` plugin isn't installed
* Fixed `PageTranslateTrait::getAllLanguages()` and `getAllLanguages()` to include default language
* Fixed multi-language saving issues with default language in `Flex Pages`
* Selfupgrade CLI: Fixed broken selfupgrade assets reference [#2681](https://github.com/getgrav/grav/issues/2681)
* Grav 1.7: Fixed PHP 7.1 compatibility issues
* Grav 1.7: Fixed fatal error in multi-site setups
* Grav 1.7: Fixed `Flex Pages` routing if using translated slugs or `system.hide_in_urls` setting
* Grav 1.7: Fixed bug where Flex index file couldn't be disabled
# v1.7.0-beta.10
## 10/03/2019
1. [](#improved)
* Flex: Removed extra exists check when creating object (messes up "non-existing" pages)
* Support customizable null character replacement in `CSVFormatter::decode()`
1. [](#bugfix)
* Fixed wrong Grav param separator when using `Route` class
* Fixed Flex User Avatar not fully backwards compatible with old user
* Grav 1.7: Fixed prev/next page missing pages if pagination was turned on in page header
* Grav 1.7: Reverted setting language for every page during initialization
* Grav 1.7: Fixed numeric language inconsistencies
# v1.7.0-beta.9
## 09/26/2019
1. [](#new)
* Added a new `{% cache %}` Twig tag eliminating need for `twigcache` extension.
1. [](#improved)
* Improved blueprint initialization in Flex Objects (fixes content aware fields)
* Improved Flex FolderStorage class to better hide storage specific logic
* Exception will output a badly formatted line in `CsvFormatter::decode()`
1. [](#bugfix)
* Fixed error when activating Flex Accounts in GRAV system configuration (PHP 7.1)
* Fixed Grav parameter handling in `RouteFactory::createFromString()`
# v1.7.0-beta.8
## 09/19/2019
1. [](#new)
* Added new `Security::sanitizeSVG()` function
* Backwards compatibility break: `FlexStorageInterface::getStoragePath()` and `getMediaPath()` can now return null
1. [](#improved)
* Several FlexObject loading improvements
* Added `bin/grav page-system-validator [-r|--record] [-c|--check]` to test Flex Pages
* Improved language support for `Route` class
1. [](#bugfix)
* Regression: Fixed language fallback
* Regression: Fixed translations when language code is used for non-language purposes
* Regression: Allow SVG avatar images for users
* Fixed error in `Session::getFlashObject()` if Flex Form is being used
* Fixed broken Twig `dump()`
* Fixed `Page::modular()` and `Page::modularTwig()` returning `null` for folders and other non-initialized pages
* Fixed 404 error when you click to non-routable menu item with children: redirect to the first child instead
* Fixed wrong `Pages::dispatch()` calls (with redirect) when we really meant to call `Pages::find()`
* Fixed avatars not being displayed with flex users [#2431](https://github.com/getgrav/grav/issues/2431)
* Fixed initial Flex Object state when creating a new objects in a form
# v1.7.0-beta.7
## 08/30/2019
1. [](#improved)
* Improved language support
1. [](#bugfix)
* `FlexForm`: Fixed some compatibility issues with Form plugin
# v1.7.0-beta.6
## 08/29/2019
1. [](#new)
* Added experimental support for `Flex Pages` (**Flex Objects** plugin required)
1. [](#improved)
* Improved `bin/grav yamllinter` CLI command by adding an option to find YAML Linting issues from the whole site or custom folder
* Added support for not instantiating pages, useful to speed up tasks
* Greatly improved speed of loading Flex collections
1. [](#bugfix)
* Fixed `$page->summary()` always striping HTML tags if the summary was set by `$page->setSummary()`
* Fixed `Flex->getObject()` when using Flex Key
* Grav 1.7: Fixed enabling PHP Debug Bar causes fatal error in Gantry [#2634](https://github.com/getgrav/grav/issues/2634)
* Grav 1.7: Fixed broken taxonomies [#2633](https://github.com/getgrav/grav/issues/2633)
* Grav 1.7: Fixed unpublished blog posts being displayed on the front-end [#2650](https://github.com/getgrav/grav/issues/2650)
# v1.7.0-beta.5
## 08/11/2019
1. [](#new)
* Added a new `bin/grav server` CLI command to easily run Symfony or PHP built-in webservers
* Added `hasFlexFeature()` method to test if `FlexObject` or `FlexCollection` implements a given feature
* Added `getFlexFeatures()` method to return all features that `FlexObject` or `FlexCollection` implements
* Deprecated `FlexDirectory::update()` and `FlexDirectory::remove()`
* Added `FlexStorage::getMetaData()` to get updated object meta information for listed keys
* Added `Language::getPageExtensions()` to get full list of supported page language extensions
* Added `$grav->close()` method to properly terminate the request with a response
* Added `Pages::getCollection()` method
1. [](#improved)
* Better support for Symfony local server `symfony server:start`
* Make `Route` objects immutable
* `FlexDirectory::getObject()` can now be called without any parameters to create a new object
* Flex objects no longer return temporary key if they do not have one; empty key is returned instead
* Updated vendor libraries
* Moved `collection()` and `evaluate()` logic from `Page` class into `Pages` class
1. [](#bugfix)
* Fixed `Form` not to use deleted flash object until the end of the request fixing issues with reset
* Fixed `FlexForm` to allow multiple form instances with non-existing objects
* Fixed `FlexObject` search by using `key`
* Grav 1.7: Fixed clockwork messages with arrays and objects
# v1.7.0-beta.4
## 07/01/2019
1. [](#new)
* Updated with Grav 1.6.12 features, improvements & fixes
* Added new configuration option `system.debugger.censored` to hide potentially sensitive information
* Added new configuration option `system.languages.include_default_lang_file_extension` to keep default language in `.md` files if set to `false`
* Added configuration option to set fallback content languages individually for every language
1. [](#improved)
* Updated Vendor libraries
1. [](#bugfix)
* Fixed `.md` page to be assigned to the default language and to be listed in translated/untranslated page list
* Fixed `Language::getFallbackPageExtensions()` to fall back only to default language instead of going through all languages
* Fixed `Language::getFallbackPageExtensions()` returning wrong file extensions when passing custom page extension
# v1.7.0-beta.3
## 06/24/2019
1. [](#bugfix)
* Fixed Clockwork on Windows machines
* Fixed parent field issues on Windows machines
* Fixed unreliable Clockwork calls in sub-folders
# v1.7.0-beta.2
## 06/21/2019
1. [](#new)
* Updated with Grav 1.6.11 fixes
1. [](#improved)
* Updated the Clockwork text
# v1.7.0-beta.1
## 06/14/2019
1. [](#new)
* Added support for [Clockwork](https://underground.works/clockwork) developer tools (now default debugger)
* Added support for [Tideways XHProf](https://github.com/tideways/php-xhprof-extension) PHP Extension for profiling method calls
* Added Twig profiling for Clockwork debugger
* Added support for Twig 2.11 (compatible with Twig 1.40+)
* Optimization: Initialize debugbar only after the configuration has been loaded
* Optimization: Combine some early Grav processors into a single one
# v1.6.17
## 11/06/2019
1. [](#new)
* Added working ETag (304 Not Modified) support based on the final rendered HTML
1. [](#improved)
* Safer file handling + customizable null char replacement in `CsvFormatter::decode()`
* Change of Behavior: `Inflector::hyphenize` will now automatically trim dashes at beginning and end of a string.
* Change in Behavior for `Folder::all()` so no longer fails if trying to copy non-existent dot file [#2581](https://github.com/getgrav/grav/pull/2581)
* renamed composer `test-plugins` script to `phpstan-plugins` to be more explicit [#2637](https://github.com/getgrav/grav/pull/2637)
1. [](#bugfix)
* Fixed PHP 7.1 bug in FlexMedia
* Fix cache image generation when using cropResize [#2639](https://github.com/getgrav/grav/pull/2639)
* Fix `array_merge()` exception with non-array page header metadata [#2701](https://github.com/getgrav/grav/pull/2701)
# v1.6.16
## 09/19/2019
1. [](#bugfix)
* Fixed Flex user creation if file storage is being used [#2444](https://github.com/getgrav/grav/issues/2444)
* Fixed `Badly encoded JSON data` warning when uploading files [#2663](https://github.com/getgrav/grav/issues/2663)
# v1.6.15
## 08/20/2019
1. [](#improved)
* Improved robots.txt [#2632](https://github.com/getgrav/grav/issues/2632)
1. [](#bugfix)
* Fixed broken markdown Twig tag [#2635](https://github.com/getgrav/grav/issues/2635)
* Force Symfony 4.2 in Grav 1.6 to remove a bunch of deprecated messages
# v1.6.14
## 08/18/2019
1. [](#bugfix)
* Actually include fix for `system\router.php` [#2627](https://github.com/getgrav/grav/issues/2627)
# v1.6.13
## 08/12/2019
## 08/16/2019
1. [](#bugfix)
* Regression fix for `system\router.php` [#2627](https://github.com/getgrav/grav/issues/2627)
# v1.6.12
## 08/11/2019
## 08/14/2019
1. [](#new)
* Added support for custom `FormFlash` save locations

Binary file not shown.

View File

@@ -3,7 +3,7 @@
use Grav\Common\Composer;
use Grav\Common\Grav;
use League\CLImate\CLImate;
use Grav\Console\Cli;
use Symfony\Component\Console\Application;
\define('GRAV_CLI', true);
@@ -52,16 +52,18 @@ if (!file_exists(GRAV_ROOT . '/index.php')) {
$app = new Application('Grav CLI Application', GRAV_VERSION);
$app->addCommands(array(
new \Grav\Console\Cli\InstallCommand(),
new \Grav\Console\Cli\ComposerCommand(),
new \Grav\Console\Cli\SandboxCommand(),
new \Grav\Console\Cli\CleanCommand(),
new \Grav\Console\Cli\ClearCacheCommand(),
new \Grav\Console\Cli\BackupCommand(),
new \Grav\Console\Cli\NewProjectCommand(),
new \Grav\Console\Cli\SchedulerCommand(),
new \Grav\Console\Cli\SecurityCommand(),
new \Grav\Console\Cli\LogViewerCommand(),
new \Grav\Console\Cli\YamlLinterCommand(),
new Cli\InstallCommand(),
new Cli\ComposerCommand(),
new Cli\SandboxCommand(),
new Cli\CleanCommand(),
new Cli\ClearCacheCommand(),
new Cli\BackupCommand(),
new Cli\NewProjectCommand(),
new Cli\SchedulerCommand(),
new Cli\SecurityCommand(),
new Cli\LogViewerCommand(),
new Cli\YamlLinterCommand(),
new Cli\ServerCommand(),
new Cli\PageSystemValidatorCommand(),
));
$app->run();

View File

@@ -2,7 +2,13 @@
"name": "getgrav/grav",
"type": "project",
"description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS",
"keywords": ["cms","flat-file cms","flat cms","flatfile cms","php"],
"keywords": [
"cms",
"flat-file cms",
"flat cms",
"flatfile cms",
"php"
],
"homepage": "https://getgrav.org",
"license": "MIT",
"require": {
@@ -16,41 +22,40 @@
"symfony/polyfill-iconv": "^1.9",
"symfony/polyfill-php72": "^1.9",
"symfony/polyfill-php73": "^1.9",
"psr/simple-cache": "^1.0",
"psr/http-message": "^1.0",
"psr/http-server-middleware": "^1.0",
"kodus/psr7-server": "*",
"nyholm/psr7": "^1.0",
"twig/twig": "~1.40",
"twig/twig": "~1.0",
"erusev/parsedown": "1.6.4",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~4.2",
"symfony/console": "~4.2",
"symfony/event-dispatcher": "~4.2",
"symfony/var-dumper": "~4.2",
"symfony/process": "~4.2",
"symfony/contracts": "~1.0",
"symfony/yaml": "~4.3.0",
"symfony/console": "~4.3.0",
"symfony/event-dispatcher": "~4.3.0",
"symfony/var-dumper": "~4.3.0",
"symfony/process": "~4.3.0",
"doctrine/cache": "^1.8",
"doctrine/collections": "^1.5",
"guzzlehttp/psr7": "^1.4",
"filp/whoops": "~2.2",
"matthiasmullie/minify": "^1.3",
"monolog/monolog": "~1.0",
"gregwar/image": "2.*",
"donatj/phpuseragentparser": "~0.10",
"pimple/pimple": "~3.2",
"rockettheme/toolbox": "~1.4",
"maximebf/debugbar": "~1.15",
"maximebf/debugbar": "~1.0",
"league/climate": "^3.4",
"antoligy/dom-string-iterators": "^1.0",
"miljar/php-exif": "^0.6.4",
"composer/ca-bundle": "^1.0",
"dragonmantank/cron-expression": "^1.2",
"phive/twig-extensions-deferred": "^1.0",
"willdurand/negotiation": "^2.3"
"willdurand/negotiation": "^2.3",
"itsgoingd/clockwork": "~4.0",
"enshrined/svg-sanitize": "^0.10.0"
},
"require-dev": {
"codeception/codeception": "^2.4",
@@ -77,24 +82,32 @@
{
"type": "vcs",
"url": "https://github.com/trilbymedia/PHP-Markdown-Documentation-Generator"
},
{
"type": "vcs",
"url": "https://github.com/itsgoingd/clockwork"
}
],
"autoload": {
"psr-4": {
"Grav\\": "system/src/Grav"
},
"files": ["system/defines.php"]
"files": [
"system/defines.php"
]
},
"archive": {
"exclude": ["VERSION"]
"exclude": [
"VERSION"
]
},
"scripts": {
"api-16": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.16.md",
"api-15": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.md",
"post-create-project-cmd": "bin/grav install",
"phpstan": "vendor/bin/phpstan analyse -l 2 -c ./tests/phpstan/phpstan.neon system/src --memory-limit=256M",
"phpstan-framework": "vendor/bin/phpstan analyse -l 5 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=256M",
"test-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=256M",
"phpstan": "vendor/bin/phpstan analyse -l 2 -c ./tests/phpstan/phpstan.neon system/src --memory-limit=300M",
"phpstan-framework": "vendor/bin/phpstan analyse -l 7 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=300M",
"test-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=300M",
"test": "vendor/bin/codecept run unit",
"test-windows": "vendor\\bin\\codecept run unit"
},

553
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,8 +16,11 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
die(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
}
if (PHP_SAPI === 'cli-server' && !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>");
if (PHP_SAPI === 'cli-server') {
$symfony_server = strpos(getenv('_'), 'symfony') !== false;
if (!isset($_SERVER['PHP_CLI_ROUTER']) && !$symfony_server) {
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
}
}
// Ensure vendor libraries exist

View File

@@ -10,3 +10,4 @@ Disallow: /user/
Allow: /user/pages/
Allow: /user/themes/
Allow: /user/images/
Allow: /

View File

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

View File

@@ -0,0 +1,2 @@
/** Clockwork Debugger CSS **/
.clockwork-badge{position:fixed;z-index:10;bottom:0;left:0;padding:2px 4px;background-color:#eee;border:1px solid #ccc;border-bottom:0;border-left:0;display:flex;align-items:center}.clockwork-badge:hover{width:auto}.clockwork-badge:hover:after{content:'Grav Clockwork debugger enabled. Install Clockwork Browser extension (Chrome or Firefox), open your Developer tools and then select the Clockwork tab.'}.clockwork-badge:after{margin-left:10px;font-family:Monaco,Consolas,"Lucida Console",monospace;font-size:12px;line-height:1.5;color:#666}.clockwork-badge i{display:block;float:left;height:22px;width:22px;min-width:22px;background-size:contain;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAA/1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeHh4AAAD///8EBAT7+/sLCwv29vYVFRUvLy/t7e3m5ubCwsKxsbE/Pz+mpqZMTEwcHBzy8vLp6emfn5+AgIA2Njbi4uLf39+rq6tzc3NWVlYhISHa2trW1tbS0tLMzMy7u7uZmZmUlJSMjIxvb29kZGRHR0c7Ozt5eXkqKiq1tbWQkJBqampbW1tSUlLHx8eHh4ckJCRDQ0M3wD42AAAAI3RSTlMA/PibTbQ0x76TVAlw4LhZLOuEYCAN9Hjx0a2ppGZEGYw97djhXHwAAATZSURBVFjDlVcHW+MwDO1eFCjj2McNOzvdpXTTXVbL/P+/5SQ7QSSX5Di1X1onfi/Sk+Q4sTDbKqWK+YuznZ2zi3wxVdqK/Zf92M1nT9gnO8rmd398GX6Z3xaoOFoiAQcx3E5efgmeSuN8F6Xg1x3G06l/wjNpMR1B0uif4EhnIuFb+0diIoFXk3IVfokisR+h52GO4JKgyjmfaMhAFNlSaPR7DpwI+lzn/E4QKIqmKIJirxCMP4izBPPZPXhgXwMBYgULw0nfg/BF5scDbslb7QeJ08yqqTEmGYoB95d4H8ETL8+n9wBqrLu6ao3bBsMwAnxISf/9BHcqxNB8Y7cWl3Zz7TAUfPrvAT6AoNEFFXvsjutL01yOuMrtBxnFXsmT/1wQHmdWAFNnI3uI48Yj0FUcHbKf62GfUfr8eeQt7Uk3mQZpZNoVRPEui5vtEz5zFEpgWnyqVBZMc6oaGNriH2hGVZ0OxEvInPeMaZWJBA7vmPbCr5jjws5HBnAUxvDMH40aCIf4G5BjRQSs8E8HFFYf8bGxgDvD55bzGhwWkoBcuIyHR/AMdaCagxXDhtL6tSqoWpd4BMnlIR+Or+rYTK/a3EAGcc6e4AWHISnWv20iCCojsHoVlQdjrMexFF2C7UMg2A2WEGWbQhXN6l3eXC6XGp4b9qxbuEB2EBGBwtocrK90cVG5mbRXm6vmx/0phq1sIAGKDgLOBiN1MrO5a9aDl+D0W6x0Ar9BCTRuIIANa90Y7LrLVRXzwVtDInCqMRWcf2bUOEAsa4wJqFowQALL9EiAtVRk8QC4OW+1pOM9jIaVASwYagyNXDj+W0NcfuZNzjtXOiL0Zzg30Llj+ptfxQs4+vBPNiL5PawFCBkgXpUaVtqGl+A8dgZHL34BcBUQrwPptToW+o37Ku+UH9eYByJIx3YkAeFnMFuGO7S5gEp7YhXxa5OOAM39RXDPXb0qmpROsswZe+twXdU55oUIZAiEv3bD1UFwIYKkmGqytPCDCwKFQCKK0yL7qtSAPX54UAbtsLuBHkb9zyLmPQSNjsSgmQwKUOIfEY8F8t4B34DvndJY9BA8tNBJq1Nev9axmaStFcQLhgYoCTo0salkIaW8OUDdWjMTR2sHPhrAFZqx6cqcKE4pl2BJJ4K6hfwvqNgAnXfKX/HU6X3Zrhnu0k7tLNZtTBRv1hkwTDBY1NzFU6doDYjJbWdQkQhWwuU7/LvhTh3SDoco4ECL4i5dwURbc8NdDZz2IwKicE8d0KIqWetLE3+lL4hvUuGSeRfVWNLfj/gpOw4smBJBkKQHCzlHGwvAj4woB1gq5NGGLSXtORBPnUQPV5/MPVkDMxbpwG7w4x0xL6Ltxka0A/4NBvV09UVk4DoSn/jl2+JQS9q9KYawisAD4CfhsZ4TH3htylsdEHARIQBusqCKyUpymycgbbkkXEXjT3z7/oKQFTFVuZD2FMJHZIDsO5x2d4aAr2jR+GLwZhtAb028/0yJ9J8dE87jQyKObcjtTXT8dH+fDuKF4/eiPwzH44wTf/yUi6wrpRIOZ9lM1EtXAifFI+CJn9+iX/t2xMQwOMth/UZbASi8btAwR9FHWSpJr75g9Oqbin3VDg+SpwlP6k6TB4ex/7JvmcJx8jydy6XPk8eFTKhyfwCgX71MSvaBHgAAAABJRU5ErkJggg==)}

View File

@@ -0,0 +1,3 @@
/** Clockwork Debugger JS **/
document.addEventListener("DOMContentLoaded",function () {
var e=document.createElement("div");e.appendChild(document.createElement("i")),e.className="clockwork-badge",document.body.appendChild(e)});

View File

@@ -0,0 +1,70 @@
div.phpdebugbar {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.phpdebugbar pre {
padding: 1rem;
}
.phpdebugbar div.phpdebugbar-header > div > * {
padding: 5px 15px;
}
.phpdebugbar div.phpdebugbar-header > div.phpdebugbar-header-right > * {
padding: 5px 8px;
}
.phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAA/1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeHh4AAAD///8EBAT7+/sLCwv29vYVFRUvLy/t7e3m5ubCwsKxsbE/Pz+mpqZMTEwcHBzy8vLp6emfn5+AgIA2Njbi4uLf39+rq6tzc3NWVlYhISHa2trW1tbS0tLMzMy7u7uZmZmUlJSMjIxvb29kZGRHR0c7Ozt5eXkqKiq1tbWQkJBqampbW1tSUlLHx8eHh4ckJCRDQ0M3wD42AAAAI3RSTlMA/PibTbQ0x76TVAlw4LhZLOuEYCAN9Hjx0a2ppGZEGYw97djhXHwAAATZSURBVFjDlVcHW+MwDO1eFCjj2McNOzvdpXTTXVbL/P+/5SQ7QSSX5Di1X1onfi/Sk+Q4sTDbKqWK+YuznZ2zi3wxVdqK/Zf92M1nT9gnO8rmd398GX6Z3xaoOFoiAQcx3E5efgmeSuN8F6Xg1x3G06l/wjNpMR1B0uif4EhnIuFb+0diIoFXk3IVfokisR+h52GO4JKgyjmfaMhAFNlSaPR7DpwI+lzn/E4QKIqmKIJirxCMP4izBPPZPXhgXwMBYgULw0nfg/BF5scDbslb7QeJ08yqqTEmGYoB95d4H8ETL8+n9wBqrLu6ao3bBsMwAnxISf/9BHcqxNB8Y7cWl3Zz7TAUfPrvAT6AoNEFFXvsjutL01yOuMrtBxnFXsmT/1wQHmdWAFNnI3uI48Yj0FUcHbKf62GfUfr8eeQt7Uk3mQZpZNoVRPEui5vtEz5zFEpgWnyqVBZMc6oaGNriH2hGVZ0OxEvInPeMaZWJBA7vmPbCr5jjws5HBnAUxvDMH40aCIf4G5BjRQSs8E8HFFYf8bGxgDvD55bzGhwWkoBcuIyHR/AMdaCagxXDhtL6tSqoWpd4BMnlIR+Or+rYTK/a3EAGcc6e4AWHISnWv20iCCojsHoVlQdjrMexFF2C7UMg2A2WEGWbQhXN6l3eXC6XGp4b9qxbuEB2EBGBwtocrK90cVG5mbRXm6vmx/0phq1sIAGKDgLOBiN1MrO5a9aDl+D0W6x0Ar9BCTRuIIANa90Y7LrLVRXzwVtDInCqMRWcf2bUOEAsa4wJqFowQALL9EiAtVRk8QC4OW+1pOM9jIaVASwYagyNXDj+W0NcfuZNzjtXOiL0Zzg30Llj+ptfxQs4+vBPNiL5PawFCBkgXpUaVtqGl+A8dgZHL34BcBUQrwPptToW+o37Ku+UH9eYByJIx3YkAeFnMFuGO7S5gEp7YhXxa5OOAM39RXDPXb0qmpROsswZe+twXdU55oUIZAiEv3bD1UFwIYKkmGqytPCDCwKFQCKK0yL7qtSAPX54UAbtsLuBHkb9zyLmPQSNjsSgmQwKUOIfEY8F8t4B34DvndJY9BA8tNBJq1Nev9axmaStFcQLhgYoCTo0salkIaW8OUDdWjMTR2sHPhrAFZqx6cqcKE4pl2BJJ4K6hfwvqNgAnXfKX/HU6X3Zrhnu0k7tLNZtTBRv1hkwTDBY1NzFU6doDYjJbWdQkQhWwuU7/LvhTh3SDoco4ECL4i5dwURbc8NdDZz2IwKicE8d0KIqWetLE3+lL4hvUuGSeRfVWNLfj/gpOw4smBJBkKQHCzlHGwvAj4woB1gq5NGGLSXtORBPnUQPV5/MPVkDMxbpwG7w4x0xL6Ltxka0A/4NBvV09UVk4DoSn/jl2+JQS9q9KYawisAD4CfhsZ4TH3htylsdEHARIQBusqCKyUpymycgbbkkXEXjT3z7/oKQFTFVuZD2FMJHZIDsO5x2d4aAr2jR+GLwZhtAb028/0yJ9J8dE87jQyKObcjtTXT8dH+fDuKF4/eiPwzH44wTf/yUi6wrpRIOZ9lM1EtXAifFI+CJn9+iX/t2xMQwOMth/UZbASi8btAwR9FHWSpJr75g9Oqbin3VDg+SpwlP6k6TB4ex/7JvmcJx8jydy6XPk8eFTKhyfwCgX71MSvaBHgAAAABJRU5ErkJggg==);
}
.phpdebugbar a.phpdebugbar-restore-btn {
width: 13px;
}
.phpdebugbar a.phpdebugbar-tab.phpdebugbar-active {
background: #3DB9EC;
color: #fff;
margin-top: -1px;
padding-top: 6px;
}
.phpdebugbar .phpdebugbar-widgets-toolbar {
border-top: 1px solid #ddd;
padding-left: 5px;
padding-right: 2px;
padding-top: 2px;
background-color: #fafafa !important;
width: auto !important;
left: 0;
right: 0;
}
.phpdebugbar .phpdebugbar-widgets-toolbar input {
background: transparent !important;
}
.phpdebugbar .phpdebugbar-widgets-toolbar .phpdebugbar-widgets-filter {
}
.phpdebugbar input[type=text] {
padding: 0;
display: inline;
}
.phpdebugbar dl.phpdebugbar-widgets-varlist, ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
font-size: 12px;
}
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
top: 0;
}
.phpdebugbar pre, .phpdebugbar code {
margin: 0;
font-size: 14px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

View File

@@ -105,3 +105,15 @@ form:
validate:
type: commalist
sanitize_svg:
type: toggle
label: PLUGIN_ADMIN.SANITIZE_SVG
help: PLUGIN_ADMIN.SANITIZE_SVG_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
default: true
validate:
type: bool

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
title: Flex Accounts
description: Manage your User Accounts in Flex.
type: flex-objects
extends@:
type: account
context: blueprints://user
config:
admin:
menu:
list:
route: '/accounts'
title: PLUGIN_ADMIN.ACCOUNTS
icon: fa-users
authorize: ['admin.users', 'admin.accounts', 'admin.super']
priority: 6
template: grav-accounts
list:
fields:
username:
link: edit
search: true
email:
search: true
fullname:
search: true
options:
per_page: 20
order:
by: username
dir: asc
site:
hidden: true
templates:
collection:
paths:
- 'flex/{TYPE}/collection/{LAYOUT}{EXT}'
object:
paths:
- 'flex/{TYPE}/object/{LAYOUT}{EXT}'
defaults:
type: accounts
layout: default
data:
object: 'Grav\Common\User\FlexUser\User'
collection: 'Grav\Common\User\FlexUser\UserCollection'
index: 'Grav\Common\User\FlexUser\UserIndex'
storage:
class: 'Grav\Common\User\FlexUser\Storage\UserFileStorage'
options:
formatter:
class: 'Grav\Framework\File\Formatter\YamlFormatter'
folder: 'account://'
pattern: '{FOLDER}/{KEY}{EXT}'
key: storage_key
indexed: true
search:
options:
contains: 1
fields:
- key
- email
form:
fields:
username:
flex-disabled@: exists
disabled: false
flex-readonly@: exists
readonly: false
validate:
required: true

View File

@@ -0,0 +1,139 @@
title: Flex Pages
description: Manage your Grav Pages in Flex.
type: flex-objects
extends@:
type: default
context: blueprints://pages
config:
admin:
menu:
list:
route: '/pages'
title: PLUGIN_ADMIN.PAGES
icon: fa-file-text
authorize: ['admin.pages', 'admin.super']
priority: 5
template: grav-pages
list:
fields:
published:
width: 8
alias: header.published
visible:
width: 8
field:
label: Visible
type: toggle
menu:
link: edit
alias: header.menu
full_route:
field:
label: Route
type: text
link: edit
sort:
field: key
name:
width: 8
field:
label: Type
type: text
translations:
width: 8
field:
label: Translations
type: text
# updated_date:
# alias: header.update_date
options:
per_page: 20
order:
by: key
dir: asc
# TODO: not used yet
buttons:
back:
icon: reply
title: PLUGIN_ADMIN.BACK
add:
icon: plus
label: PLUGIN_ADMIN.ADD
edit:
# TODO: not used yet
buttons:
back:
icon: reply
title: PLUGIN_ADMIN.BACK
preview:
icon: eye
title: PLUGIN_ADMIN.PREVIEW
add:
icon: plus
label: PLUGIN_ADMIN.ADD
copy:
icon: copy
label: PLUGIN_ADMIN.COPY
move:
icon: arrows
label: PLUGIN_ADMIN.MOVE
delete:
icon: close
label: PLUGIN_ADMIN.DELETE
save:
icon: check
label: PLUGIN_ADMIN.SAVE
# Preview View
preview:
enabled: true
site:
hidden: true
templates:
collection:
paths:
- 'flex/{TYPE}/collection/{LAYOUT}{EXT}'
object:
paths:
- 'flex/{TYPE}/object/{LAYOUT}{EXT}'
defaults:
type: pages
layout: default
filter:
- withPublished
data:
object: 'Grav\Common\Page\Flex\PageObject'
collection: 'Grav\Common\Page\Flex\PageCollection'
index: 'Grav\Common\Page\Flex\PageIndex'
storage:
class: 'Grav\Common\Page\Flex\PageStorage'
options:
formatter:
class: 'Grav\Framework\File\Formatter\MarkdownFormatter'
folder: 'page://'
indexed: true
ordering:
key: ASC
search:
options:
contains: 1
fields:
- key
- menu
- title
- name
form:
fields:
lang:
type: hidden
value: ''

View File

@@ -1,39 +0,0 @@
title: User Accounts
description: User Accounts
type: flex-objects
extends@: 'user/account'
config:
admin:
list:
fields:
username:
link: edit
search: true
email:
search: true
fullname:
search: true
options:
per_page: 20
order:
by: username
dir: asc
menu:
list:
route: '/accounts'
title: Accounts
icon: fa-users
authorize: ['admin.users', 'admin.accounts', 'admin.super']
form:
fields:
username:
flex-disabled@: exists
disabled: false
flex-readonly@: exists
readonly: false
validate:
required: true

View File

@@ -36,3 +36,4 @@ uploads_dangerous_extensions:
- htm
- js
- exe
sanitize_svg: true

View File

@@ -27,6 +27,7 @@ home:
hide_in_urls: false # Hide the home route in URLs
pages:
type: page # EXPERIMENTAL: Page type: page or flex
theme: quark # Default theme (defaults to "quark" theme)
order:
by: default # Order pages by "default", "alpha" or "date"
@@ -125,6 +126,8 @@ log:
debugger:
enabled: false # Enable Grav debugger and following settings
provider: clockwork # Debugger provider: debugbar | clockwork
censored: false # Censor potentially sensitive information (POST parameters, cookies, files, configuration and most array/object data in log messages)
shutdown:
close_connection: true # Close the connection before calling onShutdown(). false for debugging
@@ -154,15 +157,15 @@ session:
path:
gpm:
releases: stable # Set to either 'stable' or 'testing'
releases: testing # 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.
official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
accounts:
type: data # Account type: data or flex
storage: file # Flex storage type: file or folder
type: data # EXPERIMENTAL: Account type: data or flex
storage: file # EXPERIMENTAL: Flex storage type: file or folder
strict_mode:
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility

View File

@@ -8,8 +8,8 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.6.13');
define('GRAV_TESTING', false);
define('GRAV_VERSION', '1.7.0-rc.1');
define('GRAV_TESTING', true);
define('DS', '/');
if (!defined('GRAV_PHP_MIN')) {

View File

@@ -14,4 +14,4 @@ use Grav\Installer\Install;
require_once __DIR__ . '/src/Grav/Installer/Install.php';
return Install::instance();
return Install::instance();

View File

@@ -20,9 +20,9 @@ if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_N
$grav_index = 'index.php';
/* Check the GRAV_BASEDIR environment variable and use if set */
$grav_basedir = getenv('GRAV_BASEDIR') ?: '';
if (isset($grav_basedir)) {
$grav_basedir = getenv('GRAV_BASEDIR') ?: '';
if ($grav_basedir) {
$grav_index = ltrim($grav_basedir, '/') . DIRECTORY_SEPARATOR . $grav_index;
$grav_basedir = DIRECTORY_SEPARATOR . trim($grav_basedir, DIRECTORY_SEPARATOR);
define('GRAV_ROOT', str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . $grav_basedir);

View File

@@ -202,7 +202,6 @@ class Assets extends PropertyObject
}
return $this;
}
/**
@@ -212,7 +211,7 @@ class Assets extends PropertyObject
*/
public function addCss($asset)
{
return $this->addType(Assets::CSS_COLLECTION,Assets::CSS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::CSS_TYPE));
return $this->addType(Assets::CSS_COLLECTION, Assets::CSS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::CSS_TYPE));
}
/**
@@ -266,13 +265,12 @@ class Assets extends PropertyObject
protected function filterAssets($assets, $key, $value, $sort = false)
{
$results = array_filter($assets, function($asset) use ($key, $value) {
$results = array_filter($assets, function ($asset) use ($key, $value) {
if ($key === 'position' && $value === 'pipeline') {
$type = $asset->getType();
if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline' ) {
if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline') {
if ($this->{$type . '_pipeline_before_excludes'}) {
$asset->setPosition('after');
} else {
@@ -280,10 +278,11 @@ class Assets extends PropertyObject
}
return false;
}
}
if ($asset[$key] === $value) return true;
if ($asset[$key] === $value) {
return true;
}
return false;
});
@@ -297,7 +296,7 @@ class Assets extends PropertyObject
protected function sortAssets($assets)
{
uasort ($assets, function($a, $b) {
uasort($assets, function ($a, $b) {
if ($a['priority'] == $b['priority']) {
return $a['order'] - $b['order'];
}

View File

@@ -88,7 +88,6 @@ abstract class BaseAsset extends PropertyObject
// Move this to render?
if (!$this->remote) {
$asset_parts = parse_url($asset);
if (isset($asset_parts['query'])) {
$this->query = $asset_parts['query'];
@@ -163,7 +162,7 @@ abstract class BaseAsset extends PropertyObject
*
* @param string $asset the asset string reference
*
* @return string the final link url to the asset
* @return string|false the final link url to the asset
*/
protected function buildLocalLink($asset)
{

View File

@@ -31,7 +31,7 @@ class Css extends BaseAsset
public function render()
{
if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
$buffer = $this->gatherLinks( [$this], self::CSS_ASSET);
$buffer = $this->gatherLinks([$this], self::CSS_ASSET);
return "<style>\n" . trim($buffer) . "\n</style>\n";
}

View File

@@ -27,7 +27,7 @@ class Js extends BaseAsset
public function render()
{
if (isset($this->attributes['loading']) && $this->attributes['loading'] === 'inline') {
$buffer = $this->gatherLinks( [$this], self::JS_ASSET);
$buffer = $this->gatherLinks([$this], self::JS_ASSET);
return '<script' . $this->renderAttributes() . ">\n" . trim($buffer) . "\n</script>\n";
}

View File

@@ -39,12 +39,12 @@ trait LegacyAssetsTrait
}
switch ($type) {
case(Assets::JS_TYPE):
case (Assets::JS_TYPE):
$defaults = ['priority' => null, 'pipeline' => true, 'loading' => null, 'group' => null];
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
break;
case(Assets::INLINE_JS_TYPE):
case (Assets::INLINE_JS_TYPE):
$defaults = ['priority' => null, 'group' => null, 'attributes' => null];
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
@@ -57,13 +57,13 @@ trait LegacyAssetsTrait
break;
case(Assets::INLINE_CSS_TYPE):
case (Assets::INLINE_CSS_TYPE):
$defaults = ['priority' => null, 'group' => null];
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
break;
default:
case(Assets::CSS_TYPE):
case (Assets::CSS_TYPE):
$defaults = ['priority' => null, 'pipeline' => true, 'group' => null, 'loading' => null];
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
}
@@ -121,5 +121,4 @@ trait LegacyAssetsTrait
return $this->addJs($asset, $priority, $pipeline, 'defer', $group);
}
}

View File

@@ -230,7 +230,7 @@ trait TestingAssetsTrait
* Get the timestamp for assets
*
* @param bool $include_join
* @return string
* @return string|null
*/
public function getTimestamp($include_join = true)
{
@@ -327,8 +327,10 @@ trait TestingAssetsTrait
*/
protected function rglob($directory, $pattern, $ltrim = null)
{
$iterator = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory,
\FilesystemIterator::SKIP_DOTS)), $pattern);
$iterator = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
$directory,
\FilesystemIterator::SKIP_DOTS
)), $pattern);
$offset = \strlen($ltrim);
$files = [];
@@ -338,6 +340,4 @@ trait TestingAssetsTrait
return $files;
}
}

View File

@@ -17,9 +17,9 @@ use Grav\Common\Scheduler\Scheduler;
use Grav\Common\Utils;
use Grav\Common\Grav;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
use RocketTheme\Toolbox\File\JsonFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Symfony\Component\EventDispatcher\EventDispatcher;
class Backups
{
@@ -60,7 +60,7 @@ class Backups
$name = $inflector::hyphenize($profile['name']);
$logs = 'logs/backup-' . $name . '.out';
/** @var Job $job */
$job = $scheduler->addFunction('Grav\Common\Backup\Backups::backup', [$id], $name );
$job = $scheduler->addFunction('Grav\Common\Backup\Backups::backup', [$id], $name);
$job->at($at);
$job->output($logs);
$job->backlink('/tools/backups');
@@ -71,8 +71,10 @@ class Backups
{
$param_sep = $param_sep = Grav::instance()['config']->get('system.param_sep', ':');
$download = urlencode(base64_encode($backup));
$url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim($base_url,
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
$url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim(
$base_url,
'/'
) . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
return $url;
}
@@ -113,7 +115,6 @@ class Backups
* @var \SplFileInfo $file
*/
foreach ($backups_itr as $name => $file) {
if (preg_match(static::BACKUP_FILENAME_REGEXZ, $name, $matches)) {
$date = \DateTime::createFromFormat(static::BACKUP_DATE_FORMAT, $matches[2]);
$timestamp = $date->getTimestamp();
@@ -158,7 +159,7 @@ class Backups
$date = date(static::BACKUP_DATE_FORMAT, time());
$filename = trim($name, '_') . '--' . $date . '.zip';
$destination = static::$backup_dir . DS . $filename;
$max_execution_time = ini_set('max_execution_time', 600);
$max_execution_time = ini_set('max_execution_time', '600');
$backup_root = $backup->root;
if ($locator->isStream($backup_root)) {
@@ -220,13 +221,12 @@ class Backups
$trigger = $purge_config['trigger'];
$backups = static::getAvailableBackups(true);
switch ($trigger)
{
switch ($trigger) {
case 'number':
$backups_count = count($backups);
if ($backups_count > $purge_config['max_backups_count']) {
$last = end($backups);
unlink ($last->path);
unlink($last->path);
static::purge();
}
break;

View File

@@ -108,7 +108,7 @@ class Browser
/**
* Get the current major version identifier
*
* @return string the browser major version identifier
* @return int the browser major version identifier
*/
public function getVersion()
{

View File

@@ -15,7 +15,7 @@ use Grav\Common\Filesystem\Folder;
use Grav\Common\Scheduler\Scheduler;
use Psr\SimpleCache\CacheInterface;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**
* The GravCache object is used throughout Grav to store and retrieve cached data.
@@ -260,8 +260,10 @@ class Cache extends Getters
case 'memcache':
if (extension_loaded('memcache')) {
$memcache = new \Memcache();
$memcache->connect($this->config->get('system.cache.memcache.server', 'localhost'),
$this->config->get('system.cache.memcache.port', 11211));
$memcache->connect(
$this->config->get('system.cache.memcache.server', 'localhost'),
$this->config->get('system.cache.memcache.port', 11211)
);
$driver = new DoctrineCache\MemcacheCache();
$driver->setMemcache($memcache);
} else {
@@ -272,8 +274,10 @@ class Cache extends Getters
case 'memcached':
if (extension_loaded('memcached')) {
$memcached = new \Memcached();
$memcached->addServer($this->config->get('system.cache.memcached.server', 'localhost'),
$this->config->get('system.cache.memcached.port', 11211));
$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);
} else {
@@ -290,8 +294,10 @@ class Cache extends Getters
if ($socket) {
$redis->connect($socket);
} else {
$redis->connect($this->config->get('system.cache.redis.server', 'localhost'),
$this->config->get('system.cache.redis.port', 6379));
$redis->connect(
$this->config->get('system.cache.redis.server', 'localhost'),
$this->config->get('system.cache.redis.port', 6379)
);
}
// Authenticate with password if set
@@ -319,7 +325,7 @@ class Cache extends Getters
*
* @param string $id the id of the cached entry
*
* @return object|bool returns the cached entry, can be any type, or false if doesn't exist
* @return mixed|bool returns the cached entry, can be any type, or false if doesn't exist
*/
public function fetch($id)
{
@@ -334,7 +340,7 @@ class Cache extends Getters
* Stores a new cached entry.
*
* @param string $id the id of the cached entry
* @param array|object $data the data for the cached entry to store
* @param array|object|int $data the data for the cached entry to store
* @param int $lifetime the lifetime to store the entry in seconds
*/
public function save($id, $data, $lifetime = null)
@@ -446,7 +452,6 @@ class Cache extends Getters
} else {
$remove_paths = self::$standard_remove_no_images;
}
}
// Delete entries in the doctrine cache if required
@@ -459,11 +464,12 @@ class Cache extends Getters
Grav::instance()->fireEvent('onBeforeCacheClear', new Event(['remove' => $remove, 'paths' => &$remove_paths]));
foreach ($remove_paths as $stream) {
// Convert stream to a real path
try {
$path = $locator->findResource($stream, true, true);
if($path === false) continue;
if ($path === false) {
continue;
}
$anything = false;
$files = glob($path . '/*');
@@ -528,7 +534,6 @@ class Cache extends Getters
if (function_exists('opcache_reset')) {
@opcache_reset();
}
}
/**
@@ -634,7 +639,7 @@ class Cache extends Getters
$name = 'cache-purge';
$logs = 'logs/' . $name . '.out';
$job = $scheduler->addFunction('Grav\Common\Cache::purgeJob', [], $name );
$job = $scheduler->addFunction('Grav\Common\Cache::purgeJob', [], $name);
$job->at($at);
$job->output($logs);
$job->backlink('/config/system#caching');
@@ -645,12 +650,9 @@ class Cache extends Getters
$name = 'cache-clear';
$logs = 'logs/' . $name . '.out';
$job = $scheduler->addFunction('Grav\Common\Cache::clearJob', [$clear_type], $name );
$job = $scheduler->addFunction('Grav\Common\Cache::clearJob', [$clear_type], $name);
$job->at($at);
$job->output($logs);
$job->backlink('/config/system#caching');
}
}

View File

@@ -26,7 +26,7 @@ class Composer
}
// check for global composer install
$path = trim(shell_exec('command -v composer'));
$path = trim((string)shell_exec('command -v composer'));
// fall back to grav bundled composer
if (!$path || !preg_match('/(composer|composer\.phar)$/', $path)) {

View File

@@ -29,7 +29,7 @@ abstract class CompiledBase
public $checksum;
/**
* @var string Timestamp of compiled configuration
* @var int Timestamp of compiled configuration
*/
public $timestamp;
@@ -89,7 +89,9 @@ abstract class CompiledBase
/**
* Function gets called when cached configuration is saved.
*/
public function modified() {}
public function modified()
{
}
/**
* Get timestamp of compiled configuration
@@ -157,7 +159,7 @@ abstract class CompiledBase
* Load single configuration file and append it to the correct position.
*
* @param string $name Name of the position.
* @param string $filename File to be loaded.
* @param string|string[] $filename File(s) to be loaded.
*/
abstract protected function loadFile($name, $filename);
@@ -197,8 +199,7 @@ abstract class CompiledBase
}
$cache = include $filename;
if (
!\is_array($cache)
if (!\is_array($cache)
|| !isset($cache['checksum'], $cache['data'], $cache['@class'])
|| $cache['@class'] !== \get_class($this)
) {

View File

@@ -25,7 +25,10 @@ class Blueprint extends BlueprintForm
/** @var BlueprintSchema */
protected $blueprintSchema;
/** @var array */
/** @var object */
protected $object;
/** @var array|null */
protected $defaults;
protected $handlers = [];
@@ -42,6 +45,11 @@ class Blueprint extends BlueprintForm
$this->scope = $scope;
}
public function setObject($object)
{
$this->object = $object;
}
/**
* Set default values for field types.
*
@@ -111,6 +119,7 @@ class Blueprint extends BlueprintForm
foreach ($data as $property => $call) {
$action = $call['action'];
$method = 'dynamic' . ucfirst($action);
$call['object'] = $this->object;
if (isset($this->handlers[$action])) {
$callable = $this->handlers[$action];

View File

@@ -54,7 +54,6 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{
try {
$messages = $this->validateArray($data, $this->nested);
} catch (\RuntimeException $e) {
throw (new ValidationException($e->getMessage(), $e->getCode(), $e))->setMessages();
}
@@ -201,7 +200,6 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
} elseif (\is_array($field) && \is_array($val)) {
// Array has been defined in blueprints.
$field = $this->filterArray($field, $val, $missingValuesAsNull, $keepEmptyValues);
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
// Skip any extra data.
continue;
@@ -244,8 +242,8 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|| !empty($field['disabled'])
// Field validation is set to be ignored
|| !empty($field['validate']['ignore'])
// Field is toggleable and the toggle is turned off
|| (!empty($field['toggleable']) && empty($toggles[$key]))
// Field is overridable and the toggle is turned off
|| (!empty($field['overridable']) && empty($toggles[$key]))
) {
continue;
}
@@ -279,10 +277,15 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
continue;
}
// Skip overridable fields without value.
// TODO: We need better overridable support, which is not just ignoring required values but also looking if defaults are good.
if (!empty($field['overridable']) && !isset($data[$name])) {
continue;
}
// Check if required.
if (isset($field['validate']['required'])
&& $field['validate']['required'] === true) {
if (isset($data[$name])) {
continue;
}

View File

@@ -34,7 +34,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
/**
* @param array $items
* @param Blueprint|callable $blueprints
* @param Blueprint|callable|null $blueprints
*/
public function __construct(array $items = [], $blueprints = null)
{
@@ -227,7 +227,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
*/
public function blueprints()
{
if (!$this->blueprints){
if (!$this->blueprints) {
$this->blueprints = new Blueprint;
} elseif (\is_callable($this->blueprints)) {
// Lazy load blueprints.
@@ -280,7 +280,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
/**
* Set or get the data storage.
*
* @param FileInterface $storage Optionally enter a new storage.
* @param FileInterface|null $storage Optionally enter a new storage.
* @return FileInterface
*/
public function file(FileInterface $storage = null)

View File

@@ -63,7 +63,7 @@ interface DataInterface
/**
* Set or get the data storage.
*
* @param FileInterface $storage Optionally enter a new storage.
* @param FileInterface|null $storage Optionally enter a new storage.
* @return FileInterface
*/
public function file(FileInterface $storage = null);

View File

@@ -674,7 +674,6 @@ class Validation
}
return (array) Yaml::parse($value);
}
/**
@@ -794,7 +793,9 @@ class Validation
public static function filterItem_List($value, $params)
{
return array_values(array_filter($value, function($v) { return !empty($v); } ));
return array_values(array_filter($value, function ($v) {
return !empty($v);
}));
}
public static function validateJson($value, $params)

View File

@@ -15,7 +15,8 @@ class ValidationException extends \RuntimeException
{
protected $messages = [];
public function setMessages(array $messages = []) {
public function setMessages(array $messages = [])
{
$this->messages = $messages;
$language = Grav::instance()['language'];

View File

@@ -9,6 +9,13 @@
namespace Grav\Common;
use Clockwork\Clockwork;
use Clockwork\DataSource\MonologDataSource;
use Clockwork\DataSource\PsrMessageDataSource;
use Clockwork\DataSource\XdebugDataSource;
use Clockwork\Helpers\ServerTiming;
use Clockwork\Request\UserData;
use Clockwork\Storage\FileStorage;
use DebugBar\DataCollector\ConfigCollector;
use DebugBar\DataCollector\DataCollectorInterface;
use DebugBar\DataCollector\ExceptionsCollector;
@@ -22,11 +29,22 @@ use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
use Grav\Common\Config\Config;
use Grav\Common\Processors\ProcessorInterface;
use Grav\Common\Twig\TwigClockworkDataSource;
use Grav\Framework\Psr7\Response;
use Monolog\Logger;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RocketTheme\Toolbox\Event\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Twig\Template;
use Twig\TemplateWrapper;
class Debugger
{
/** @var static */
protected static $instance;
/** @var Grav $grav */
protected $grav;
@@ -39,8 +57,11 @@ class Debugger
/** @var StandardDebugBar $debugbar */
protected $debugbar;
/** @var Clockwork */
protected $clockwork;
/** @var bool */
protected $enabled;
protected $enabled = false;
protected $initialized = false;
@@ -50,39 +71,41 @@ class Debugger
/** @var array $deprecations */
protected $deprecations = [];
/** @var callable */
/** @var callable|null */
protected $errorHandler;
protected $requestTime;
protected $currentTime;
/** @var int */
protected $profiling = 0;
protected $censored = false;
/**
* Debugger constructor.
*/
public function __construct()
{
$currentTime = microtime(true);
static::$instance = $this;
$this->currentTime = microtime(true);
if (!\defined('GRAV_REQUEST_TIME')) {
\define('GRAV_REQUEST_TIME', $currentTime);
\define('GRAV_REQUEST_TIME', $this->currentTime);
}
// Enable debugger until $this->init() gets called.
$this->enabled = true;
$debugbar = new DebugBar();
$debugbar->addCollector(new PhpInfoCollector());
$debugbar->addCollector(new MessagesCollector());
$debugbar->addCollector(new RequestDataCollector());
$debugbar->addCollector(new TimeDataCollector($_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME));
$debugbar['time']->addMeasure('Server', $debugbar['time']->getRequestStartTime(), GRAV_REQUEST_TIME);
$debugbar['time']->addMeasure('Loading', GRAV_REQUEST_TIME, $currentTime);
$debugbar['time']->addMeasure('Debugger', $currentTime, microtime(true));
$this->debugbar = $debugbar;
$this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME;
// Set deprecation collector.
$this->setErrorHandler();
}
public function getClockwork(): ?Clockwork
{
return $this->enabled ? $this->clockwork : null;
}
/**
* Initialize the debugger
*
@@ -100,25 +123,236 @@ class Debugger
// Enable/disable debugger based on configuration.
$this->enabled = (bool)$this->config->get('system.debugger.enabled');
$this->censored = (bool)$this->config->get('system.debugger.censored', false);
if ($this->enabled()) {
if ($this->enabled) {
$this->initialized = true;
$plugins_config = (array)$this->config->get('plugins');
$clockwork = $debugbar = null;
switch ($this->config->get('system.debugger.provider', 'debugbar')) {
case 'clockwork':
$this->clockwork = $clockwork = new Clockwork();
break;
default:
$this->debugbar = $debugbar = new DebugBar();
}
$plugins_config = (array)$this->config->get('plugins');
ksort($plugins_config);
$debugbar = $this->debugbar;
$debugbar->addCollector(new MemoryCollector());
$debugbar->addCollector(new ExceptionsCollector());
$debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
$debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
$this->addMessage('Grav v' . GRAV_VERSION);
if ($clockwork) {
$log = $this->grav['log'];
$clockwork->setStorage(new FileStorage('cache://clockwork'));
if (extension_loaded('xdebug')) {
$clockwork->addDataSource(new XdebugDataSource());
}
if ($log instanceof Logger) {
$clockwork->addDataSource(new MonologDataSource($log));
}
$clockwork->addDataSource(new TwigClockworkDataSource());
$timeLine = $clockwork->getTimeline();
if ($this->requestTime !== GRAV_REQUEST_TIME) {
$timeLine->addEvent('server', 'Server', $this->requestTime, GRAV_REQUEST_TIME);
}
if ($this->currentTime !== GRAV_REQUEST_TIME) {
$timeLine->addEvent('loading', 'Loading', GRAV_REQUEST_TIME, $this->currentTime);
}
$timeLine->addEvent('setup', 'Site Setup', $this->currentTime, microtime(true));
}
if ($this->censored) {
$censored = ['CENSORED' => true];
}
if ($debugbar) {
$debugbar->addCollector(new PhpInfoCollector());
$debugbar->addCollector(new MessagesCollector());
if (!$this->censored) {
$debugbar->addCollector(new RequestDataCollector());
}
$debugbar->addCollector(new TimeDataCollector($this->requestTime));
$debugbar->addCollector(new MemoryCollector());
$debugbar->addCollector(new ExceptionsCollector());
$debugbar->addCollector(new ConfigCollector($censored ?? (array)$this->config->get('system'), 'Config'));
$debugbar->addCollector(new ConfigCollector($censored ?? $plugins_config, 'Plugins'));
$debugbar->addCollector(new ConfigCollector($this->config->get('streams.schemes'), 'Streams'));
if ($this->requestTime !== GRAV_REQUEST_TIME) {
$debugbar['time']->addMeasure('Server', $debugbar['time']->getRequestStartTime(), GRAV_REQUEST_TIME);
}
if ($this->currentTime !== GRAV_REQUEST_TIME) {
$debugbar['time']->addMeasure('Loading', GRAV_REQUEST_TIME, $this->currentTime);
}
$debugbar['time']->addMeasure('Site Setup', $this->currentTime, microtime(true));
}
$this->addMessage('Grav v' . GRAV_VERSION . ' - PHP ' . PHP_VERSION);
$this->config->debug();
if ($clockwork) {
$clockwork->info('System Configuration', $censored ?? $this->config->get('system'));
$clockwork->info('Plugins Configuration', $censored ?? $plugins_config);
$clockwork->info('Streams', $this->config->get('streams.schemes'));
}
}
return $this;
}
public function finalize(): void
{
if ($this->clockwork && $this->enabled) {
$this->stopProfiling('Profiler Analysis');
$this->addMeasures();
$deprecations = $this->getDeprecations();
$count = count($deprecations);
if (!$count) {
return;
}
/** @var UserData $userData */
$userData = $this->clockwork->userData('Deprecated');
$userData->counters([
'Deprecated' => count($deprecations)
]);
foreach ($deprecations as &$deprecation) {
if (0) {
$d = $deprecation;
unset($d['message']);
$this->clockwork->log('deprecated', $deprecation['message'], $d);
}
}
unset($deprecation);
$userData->table('Your site is using following deprecated features', $deprecations);
}
}
public function logRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
if (!$this->enabled || !$this->clockwork) {
return $response;
}
$clockwork = $this->clockwork;
$this->finalize();
$clockwork->getTimeline()->finalize($request->getAttribute('request_time'));
if ($this->censored) {
$censored = 'CENSORED';
$request = $request
->withCookieParams([$censored => ''])
->withUploadedFiles([])
->withHeader('cookie', $censored);
if ($request->getBody()) {
$request = $request->withParsedBody([$censored => '']);
}
}
$clockwork->addDataSource(new PsrMessageDataSource($request, $response));
$clockwork->resolveRequest();
$clockwork->storeRequest();
$clockworkRequest = $clockwork->getRequest();
$response = $response
->withHeader('X-Clockwork-Id', $clockworkRequest->id)
->withHeader('X-Clockwork-Version', $clockwork::VERSION);
$basePath = Grav::instance()['uri']->rootUrl();
if ($basePath) {
$response = $response->withHeader('X-Clockwork-Path', $basePath . '/__clockwork/');
}
return $response->withHeader('Server-Timing', ServerTiming::fromRequest($clockworkRequest)->value());
}
public function debuggerRequest(RequestInterface $request): Response
{
$clockwork = $this->clockwork;
$headers = [
'Content-Type' => 'application/json',
'Grav-Internal-SkipShutdown' => 1
];
$path = $request->getUri()->getPath();
$clockworkDataUri = '#/__clockwork(?:/(?<id>[0-9-]+))?(?:/(?<direction>(?:previous|next)))?(?:/(?<count>\d+))?#';
if (preg_match($clockworkDataUri, $path, $matches) === false) {
$response = ['message' => 'Bad Input'];
return new Response(400, $headers, json_encode($response));
}
$id = $matches['id'] ?? null;
$direction = $matches['direction'] ?? null;
$count = $matches['count'] ?? null;
$storage = $clockwork->getStorage();
if ($direction === 'previous') {
$data = $storage->previous($id, $count);
} elseif ($direction === 'next') {
$data = $storage->next($id, $count);
} elseif ($id === 'latest') {
$data = $storage->latest();
} else {
$data = $storage->find($id);
}
if (preg_match('#(?<id>[0-9-]+|latest)/extended#', $path)) {
$clockwork->extendRequest($data);
}
if (!$data) {
$response = ['message' => 'Not Found'];
return new Response(404, $headers, json_encode($response));
}
$data = is_array($data) ? array_map(function ($item) {
return $item->toArray();
}, $data) : $data->toArray();
return new Response(200, $headers, json_encode($data));
}
protected function addMeasures()
{
if (!$this->enabled) {
return;
}
$nowTime = microtime(true);
$clkTimeLine = $this->clockwork ? $this->clockwork->getTimeline() : null;
$debTimeLine = $this->debugbar ? $this->debugbar['time'] : null;
foreach ($this->timers as $name => $data) {
$description = $data[0];
$startTime = $data[1] ?? null;
$endTime = $data[2] ?? $nowTime;
if ($endTime - $startTime < 0.001) {
continue;
}
if ($clkTimeLine) {
$clkTimeLine->addEvent($name, $description ?? $name, $startTime, $endTime);
}
if ($debTimeLine) {
$debTimeLine->addMeasure($description ?? $name, $startTime, $endTime);
}
}
$this->timers = [];
}
/**
* Set/get the enabled state of the debugger
*
@@ -142,8 +376,7 @@ class Debugger
*/
public function addAssets()
{
if ($this->enabled()) {
if ($this->enabled) {
// Only add assets if Page is HTML
$page = $this->grav['page'];
if ($page->templateFormat() !== 'html') {
@@ -153,22 +386,32 @@ class Debugger
/** @var Assets $assets */
$assets = $this->grav['assets'];
// Add jquery library
$assets->add('jquery', 101);
$this->renderer = $this->debugbar->getJavascriptRenderer();
$this->renderer->setIncludeVendors(false);
// Get the required CSS files
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
foreach ((array)$css_files as $css) {
$assets->addCss($css);
// Clockwork specific assets
if ($this->clockwork) {
$assets->addCss('/system/assets/debugger/clockwork.css', ['loading' => 'inline']);
$assets->addJs('/system/assets/debugger/clockwork.js', ['loading' => 'inline']);
}
$assets->addCss('/system/assets/debugger.css');
foreach ((array)$js_files as $js) {
$assets->addJs($js);
// Debugbar specific assets
if ($this->debugbar) {
// Add jquery library
$assets->add('jquery', 101);
$this->renderer = $this->debugbar->getJavascriptRenderer();
$this->renderer->setIncludeVendors(false);
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
foreach ((array)$css_files as $css) {
$assets->addCss($css);
}
$assets->addCss('/system/assets/debugger/phpdebugbar.css', ['loading' => 'inline']);
foreach ((array)$js_files as $js) {
$assets->addJs($js);
}
}
}
@@ -192,7 +435,9 @@ class Debugger
*/
public function addCollector($collector)
{
$this->debugbar->addCollector($collector);
if ($this->debugbar && !$this->debugbar->hasCollector($collector->getName())) {
$this->debugbar->addCollector($collector);
}
return $this;
}
@@ -200,14 +445,18 @@ class Debugger
/**
* Returns a data collector
*
* @param DataCollectorInterface $collector
* @param string $name
*
* @return DataCollectorInterface
* @throws \DebugBar\DebugBarException
*/
public function getCollector($collector)
public function getCollector($name)
{
return $this->debugbar->getCollector($collector);
if ($this->debugbar && $this->debugbar->hasCollector($name)) {
return $this->debugbar->getCollector($name);
}
return null;
}
/**
@@ -217,13 +466,14 @@ class Debugger
*/
public function render()
{
if ($this->enabled()) {
if ($this->enabled && $this->debugbar) {
// Only add assets if Page is HTML
$page = $this->grav['page'];
if (!$this->renderer || $page->templateFormat() !== 'html') {
return $this;
}
$this->addMeasures();
$this->addDeprecations();
echo $this->renderer->render();
@@ -239,7 +489,8 @@ class Debugger
*/
public function sendDataInHeaders()
{
if ($this->enabled()) {
if ($this->enabled && $this->debugbar) {
$this->addMeasures();
$this->addDeprecations();
$this->debugbar->sendDataInHeaders();
}
@@ -250,20 +501,150 @@ class Debugger
/**
* Returns collected debugger data.
*
* @return array
* @return array|null
*/
public function getData()
{
if (!$this->enabled()) {
if (!$this->enabled || !$this->debugbar) {
return null;
}
$this->addMeasures();
$this->addDeprecations();
$this->timers = [];
return $this->debugbar->getData();
}
/**
* Hierarchical Profiler support.
*
* @param callable $callable
* @param string $message
* @return mixed
*/
public function profile(callable $callable, string $message = null)
{
$this->startProfiling();
$response = $callable();
$this->stopProfiling($message);
return $response;
}
/**
* Start profiling code.
*/
public function startProfiling(): void
{
if ($this->enabled && extension_loaded('tideways_xhprof')) {
$this->profiling++;
if ($this->profiling === 1) {
\tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_NO_BUILTINS);
}
}
}
/**
* Stop profiling code. Returns profiling array or null if profiling couldn't be done.
*
* @param string $message
* @return array|null
*/
public function stopProfiling(string $message = null): ?array
{
$timings = null;
if ($this->enabled && extension_loaded('tideways_xhprof')) {
$profiling = $this->profiling - 1;
if ($profiling === 0) {
$timings = \tideways_xhprof_disable();
$timings = $this->buildProfilerTimings($timings);
if ($this->clockwork) {
/** @var UserData $userData */
$userData = $this->clockwork->userData('Profiler');
$userData->counters([
'Calls' => count($timings)
]);
$userData->table('Profiler', $timings);
} else {
$this->addMessage($message ?? 'Profiler Analysis', 'debug', $timings);
}
}
$this->profiling = max(0, $profiling);
}
return $timings;
}
protected function buildProfilerTimings(array $timings): array
{
// Filter method calls which take almost no time.
$timings = array_filter($timings, function ($value) {
return $value['wt'] > 50;
});
uasort($timings, function (array $a, array $b) {
return $b['wt'] <=> $a['wt'];
});
$table = [];
foreach ($timings as $key => $timing) {
$parts = explode('==>', $key);
$method = $this->parseProfilerCall(array_pop($parts));
$context = $this->parseProfilerCall(array_pop($parts));
// Skip redundant method calls.
if ($context === 'Grav\Framework\RequestHandler\RequestHandler::handle()') {
continue;
}
// Do not profile library calls.
if (strpos($context, 'Grav\\') !== 0) {
continue;
}
$table[] = [
'Context' => $context,
'Method' => $method,
'Calls' => $timing['ct'],
'Time (ms)' => $timing['wt'] / 1000,
];
}
return $table;
}
protected function parseProfilerCall(?string $call)
{
if (null === $call) {
return '';
}
if (strpos($call, '@')) {
[$call,] = explode('@', $call);
}
if (strpos($call, '::')) {
[$class, $call] = explode('::', $call);
}
if (!isset($class)) {
return $call;
}
// It is also possible to display twig files, but they are being logged in views.
/*
if (strpos($class, '__TwigTemplate_') === 0 && class_exists($class)) {
$env = new Environment();
/ ** @var Template $template * /
$template = new $class($env);
return $template->getTemplateName();
}
*/
return "{$class}::{$call}()";
}
/**
* Start a timer with an associated name and description
*
@@ -274,10 +655,7 @@ class Debugger
*/
public function startTimer($name, $description = null)
{
if (strpos($name, '_') === 0 || $this->enabled()) {
$this->debugbar['time']->startMeasure($name, $description);
$this->timers[] = $name;
}
$this->timers[$name] = [$description, microtime(true)];
return $this;
}
@@ -291,8 +669,9 @@ class Debugger
*/
public function stopTimer($name)
{
if (\in_array($name, $this->timers, true) && (strpos($name, '_') === 0 || $this->enabled())) {
$this->debugbar['time']->stopMeasure($name);
if (isset($this->timers[$name])) {
$endTime = microtime(true);
$this->timers[$name][] = $endTime;
}
return $this;
@@ -303,14 +682,60 @@ class Debugger
*
* @param mixed $message
* @param string $label
* @param bool $isString
* @param mixed|bool $isString
*
* @return $this
*/
public function addMessage($message, $label = 'info', $isString = true)
{
if ($this->enabled()) {
$this->debugbar['messages']->addMessage($message, $label, $isString);
if ($this->enabled) {
if ($this->censored) {
if (!is_scalar($message)) {
$message = 'CENSORED';
}
if (!is_scalar($isString)) {
$isString = ['CENSORED'];
}
}
if ($this->debugbar) {
if (is_array($isString)) {
$message = $isString;
$isString = false;
} elseif (is_string($isString)) {
$message = $isString;
$isString = true;
}
$this->debugbar['messages']->addMessage($message, $label, $isString);
}
if ($this->clockwork) {
if (!is_scalar($message)) {
$isString = $message;
$message = '';
} elseif (is_bool($isString)) {
$isString = [];
}
if (!is_array($isString)) {
$isString = [gettype($isString) => $isString];
}
$this->clockwork->log($label, $message, $isString);
}
}
return $this;
}
public function addEvent(string $name, ?Event $event, EventDispatcherInterface $dispatcher)
{
if ($this->enabled) {
if ($this->clockwork) {
$listeners = [];
foreach ($dispatcher->getListeners($name) as $listener) {
$listeners[] = $this->resolveCallable($listener);
}
$this->clockwork->addEvent($name, null, microtime(true), ['listeners' => $listeners]);
}
}
return $this;
@@ -319,13 +744,23 @@ class Debugger
/**
* Dump exception into the Messages tab of the Debug Bar
*
* @param \Exception $e
* @param \Throwable $e
* @return Debugger
*/
public function addException(\Exception $e)
public function addException(\Throwable $e)
{
if ($this->initialized && $this->enabled()) {
$this->debugbar['exceptions']->addException($e);
if ($this->initialized && $this->enabled) {
if ($this->debugbar) {
$this->debugbar['exceptions']->addException($e);
}
if ($this->clockwork) {
/** @var UserData $exceptions */
$exceptions = $this->clockwork->userData('Exceptions');
$exceptions->data(['message' => $e->getMessage()]);
$this->clockwork->alert($e->getMessage(), ['exception' => $e]);
}
}
return $this;
@@ -355,7 +790,7 @@ class Debugger
return true;
}
if (!$this->enabled()) {
if (!$this->enabled) {
return true;
}
@@ -540,6 +975,21 @@ class Debugger
return true;
}
protected function getDeprecations(): array
{
if (!$this->deprecations) {
return [];
}
$list = [];
/** @var array $deprecated */
foreach ($this->deprecations as $deprecated) {
$list[] = $this->getDepracatedMessage($deprecated)[0];
}
return $list;
}
protected function addDeprecations()
{
if (!$this->deprecations) {
@@ -603,4 +1053,13 @@ class Debugger
return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
}
protected function resolveCallable(callable $callable)
{
if (is_array($callable)) {
return get_class($callable[0]) . '->' . $callable[1] . '()';
}
return 'unknown';
}
}

View File

@@ -21,12 +21,10 @@ class BareHandler extends Handler
{
$inspector = $this->getInspector();
$code = $inspector->getException()->getCode();
if ( ($code >= 400) && ($code < 600) )
{
$this->getRun()->sendHttpCode($code);
if (($code >= 400) && ($code < 600)) {
$this->getRun()->sendHttpCode($code);
}
return Handler::QUIT;
}
}

View File

@@ -40,23 +40,23 @@ class Errors
$error_page->setPageTitle('Crikey! There was an error...');
$error_page->addResourcePath(GRAV_ROOT . '/system/assets');
$error_page->addCustomCss('whoops.css');
$whoops->pushHandler($error_page);
$whoops->prependHandler($error_page);
break;
case -1:
$whoops->pushHandler(new BareHandler);
$whoops->prependHandler(new BareHandler);
break;
default:
$whoops->pushHandler(new SimplePageHandler);
$whoops->prependHandler(new SimplePageHandler);
break;
}
if (Whoops\Util\Misc::isAjaxRequest() || $jsonRequest) {
$whoops->pushHandler(new Whoops\Handler\JsonResponseHandler);
$whoops->prependHandler(new Whoops\Handler\JsonResponseHandler);
}
if (isset($config['log']) && $config['log']) {
$logger = $grav['log'];
$whoops->pushHandler(function($exception, $inspector, $run) use ($logger) {
$whoops->prependHandler(function ($exception, $inspector, $run) use ($logger) {
try {
$logger->addCritical($exception->getMessage() . ' - Trace: ' . $exception->getTraceAsString());
} catch (\Exception $e) {

View File

@@ -36,9 +36,8 @@ class SimplePageHandler extends Handler
$cssFile = $this->getResource('error.css');
$code = $inspector->getException()->getCode();
if ( ($code >= 400) && ($code < 600) )
{
$this->getRun()->sendHttpCode($code);
if (($code >= 400) && ($code < 600)) {
$this->getRun()->sendHttpCode($code);
}
$message = $inspector->getException()->getMessage();

View File

@@ -38,8 +38,7 @@ trait CompiledFile
$cache = $file->exists() ? $file->content() : null;
// Load real file if cache isn't up to date (or is invalid).
if (
!isset($cache['@class'])
if (!isset($cache['@class'])
|| $cache['@class'] !== $class
|| $cache['modified'] !== $modified
|| $cache['filename'] !== $this->filename
@@ -76,7 +75,6 @@ trait CompiledFile
$this->content = $cache['data'];
}
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Failed to read %s: %s', basename($this->filename), $e->getMessage()), 500, $e);
}

View File

@@ -46,11 +46,11 @@ abstract class Archiver
return $this;
}
public abstract function compress($folder, callable $status = null);
abstract public function compress($folder, callable $status = null);
public abstract function extract($destination, callable $status = null);
abstract public function extract($destination, callable $status = null);
public abstract function addEmptyFolders($folders, callable $status = null);
abstract public function addEmptyFolders($folders, callable $status = null);
protected function getArchiveFiles($rootPath)
{
@@ -62,5 +62,4 @@ abstract class Archiver
return $files;
}
}

View File

@@ -235,7 +235,7 @@ abstract class Folder
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
// Ignore hidden files.
if (strpos($file->getFilename(), '.') === 0) {
if (strpos($file->getFilename(), '.') === 0 && $file->isFile()) {
continue;
}
if (!$folders && $file->isDir()) {

View File

@@ -97,7 +97,7 @@ class ZipArchiver extends Archiver
'message' => 'Adding empty folders...'
]);
foreach($folders as $folder) {
foreach ($folders as $folder) {
$zip->addEmptyDir($folder);
$status && $status([
'type' => 'progress',

View File

@@ -11,6 +11,9 @@ namespace Grav\Common\GPM\Common;
use Grav\Common\Data\Data;
/**
* @property string $name
*/
class Package
{
/**
@@ -42,7 +45,7 @@ class Package
public function __set($key, $value)
{
return $this->data->set($key, $value);
$this->data->set($key, $value);
}
public function __isset($key)

View File

@@ -110,7 +110,7 @@ class GPM extends Iterator
* Return the instance of a specific Package
*
* @param string $slug The slug of the Package
* @return Local\Package The instance of the Package
* @return Local\Package|null The instance of the Package
*/
public function getInstalledPackage($slug)
{
@@ -561,8 +561,7 @@ class GPM extends Iterator
$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') &&
if (file_exists($source . 'system/defines.php') &&
file_exists($source . 'system/config/system.yaml')
) {
return 'grav';
@@ -784,18 +783,24 @@ class GPM extends Iterator
if (count($dependent_packages)) {
foreach ($dependent_packages as $dependent_package) {
$other_dependency_version_with_operator = $this->getVersionOfDependencyRequiredByPackage($dependent_package,
$slug);
$other_dependency_version_with_operator = $this->getVersionOfDependencyRequiredByPackage(
$dependent_package,
$slug
);
$other_dependency_version = $this->calculateVersionNumberFromDependencyVersion($other_dependency_version_with_operator);
// check version is compatible with the one needed by the current package
if ($this->versionFormatIsNextSignificantRelease($other_dependency_version_with_operator)) {
$compatible = $this->checkNextSignificantReleasesAreCompatible($version,
$other_dependency_version);
$compatible = $this->checkNextSignificantReleasesAreCompatible(
$version,
$other_dependency_version
);
if (!$compatible) {
if (!in_array($dependent_package, $ignore_packages_list, true)) {
throw new \RuntimeException("Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
2);
throw new \RuntimeException(
"Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
2
);
}
}
}
@@ -815,8 +820,11 @@ class GPM extends Iterator
public function checkPackagesCanBeInstalled($packages_names_list)
{
foreach ($packages_names_list as $package_name) {
$this->checkNoOtherPackageNeedsThisDependencyInALowerVersion($package_name,
$this->getLatestVersionOfPackage($package_name), $packages_names_list);
$this->checkNoOtherPackageNeedsThisDependencyInALowerVersion(
$package_name,
$this->getLatestVersionOfPackage($package_name),
$packages_names_list
);
}
}
@@ -845,8 +853,10 @@ class GPM extends Iterator
// Check PHP version
if ($dependency_slug === 'php') {
$current_php_version = phpversion();
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
$current_php_version) === 1
if (version_compare(
$this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
$current_php_version
) === 1
) {
//Needs a Grav update first
throw new \RuntimeException("<red>One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
@@ -858,8 +868,10 @@ class GPM extends Iterator
//First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell.
if ($dependency_slug === 'grav') {
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
GRAV_VERSION) === 1
if (version_compare(
$this->calculateVersionNumberFromDependencyVersion($dependencyVersionWithOperator),
GRAV_VERSION
) === 1
) {
//Needs a Grav update first
throw new \RuntimeException("<red>One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
@@ -888,12 +900,16 @@ class GPM extends Iterator
// if requirement is next significant release, check is compatible with currently installed version, might not be
if ($this->versionFormatIsNextSignificantRelease($dependencyVersionWithOperator)) {
if ($this->firstVersionIsLower($dependencyVersion, $currentlyInstalledVersion)) {
$compatible = $this->checkNextSignificantReleasesAreCompatible($dependencyVersion,
$currentlyInstalledVersion);
$compatible = $this->checkNextSignificantReleasesAreCompatible(
$dependencyVersion,
$currentlyInstalledVersion
);
if (!$compatible) {
throw new \RuntimeException('Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the one installed. This package must be updated. Please get in touch with its developer.',
2);
throw new \RuntimeException(
'Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the one installed. This package must be updated. Please get in touch with its developer.',
2
);
}
}
}
@@ -903,8 +919,10 @@ class GPM extends Iterator
if ($this->firstVersionIsLower($latestRelease, $dependencyVersion)) {
//throw an exception if a required version cannot be found in the GPM yet
throw new \RuntimeException('Dependency <cyan>' . $package_yaml['name'] . '</cyan> is required in version <cyan>' . $dependencyVersion . '</cyan> which is higher than the latest release, <cyan>' . $latestRelease . '</cyan>. Try running `bin/gpm -f index` to force a refresh of the GPM cache',
1);
throw new \RuntimeException(
'Dependency <cyan>' . $package_yaml['name'] . '</cyan> is required in version <cyan>' . $dependencyVersion . '</cyan> which is higher than the latest release, <cyan>' . $latestRelease . '</cyan>. Try running `bin/gpm -f index` to force a refresh of the GPM cache',
1
);
}
if ($this->firstVersionIsLower($currentlyInstalledVersion, $dependencyVersion)) {
@@ -924,12 +942,16 @@ class GPM extends Iterator
if ($this->versionFormatIsNextSignificantRelease($dependencyVersionWithOperator)) {
$latestVersionOfPackage = $this->getLatestVersionOfPackage($dependency_slug);
if ($this->firstVersionIsLower($dependencyVersion, $latestVersionOfPackage)) {
$compatible = $this->checkNextSignificantReleasesAreCompatible($dependencyVersion,
$latestVersionOfPackage);
$compatible = $this->checkNextSignificantReleasesAreCompatible(
$dependencyVersion,
$latestVersionOfPackage
);
if (!$compatible) {
throw new \Exception('Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the latest release available, and it cannot be installed. This package must be updated. Please get in touch with its developer.',
2);
throw new \Exception(
'Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the latest release available, and it cannot be installed. This package must be updated. Please get in touch with its developer.',
2
);
}
}
}
@@ -947,8 +969,11 @@ class GPM extends Iterator
public function checkNoOtherPackageNeedsTheseDependenciesInALowerVersion($dependencies_slugs)
{
foreach ($dependencies_slugs as $dependency_slug) {
$this->checkNoOtherPackageNeedsThisDependencyInALowerVersion($dependency_slug,
$this->getLatestVersionOfPackage($dependency_slug), $dependencies_slugs);
$this->checkNoOtherPackageNeedsThisDependencyInALowerVersion(
$dependency_slug,
$this->getLatestVersionOfPackage($dependency_slug),
$dependencies_slugs
);
}
}
@@ -994,7 +1019,6 @@ class GPM extends Iterator
// Dependency already added by another package
//if this package requires a version higher than the currently stored one, store this requirement instead
if (isset($current_package_version_information) && $current_package_version_information !== '*') {
$currently_stored_version_information = $dependencies[$current_package_name];
$currently_stored_version_number = $this->calculateVersionNumberFromDependencyVersion($currently_stored_version_information);
@@ -1009,8 +1033,10 @@ class GPM extends Iterator
$current_package_version_number = $this->calculateVersionNumberFromDependencyVersion($current_package_version_information);
if (!$current_package_version_number) {
throw new \RuntimeException('Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName,
1);
throw new \RuntimeException(
'Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName,
1
);
}
$current_package_version_is_in_next_significant_release_format = false;
@@ -1024,17 +1050,23 @@ class GPM extends Iterator
} else {
if (!$currently_stored_version_is_in_next_significant_release_format && !$current_package_version_is_in_next_significant_release_format) {
//Comparing versions equals or higher, a simple version_compare is enough
if (version_compare($currently_stored_version_number,
$current_package_version_number) === -1
if (version_compare(
$currently_stored_version_number,
$current_package_version_number
) === -1
) { //Current package version is higher
$dependencies[$current_package_name] = $current_package_version_information;
}
} else {
$compatible = $this->checkNextSignificantReleasesAreCompatible($currently_stored_version_number,
$current_package_version_number);
$compatible = $this->checkNextSignificantReleasesAreCompatible(
$currently_stored_version_number,
$current_package_version_number
);
if (!$compatible) {
throw new \RuntimeException('Dependency ' . $current_package_name . ' is required in two incompatible versions',
2);
throw new \RuntimeException(
'Dependency ' . $current_package_name . ' is required in two incompatible versions',
2
);
}
}
}
@@ -1156,5 +1188,4 @@ class GPM extends Iterator
return true;
}
}

View File

@@ -40,7 +40,7 @@ class Installer
protected static $target;
/**
* @var int Error Code
* @var int|string Error code or string
*/
protected static $error = 0;
@@ -84,8 +84,10 @@ class Installer
$options = array_merge(self::$options, $options);
$install_path = rtrim($destination . DS . ltrim($options['install_path'], DS), DS);
if (!self::isGravInstance($destination) || !self::isValidDestination($install_path,
$options['exclude_checks'])
if (!self::isGravInstance($destination) || !self::isValidDestination(
$install_path,
$options['exclude_checks']
)
) {
return false;
}
@@ -160,7 +162,6 @@ class Installer
self::$error = self::OK;
return true;
}
/**
@@ -297,7 +298,6 @@ class Installer
public static function sophisticatedInstall($source_path, $install_path, $ignores = [], $keep_source = false)
{
foreach (new \DirectoryIterator($source_path) as $file) {
if ($file->isLink() || $file->isDot() || \in_array($file->getFilename(), $ignores, true)) {
continue;
}
@@ -410,8 +410,7 @@ class Installer
self::$error = 0;
self::$target = $target;
if (
!file_exists($target . DS . 'index.php') ||
if (!file_exists($target . DS . 'index.php') ||
!file_exists($target . DS . 'bin') ||
!file_exists($target . DS . 'user') ||
!file_exists($target . DS . 'system' . DS . 'config' . DS . 'system.yaml')
@@ -473,7 +472,7 @@ class Installer
case self::ZIP_EXTRACT_ERROR:
$msg = 'Unable to extract the package. ';
if (self::$error_zip) {
switch(self::$error_zip) {
switch (self::$error_zip) {
case \ZipArchive::ER_EXISTS:
$msg .= 'File already exists.';
break;

View File

@@ -108,7 +108,6 @@ class Licenses
* @return \RocketTheme\Toolbox\File\FileInterface
*/
public static function getLicenseFile()
{
if (!isset(self::$file)) {
$path = Grav::instance()['locator']->findResource('user-data://') . '/licenses.yaml';

View File

@@ -15,6 +15,7 @@ use \Doctrine\Common\Cache\FilesystemCache;
class GravCore extends AbstractPackageCollection
{
protected $repository = 'https://getgrav.org/downloads/grav.json';
/** @var array */
private $data;
private $version;
@@ -23,7 +24,7 @@ class GravCore extends AbstractPackageCollection
/**
* @param bool $refresh
* @param null $callback
* @param callable|null $callback
* @throws \InvalidArgumentException
*/
public function __construct($refresh = false, $callback = null)

View File

@@ -135,7 +135,8 @@ class Response
if (!$settings['verify_peer']) {
$overrides = array_replace_recursive([], $overrides, [
'curl' => [
CURLOPT_SSL_VERIFYPEER => $settings['verify_peer']
CURLOPT_SSL_VERIFYPEER => $settings['verify_peer'],
CURLOPT_SSL_VERIFYHOST => false
],
'fopen' => [
'ssl' => [
@@ -233,7 +234,6 @@ class Response
if ($bytes_transferred > 0) {
if ($notification_code == STREAM_NOTIFY_PROGRESS | STREAM_NOTIFY_COMPLETED || $isCurlResource) {
$progress = [
'code' => $notification_code,
'filesize' => $filesize,
@@ -251,7 +251,7 @@ class Response
/**
* Automatically picks the preferred method
*
* @return string The response of the request
* @return string|null The response of the request
*/
private static function getAuto()
{

View File

@@ -109,7 +109,7 @@ class Upgrader
/**
* Get minimum PHP version from remote
*
* @return null
* @return string
*/
public function minPHPVersion()
{

View File

@@ -11,17 +11,14 @@ namespace Grav\Common;
use Grav\Common\Config\Config;
use Grav\Common\Config\Setup;
use Grav\Common\Helpers\Exif;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Processors\AssetsProcessor;
use Grav\Common\Processors\BackupsProcessor;
use Grav\Common\Processors\ConfigurationProcessor;
use Grav\Common\Processors\DebuggerAssetsProcessor;
use Grav\Common\Processors\DebuggerProcessor;
use Grav\Common\Processors\ErrorsProcessor;
use Grav\Common\Processors\InitializeProcessor;
use Grav\Common\Processors\LoggerProcessor;
use Grav\Common\Processors\PagesProcessor;
use Grav\Common\Processors\PluginsProcessor;
use Grav\Common\Processors\RenderProcessor;
@@ -30,13 +27,16 @@ use Grav\Common\Processors\SchedulerProcessor;
use Grav\Common\Processors\TasksProcessor;
use Grav\Common\Processors\ThemesProcessor;
use Grav\Common\Processors\TwigProcessor;
use Grav\Common\Scheduler\Scheduler;
use Grav\Common\Twig\Twig;
use Grav\Framework\DI\Container;
use Grav\Framework\Psr7\Response;
use Grav\Framework\RequestHandler\RequestHandler;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Grav container is the heart of Grav.
@@ -51,7 +51,7 @@ class Grav extends Container
public $output;
/**
* @var static The singleton instance
* @var static|null The singleton instance
*/
protected static $instance;
@@ -74,26 +74,22 @@ class Grav extends Container
'Grav\Common\Service\SessionServiceProvider',
'Grav\Common\Service\StreamsServiceProvider',
'Grav\Common\Service\TaskServiceProvider',
'browser' => 'Grav\Common\Browser',
'cache' => 'Grav\Common\Cache',
'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
'exif' => 'Grav\Common\Helpers\Exif',
'plugins' => 'Grav\Common\Plugins',
'scheduler' => 'Grav\Common\Scheduler\Scheduler',
'taxonomy' => 'Grav\Common\Taxonomy',
'themes' => 'Grav\Common\Themes',
'twig' => 'Grav\Common\Twig\Twig',
'uri' => 'Grav\Common\Uri',
'browser' => Browser::class,
'cache' => Cache::class,
'events' => EventDispatcher::class,
'exif' => Exif::class,
'plugins' => Plugins::class,
'scheduler' => Scheduler::class,
'taxonomy' => Taxonomy::class,
'themes' => Themes::class,
'twig' => Twig::class,
'uri' => Uri::class,
];
/**
* @var array All middleware processors that are processed in $this->process()
*/
protected $middleware = [
'configurationProcessor',
'loggerProcessor',
'errorsProcessor',
'debuggerProcessor',
'initializeProcessor',
'pluginsProcessor',
'themesProcessor',
@@ -129,7 +125,7 @@ class Grav extends Container
*/
public static function instance(array $values = [])
{
if (!self::$instance) {
if (null === self::$instance) {
self::$instance = static::load($values);
} elseif ($values) {
$instance = self::$instance;
@@ -157,15 +153,14 @@ class Grav extends Container
$this->initialized['setup'] = true;
$this->measureTime('_setup', 'Site Setup', function () use ($environment) {
// Force environment if passed to the method.
if ($environment) {
Setup::$environment = $environment;
}
// Force environment if passed to the method.
if ($environment) {
Setup::$environment = $environment;
}
$this['setup'];
$this['streams'];
});
// Initialize setup and streams.
$this['setup'];
$this['streams'];
return $this;
}
@@ -186,18 +181,6 @@ class Grav extends Container
$container = new Container(
[
'configurationProcessor' => function () {
return new ConfigurationProcessor($this);
},
'loggerProcessor' => function () {
return new LoggerProcessor($this);
},
'errorsProcessor' => function () {
return new ErrorsProcessor($this);
},
'debuggerProcessor' => function () {
return new DebuggerProcessor($this);
},
'initializeProcessor' => function () {
return new InitializeProcessor($this);
},
@@ -237,46 +220,100 @@ class Grav extends Container
]
);
$default = function (ServerRequestInterface $request) {
$default = static function () {
return new Response(404);
};
/** @var Debugger $debugger */
$debugger = $this['debugger'];
$collection = new RequestHandler($this->middleware, $default, $container);
$response = $collection->handle($this['request']);
$body = $response->getBody();
// Handle ETag and If-None-Match headers.
if ($response->getHeaderLine('ETag') === '1') {
$etag = md5($body);
$response = $response->withHeader('ETag', $etag);
if ($this['request']->getHeaderLine('If-None-Match') === $etag) {
$response = $response->withStatus(304);
$body = '';
}
}
$this->header($response);
echo $response->getBody();
echo $body;
$debugger->render();
$this['debugger']->render();
register_shutdown_function([$this, 'shutdown']);
}
/**
* Set the system locale based on the language and configuration
*/
public function setLocale()
{
// Initialize Locale if set and configured.
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
$language = $this['language']->getLanguage();
setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
} elseif ($this['config']->get('system.default_locale')) {
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
// Response object can turn off all shutdown processing. This can be used for example to speed up AJAX responses.
// Note that using this feature will also turn off response compression.
if ($response->getHeaderLine('Grav-Internal-SkipShutdown') !== '1') {
register_shutdown_function([$this, 'shutdown']);
}
}
/**
* Redirect browser to another location.
* Terminates Grav request with a response.
*
* Please use this method instead of calling `die();` or `exit();`. Note that you need to create a response object.
*
* @param ResponseInterface $response
*/
public function close(ResponseInterface $response): void
{
// Make sure nothing extra gets written to the response.
while (ob_get_level()) {
ob_end_clean();
}
// Close the session.
if (isset($this['session'])) {
$this['session']->close();
}
/** @var ServerRequestInterface $request */
$request = $this['request'];
/** @var Debugger $debugger */
$debugger = $this['debugger'];
$response = $debugger->logRequest($request, $response);
$body = $response->getBody();
// Handle ETag and If-None-Match headers.
if ($response->getHeaderLine('ETag') === '1') {
$etag = md5($body);
$response = $response->withHeader('ETag', $etag);
if ($request->getHeaderLine('If-None-Match') === $etag) {
$response = $response->withStatus(304);
$body = '';
}
}
$this->header($response);
echo $body;
exit();
}
/**
* @param ResponseInterface $response
* @deprecated 1.7 Do not use
*/
public function exit(ResponseInterface $response): void
{
$this->close($response);
}
/**
* Terminates Grav request and redirects browser to another location.
*
* Please use this method instead of calling `header("Location: {$url}", true, 302); exit();`.
*
* @param string $route Internal route.
* @param int $code Redirection code (30x)
*/
public function redirect($route, $code = null)
public function redirect($route, $code = null): void
{
/** @var Uri $uri */
$uri = $this['uri'];
@@ -293,11 +330,7 @@ class Grav extends Container
$code = $this['config']->get('system.pages.redirect_default_code', 302);
}
if (isset($this['session'])) {
$this['session']->close();
}
if ($uri->isExternal($route)) {
if ($uri::isExternal($route)) {
$url = $route;
} else {
$url = rtrim($uri->rootUrl(), '/') . '/';
@@ -309,8 +342,9 @@ class Grav extends Container
}
}
header("Location: {$url}", true, $code);
exit();
$response = new Response($code, ['Location' => $url]);
$this->close($response);
}
/**
@@ -343,26 +377,53 @@ class Grav extends Container
header("HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}");
foreach ($response->getHeaders() as $key => $values) {
// Skip internal Grav headers.
if (strpos($key, 'Grav-Internal-') === 0) {
continue;
}
foreach ($values as $i => $value) {
header($key . ': ' . $value, $i === 0);
}
}
}
/**
* Set the system locale based on the language and configuration
*/
public function setLocale()
{
// Initialize Locale if set and configured.
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
$language = $this['language']->getLanguage();
setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
} elseif ($this['config']->get('system.default_locale')) {
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
}
}
/**
* Fires an event with optional parameters.
*
* @param string $eventName
* @param Event $event
* @param Event|null $event
*
* @return Event
*/
public function fireEvent($eventName, Event $event = null)
{
/** @var EventDispatcher $events */
/** @var EventDispatcherInterface $events */
$events = $this['events'];
if (null === $event) {
$event = new Event();
}
return $events->dispatch($eventName, $event);
/** @var Debugger $debugger */
$debugger = $this['debugger'];
$debugger->addEvent($eventName, $event, $events);
$events->dispatch($event, $eventName);
return $event;
}
/**
@@ -394,7 +455,6 @@ class Grav extends Container
if ($this['config']->get('system.cache.gzip')) {
// Flush gzhandler buffer if gzip setting was enabled.
ob_end_flush();
} 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.
@@ -403,7 +463,6 @@ class Grav extends Container
} else {
header('Content-Encoding: none');
}
}
@@ -470,9 +529,7 @@ class Grav extends Container
return $container;
};
$container->measureTime('_services', 'Services', function () use ($container) {
$container->registerServices();
});
$container->registerServices();
return $container;
}
@@ -528,7 +585,7 @@ class Grav extends Container
$path_parts = pathinfo($path);
/** @var PageInterface $page */
$page = $this['pages']->dispatch($path_parts['dirname'], true);
$page = $this['pages']->find($path_parts['dirname'], true);
if ($page) {
$media = $page->media()->all();

View File

@@ -33,7 +33,8 @@ class Base32
*/
public static function encode($bytes)
{
$i = 0; $index = 0;
$i = 0;
$index = 0;
$base32 = '';
$bytesLen = \strlen($bytes);

View File

@@ -32,7 +32,7 @@ class Excerpts
$excerpt = static::processLinkExcerpt($excerpt, $page, 'image');
$excerpt['element']['attributes']['src'] = $excerpt['element']['attributes']['href'];
unset ($excerpt['element']['attributes']['href']);
unset($excerpt['element']['attributes']['href']);
$excerpt = static::processImageExcerpt($excerpt, $page);

View File

@@ -41,15 +41,20 @@ class LogViewer
* @param int $lines
* @return bool|string
*/
public function tail($filepath, $lines = 1) {
public function tail($filepath, $lines = 1)
{
$f = @fopen($filepath, "rb");
if ($f === false) return false;
else $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
if ($f === false) {
return false;
} else {
$buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
}
fseek($f, -1, SEEK_END);
if (fread($f, 1) != "\n") $lines -= 1;
if (fread($f, 1) != "\n") {
$lines -= 1;
}
// Start reading
$output = '';
@@ -108,7 +113,7 @@ class LogViewer
*/
public function parse($line)
{
if( !is_string($line) || strlen($line) === 0) {
if (!is_string($line) || strlen($line) === 0) {
return array();
}
preg_match($this->pattern, $line, $data);
@@ -145,5 +150,4 @@ class LogViewer
$lines = array_filter(preg_split('/#\d*/m', $trace));
return array_slice($lines, 0, $rows);
}
}

View File

@@ -26,7 +26,8 @@ use DOMLettersIterator;
* file that was distributed with this source code.
*/
class Truncator {
class Truncator
{
/**
* Safely truncates HTML by a given number of words.
@@ -49,10 +50,8 @@ class Truncator {
$words = new DOMWordsIterator($container);
$truncated = false;
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];
@@ -75,7 +74,6 @@ class Truncator {
break;
}
}
// Return original HTML if not truncated.
@@ -107,10 +105,8 @@ class Truncator {
$letters = new DOMLettersIterator($container);
$truncated = false;
foreach ($letters as $letter) {
// If we have exceeded the limit, we want to delete the remainder of this document.
if ($letters->key() >= $limit) {
$currentText = $letters->currentTextPosition();
$currentText[0]->nodeValue = mb_substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
self::removeProceedingNodes($currentText[0], $container);
@@ -136,7 +132,7 @@ class Truncator {
/**
* Builds a DOMDocument object from a string containing HTML.
* @param string $html HTML to load
* @returns DOMDocument Returns a DOMDocument object.
* @return DOMDocument Returns a DOMDocument object.
*/
public static function htmlToDomDocument($html)
{
@@ -190,7 +186,7 @@ class Truncator {
* Clean extra code
*
* @param DOMDocument $doc
* @param $container
* @param DOMDocument $container
* @return string
*/
private static function getCleanedHTML(DOMDocument $doc, $container)
@@ -199,12 +195,11 @@ class Truncator {
$doc->removeChild($doc->firstChild);
}
while ($container->firstChild ) {
while ($container->firstChild) {
$doc->appendChild($container->firstChild);
}
$html = trim($doc->saveHTML());
return $html;
return trim($doc->saveHTML());
}
/**
@@ -226,7 +221,6 @@ class Truncator {
} else {
$domNode->parentNode->parentNode->appendChild($textNode);
}
} else {
// Append to current node
$domNode->nodeValue = rtrim($domNode->nodeValue) . $ellipsis;
@@ -248,11 +242,13 @@ class Truncator {
if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
return $text;
}
// splits all html-tags to scanable lines
preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
$total_length = strlen($ending);
$open_tags = array();
$truncate = '';
$open_tags = [];
foreach ($lines as $line_matchings) {
// if there is any html-tag in this line, handle it and add it (uncounted) to the output
if (!empty($line_matchings[1])) {
@@ -260,14 +256,14 @@ class Truncator {
if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
// do nothing
// if tag is a closing tag
} else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
} elseif (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
// delete tag from $open_tags list
$pos = array_search($tag_matchings[1], $open_tags);
if ($pos !== false) {
unset($open_tags[$pos]);
}
// if tag is an opening tag
} else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
} elseif (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
// add tag to the beginning of $open_tags list
array_unshift($open_tags, strtolower($tag_matchings[1]));
}
@@ -301,29 +297,29 @@ class Truncator {
$total_length += $content_length;
}
// if the maximum length is reached, get off the loop
if($total_length>= $length) {
if ($total_length>= $length) {
break;
}
}
} else {
if (strlen($text) <= $length) {
return $text;
} else {
$truncate = substr($text, 0, $length - strlen($ending));
}
$truncate = substr($text, 0, $length - strlen($ending));
}
// if the words shouldn't be cut in the middle...
if (!$exact) {
// ...search the last occurance of a space...
$spacepos = strrpos($truncate, ' ');
if (isset($spacepos)) {
if (false !== $spacepos) {
// ...and cut the text in this position
$truncate = substr($truncate, 0, $spacepos);
}
}
// add the defined ending to the text
$truncate .= $ending;
if($considerHtml) {
if (isset($open_tags)) {
// close all unclosed html-tags
foreach ($open_tags as $tag) {
$truncate .= '</' . $tag . '>';
@@ -331,5 +327,4 @@ class Truncator {
}
return $truncate;
}
}

View File

@@ -16,13 +16,15 @@ use Symfony\Component\Yaml\Yaml;
class YamlLinter
{
public static function lint()
public static function lint(string $folder = null)
{
$errors = static::lintConfig();
$errors = $errors + static::lintPages();
$errors = $errors + static::lintBlueprints();
return $errors;
if (null !== $folder) {
$folder = $folder ?: GRAV_ROOT;
return static::recurseFolder($folder);
}
return array_merge(static::lintConfig(), static::lintPages(), static::lintBlueprints());
}
public static function lintPages()
@@ -47,7 +49,7 @@ class YamlLinter
return static::recurseFolder('blueprints://');
}
public static function recurseFolder($path, $extensions = 'md|yaml')
public static function recurseFolder($path, $extensions = '(md|yaml)')
{
$lint_errors = [];
@@ -85,5 +87,4 @@ class YamlLinter
}
return $contents;
}
}

View File

@@ -9,6 +9,8 @@
namespace Grav\Common;
use Grav\Common\Language\Language;
/**
* This file was originally part of the Akelos Framework
*/
@@ -24,6 +26,7 @@ class Inflector
public static function init()
{
if (empty(static::$plural)) {
/** @var Language $language */
$language = Grav::instance()['language'];
static::$plural = $language->translate('GRAV.INFLECTOR_PLURALS', null, true) ?: [];
static::$singular = $language->translate('GRAV.INFLECTOR_SINGULAR', null, true) ?: [];
@@ -70,7 +73,6 @@ class Inflector
}
return false;
}
/**
@@ -193,6 +195,8 @@ class Inflector
$regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2);
$regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3);
$regex4 = trim($regex4, '-');
return strtolower($regex4);
}

View File

@@ -228,8 +228,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
public function filter(callable $callback = null)
{
foreach ($this->items as $key => $value) {
if (
(!$callback && !(bool)$value) ||
if ((!$callback && !(bool)$value) ||
($callback && !$callback($value, $key))
) {
unset($this->items[$key]);
@@ -248,7 +247,6 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
* @param bool $desc
*
* @return $this|array
* @internal param bool $asc
*
*/
public function sort(callable $callback = null, $desc = false)

View File

@@ -9,6 +9,7 @@
namespace Grav\Common\Language;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Negotiation\AcceptLanguage;
@@ -16,20 +17,22 @@ use Negotiation\LanguageNegotiator;
class Language
{
/** @var Grav */
protected $grav;
protected $enabled = true;
/**
* @var array
*/
protected $languages = [];
protected $page_extensions = [];
protected $fallback_languages = [];
protected $default;
protected $active = null;
/** @var Config $config */
/** @var Config */
protected $config;
protected $enabled = true;
/** @var array */
protected $languages = [];
protected $fallback_languages = [];
protected $fallback_extensions = [];
protected $page_extesions = [];
protected $default;
protected $active;
protected $http_accept_language;
protected $lang_in_url = false;
@@ -42,7 +45,12 @@ class Language
{
$this->grav = $grav;
$this->config = $grav['config'];
$this->languages = $this->config->get('system.languages.supported', []);
$languages = $this->config->get('system.languages.supported', []);
foreach ($languages as &$language) {
$language = (string)$language;
}
$this->languages = $languages;
$this->init();
}
@@ -58,7 +66,7 @@ class Language
$this->default = reset($this->languages);
}
$this->page_extensions = null;
$this->resetFallbackPageExtensions();
if (empty($this->languages)) {
$this->enabled = false;
@@ -93,20 +101,22 @@ class Language
public function setLanguages($langs)
{
$this->languages = $langs;
$this->init();
}
/**
* Gets a pipe-separated string of available languages
*
* @param string|null $delimiter Delimiter to be quoted.
* @return string
*/
public function getAvailable()
public function getAvailable($delimiter = null)
{
$languagesArray = $this->languages; //Make local copy
$languagesArray = array_map(function($value) {
return preg_quote($value);
$languagesArray = array_map(function ($value) use ($delimiter) {
return preg_quote($value, $delimiter);
}, $languagesArray);
sort($languagesArray);
@@ -143,6 +153,7 @@ class Language
*/
public function setDefault($lang)
{
$lang = (string)$lang;
if ($this->validate($lang)) {
$this->default = $lang;
@@ -171,7 +182,12 @@ class Language
*/
public function setActive($lang)
{
$lang = (string)$lang;
if ($this->validate($lang)) {
/** @var Debugger $debugger */
$debugger = $this->grav['debugger'];
$debugger->addMessage('Active language set to ' . $lang, 'debug');
$this->active = $lang;
return $lang;
@@ -196,7 +212,7 @@ class Language
// Try setting language from prefix of URL (/en/blah/blah).
if (preg_match($regex, $uri, $matches)) {
$this->lang_in_url = true;
$this->active = $matches[2];
$this->setActive($matches[2]);
$uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1);
// Store in session if language is different.
@@ -210,22 +226,20 @@ class Language
// Try getting language from the session, else no active.
if (isset($this->grav['session']) && $this->grav['session']->isStarted() &&
$this->config->get('system.languages.session_store_active', true)) {
$this->active = $this->grav['session']->active_language ?: null;
$this->setActive($this->grav['session']->active_language ?: null);
}
// if still null, try from http_accept_language header
if ($this->active === null &&
$this->config->get('system.languages.http_accept_language') &&
$accept = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? false) {
$negotiator = new LanguageNegotiator();
$best_language = $negotiator->getBest($accept, $this->languages);
if ($best_language instanceof AcceptLanguage) {
$this->active = $best_language->getType();
$this->setActive($best_language->getType());
} else {
$this->active = $this->getDefault();
$this->setActive($this->getDefault());
}
}
}
}
@@ -275,52 +289,67 @@ class Language
return (bool) $this->lang_in_url;
}
/**
* Get full list of used language page extensions: [''=>'.md', 'en'=>'.en.md', ...]
*
* @param string|null $fileExtension
* @return mixed
*/
public function getPageExtensions($fileExtension = null)
{
$fileExtension = $fileExtension ?: CONTENT_EXT;
if (!isset($this->fallback_extensions[$fileExtension])) {
$extensions[''] = $fileExtension;
foreach ($this->languages as $code) {
$extensions[$code] = ".{$code}{$fileExtension}";
}
$this->fallback_extensions[$fileExtension] = $extensions;
}
return $this->fallback_extensions[$fileExtension];
}
/**
* Gets an array of valid extensions with active first, then fallback extensions
*
* @param string|null $file_ext
*
* @return array
* @param string|null $fileExtension
* @param string|null $languageCode
* @param bool $assoc Return values in ['en' => '.en.md', ...] format.
* @return array Key is the language code, value is the file extension to be used.
*/
public function getFallbackPageExtensions($file_ext = null)
public function getFallbackPageExtensions(string $fileExtension = null, string $languageCode = null, bool $assoc = false)
{
if (empty($this->page_extensions)) {
if (!$file_ext) {
$file_ext = CONTENT_EXT;
$fileExtension = $fileExtension ?: CONTENT_EXT;
$key = $fileExtension . '-' . ($languageCode ?? 'default') . '-' . (int)$assoc;
if (!isset($this->fallback_extensions[$key])) {
$all = $this->getPageExtensions($fileExtension);
$list = [];
$fallback = $this->getFallbackLanguages($languageCode, true);
foreach ($fallback as $code) {
$ext = $all[$code] ?? null;
if (null !== $ext) {
$list[$code] = $ext;
}
}
if (!$assoc) {
$list = array_values($list);
}
if ($this->enabled()) {
$valid_lang_extensions = [];
foreach ($this->languages as $lang) {
$valid_lang_extensions[] = '.' . $lang . $file_ext;
}
$this->fallback_extensions[$key] = $list;
if ($this->active) {
$active_extension = '.' . $this->active . $file_ext;
$key = \array_search($active_extension, $valid_lang_extensions, true);
// Default behavior is to find any language other than active
if ($this->config->get('system.languages.pages_fallback_only')) {
$slice = \array_slice($valid_lang_extensions, 0, $key+1);
$valid_lang_extensions = array_reverse($slice);
} else {
unset($valid_lang_extensions[$key]);
array_unshift($valid_lang_extensions, $active_extension);
}
}
$valid_lang_extensions[] = $file_ext;
$this->page_extensions = $valid_lang_extensions;
} else {
$this->page_extensions = (array)$file_ext;
}
/** @var Debugger $debugger */
//$debugger = $this->grav['debugger'];
//$debugger->addMessage("Language fallback extensions for {$languageCode}", 'debug', $list);
}
return $this->page_extensions;
return $this->fallback_extensions[$key];
}
/**
* Resets the page_extensions value.
* Resets the fallback_languages value.
*
* Useful to re-initialize the pages and change site language at runtime, example:
*
@@ -332,33 +361,76 @@ class Language
*/
public function resetFallbackPageExtensions()
{
$this->page_extensions = null;
$this->fallback_languages = [];
$this->fallback_extensions = [];
$this->page_extesions = [];
}
/**
* Gets an array of languages with active first, then fallback languages
* Gets an array of languages with active first, then fallback languages.
*
*
* @param string|null $languageCode
* @param bool $includeDefault If true, list contains '', which can be used for default
* @return array
*/
public function getFallbackLanguages()
public function getFallbackLanguages(string $languageCode = null, bool $includeDefault = false)
{
if (empty($this->fallback_languages)) {
if ($this->enabled()) {
$fallback_languages = $this->languages;
if ($this->active) {
$active_extension = $this->active;
$key = \array_search($active_extension, $fallback_languages, true);
unset($fallback_languages[$key]);
array_unshift($fallback_languages, $active_extension);
}
$this->fallback_languages = $fallback_languages;
}
// always add english in case a translation doesn't exist
$this->fallback_languages[] = 'en';
// Handle default.
if ($languageCode === '' || !$this->enabled()) {
return [''];
}
return $this->fallback_languages;
$default = $this->getDefault() ?? 'en';
$active = $languageCode ?? $this->getActive() ?? $default;
$key = $active . '-' . (int)$includeDefault;
if (!isset($this->fallback_languages[$key])) {
$fallback = $this->config->get('system.languages.content_fallback.' . $active);
$fallback_languages = [];
if (null === $fallback && $this->config->get('system.languages.pages_fallback_only', false)) {
// Special fallback list returns itself and all the previous items in reverse order:
// active: 'v2', languages: ['v1', 'v2', 'v3', 'v4'] => ['v2', 'v1', '']
if ($includeDefault) {
$fallback_languages[''] = '';
}
foreach ($this->languages as $code) {
$fallback_languages[$code] = $code;
if ($code === $active) {
break;
}
}
$fallback_languages = array_reverse($fallback_languages);
} else {
if (null === $fallback) {
$fallback = [$default];
} elseif (!is_array($fallback)) {
$fallback = is_string($fallback) && $fallback !== '' ? explode(',', $fallback) : [];
}
array_unshift($fallback, $active);
$fallback = array_unique($fallback);
foreach ($fallback as $code) {
// Default fallback list has active language followed by default language and extensionless file:
// active: 'fi', default: 'en', languages: ['sv', 'en', 'de', 'fi'] => ['fi', 'en', '']
$fallback_languages[$code] = $code;
if ($includeDefault && $code === $default) {
$fallback_languages[''] = '';
}
}
}
$fallback_languages = array_values($fallback_languages);
$this->fallback_languages[$key] = $fallback_languages;
/** @var Debugger $debugger */
//$debugger = $this->grav['debugger'];
//$debugger->addMessage("Language fallback for {$active}", 'debug', $fallback_languages);
}
return $this->fallback_languages[$key];
}
/**
@@ -378,7 +450,7 @@ class Language
*
* @param string|array $args The first argument is the lookup key value
* Other arguments can be passed and replaced in the translation with sprintf syntax
* @param array $languages
* @param array|null $languages
* @param bool $array_support
* @param bool $html_out
*
@@ -394,18 +466,12 @@ class Language
}
if ($this->config->get('system.languages.translations', true)) {
if ($this->enabled() && $lookup) {
if (empty($languages)) {
if ($this->config->get('system.languages.translations_fallback', true)) {
$languages = $this->getFallbackLanguages();
} else {
$languages = (array)$this->getLanguage();
}
}
} else {
$languages = ['en'];
if ($this->enabled() && $lookup && empty($languages)) {
$languages = $this->getTranslatedLanguages();
}
$languages = $languages ?: ['en'];
foreach ((array)$languages as $lang) {
$translation = $this->getTranslation($lang, $lookup, $array_support);
@@ -439,18 +505,12 @@ class Language
public function translateArray($key, $index, $languages = null, $html_out = false)
{
if ($this->config->get('system.languages.translations', true)) {
if ($this->enabled() && $key) {
if (empty($languages)) {
if ($this->config->get('system.languages.translations_fallback', true)) {
$languages = $this->getFallbackLanguages();
} else {
$languages = (array)$this->getDefault();
}
}
} else {
$languages = ['en'];
if ($this->enabled() && $key && empty($languages)) {
$languages = $this->getTranslatedLanguages();
}
$languages = $languages ?: ['en'];
foreach ((array)$languages as $lang) {
$translation_array = (array)Grav::instance()['languages']->get($lang . '.' . $key, null);
if ($translation_array && array_key_exists($index, $translation_array)) {
@@ -534,4 +594,27 @@ class Language
return LanguageCodes::get($code, $type);
}
public function __debugInfo()
{
$vars = get_object_vars($this);
unset($vars['grav'], $vars['config']);
return $vars;
}
/**
* @return array
*/
protected function getTranslatedLanguages(): array
{
if ($this->config->get('system.languages.translations_fallback', true)) {
$languages = $this->getFallbackLanguages();
} else {
$languages = [$this->getLanguage()];
}
$languages[] = 'en';
return array_values(array_unique($languages));
}
}

View File

@@ -202,4 +202,13 @@ class LanguageCodes
return false;
}
public static function getList($native = true)
{
$list = [];
foreach (static::$codes as $key => $names) {
$list[$key] = $native ? $names['nativeName'] : $names['name'];
}
return $list;
}
}

View File

@@ -35,5 +35,4 @@ class Parsedown extends \Parsedown
$this->init($excerpts, $defaults);
}
}

View File

@@ -20,4 +20,25 @@ interface MediaCollectionInterface extends \Grav\Framework\Media\Interfaces\Medi
* @return string|null
*/
public function getPath();
/**
* Get a list of all media.
*
* @return MediaObjectInterface[]
*/
public function all();
/**
* Set file modification timestamps (query params) for all the media files.
*
* @param string|int|null $timestamp
* @return $this
*/
public function setTimestamps($timestamp = null);
/**
* @param string $name
* @param MediaObjectInterface $file
*/
public function add($name, $file);
}

View File

@@ -19,6 +19,7 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
trait MediaTrait
{
protected $media;
protected $_loadMedia = true;
/**
* Get filesystem path to the associated media.
@@ -40,30 +41,33 @@ trait MediaTrait
/**
* Get URI ot the associated media. Method will return null if path isn't URI.
*
* @return null|string
* @return string|null
*/
public function getMediaUri()
{
$folder = $this->getMediaFolder();
$folder = $this->getMediaFolder();
if (!$folder) {
return null;
}
if (strpos($folder, '://')) {
return $folder;
}
if (strpos($folder, '://')) {
return $folder;
}
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$user = $locator->findResource('user://');
if (strpos($folder, $user) === 0) {
return 'user://' . substr($folder, \strlen($user)+1);
}
$locator = Grav::instance()['locator'];
$user = $locator->findResource('user://');
if (strpos($folder, $user) === 0) {
return 'user://' . substr($folder, \strlen($user)+1);
}
return null;
return null;
}
/**
* Gets the associated media collection.
*
* @return MediaCollectionInterface Representation of associated media.
* @return MediaCollectionInterface|Media Representation of associated media.
*/
public function getMedia()
{
@@ -73,7 +77,7 @@ trait MediaTrait
// Use cached media if possible.
$cacheKey = md5('media' . $this->getCacheKey());
if (!$media = $cache->get($cacheKey)) {
$media = new Media($this->getMediaFolder(), $this->getMediaOrder());
$media = new Media($this->getMediaFolder(), $this->getMediaOrder(), $this->_loadMedia);
$cache->set($cacheKey, $media);
}
$this->media = $media;
@@ -85,7 +89,7 @@ trait MediaTrait
/**
* Sets the associated media collection.
*
* @param MediaCollectionInterface $media Representation of associated media.
* @param MediaCollectionInterface|Media $media Representation of associated media.
* @return $this
*/
protected function setMedia(MediaCollectionInterface $media)
@@ -125,5 +129,5 @@ trait MediaTrait
/**
* @return string
*/
abstract protected function getCacheKey();
abstract protected function getCacheKey(): string;
}

View File

@@ -11,10 +11,11 @@ namespace Grav\Common\Page;
use Grav\Common\Grav;
use Grav\Common\Iterator;
use Grav\Common\Page\Interfaces\PageCollectionInterface;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Utils;
class Collection extends Iterator
class Collection extends Iterator implements PageCollectionInterface
{
/**
* @var Pages
@@ -51,6 +52,20 @@ class Collection extends Iterator
return $this->params;
}
/**
* Set parameters to the Collection
*
* @param array $params
*
* @return $this
*/
public function setParams(array $params)
{
$this->params = array_merge($this->params, $params);
return $this;
}
/**
* Add a single page to a collection
*
@@ -94,12 +109,12 @@ class Collection extends Iterator
*
* Merge another collection with the current collection
*
* @param Collection $collection
* @param PageCollectionInterface $collection
* @return $this
*/
public function merge(Collection $collection)
public function merge(PageCollectionInterface $collection)
{
foreach($collection as $page) {
foreach ($collection as $page) {
$this->addPage($page);
}
@@ -109,35 +124,21 @@ class Collection extends Iterator
/**
* Intersect another collection with the current collection
*
* @param Collection $collection
* @param PageCollectionInterface $collection
* @return $this
*/
public function intersect(Collection $collection)
public function intersect(PageCollectionInterface $collection)
{
$array1 = $this->items;
$array2 = $collection->toArray();
$this->items = array_uintersect($array1, $array2, function($val1, $val2) {
$this->items = array_uintersect($array1, $array2, function ($val1, $val2) {
return strcmp($val1['slug'], $val2['slug']);
});
return $this;
}
/**
* Set parameters to the Collection
*
* @param array $params
*
* @return $this
*/
public function setParams(array $params)
{
$this->params = array_merge($this->params, $params);
return $this;
}
/**
* Returns current page.
*
@@ -240,7 +241,7 @@ class Collection extends Iterator
*
* @return bool True if item is first.
*/
public function isFirst($path)
public function isFirst($path): bool
{
return $this->items && $path === array_keys($this->items)[0];
}
@@ -252,7 +253,7 @@ class Collection extends Iterator
*
* @return bool True if item is last.
*/
public function isLast($path)
public function isLast($path): bool
{
return $this->items && $path === array_keys($this->items)[\count($this->items) - 1];
}
@@ -301,7 +302,6 @@ class Collection extends Iterator
}
return $this;
}
/**
@@ -309,11 +309,13 @@ class Collection extends Iterator
*
* @param string $path the path the item
*
* @return int the index of the current page.
* @return int|null The index of the current page, null if not found.
*/
public function currentPosition($path)
public function currentPosition($path): ?int
{
return \array_search($path, \array_keys($this->items), true);
$pos = \array_search($path, \array_keys($this->items), true);
return $pos !== false ? $pos : null;
}
/**
@@ -600,7 +602,6 @@ class Collection extends Iterator
$items[$path] = $slug;
}
}
}
}

View File

@@ -0,0 +1,476 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Flex;
use Grav\Common\Page\Interfaces\PageCollectionInterface;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Utils;
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
use Grav\Framework\Flex\Pages\FlexPageCollection;
/**
* Class GravPageCollection
* @package Grav\Plugin\FlexObjects\Types\GravPages
*
* Incompatibilities with Grav\Common\Page\Collection:
* $page = $collection->key() will not work at all
* $clone = clone $collection does not clone objects inside the collection, does it matter?
* $string = (string)$collection returns collection id instead of comma separated list
* $collection->add() incompatible method signature
* $collection->remove() incompatible method signature
* $collection->filter() incompatible method signature (takes closure instead of callable)
* $collection->prev() does not rewind the internal pointer
* AND most methods are immutable; they do not update the current collection, but return updated one
*/
class PageCollection extends FlexPageCollection implements PageCollectionInterface
{
protected $_params;
/**
* @return PageInterface
*/
public function getRoot()
{
/** @var PageIndex $index */
$index = $this->getIndex();
return $index->getRoot();
}
/**
* Get the collection params
*
* @return array
*/
public function getParams(): array
{
return $this->_params ?? [];
}
/**
* Set parameters to the Collection
*
* @param array $params
*
* @return $this
*/
public function setParams(array $params)
{
$this->_params = $this->_params ? array_merge($this->_params, $params) : $params;
return $this;
}
/**
* Get the collection params
*
* @return array
*/
public function params(): array
{
return $this->getParams();
}
/**
* Add a single page to a collection
*
* @param PageInterface $page
*
* @return static
*/
public function addPage(PageInterface $page)
{
if (!$page instanceof FlexObjectInterface) {
throw new \InvalidArgumentException('$page is not a flex page.');
}
// FIXME: support other keys.
$this->set($page->getKey(), $page);
return $this;
}
/**
*
* Merge another collection with the current collection
*
* @param PageCollectionInterface $collection
* @return static
*/
public function merge(PageCollectionInterface $collection)
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
}
/**
* Intersect another collection with the current collection
*
* @param PageCollectionInterface $collection
* @return static
*/
public function intersect(PageCollectionInterface $collection)
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
}
/**
* Return previous item.
*
* @return PageInterface|false
*/
public function prev()
{
// FIXME: this method does not rewind the internal pointer!
$key = $this->key();
$prev = $this->prevSibling($key);
return $prev !== $this->current() ? $prev : false;
}
/**
* Return nth item.
* @param int $key
*
* @return PageInterface|bool
*/
public function nth($key)
{
return $this->slice($key, 1)[0] ?? false;
}
/**
* Pick one or more random entries.
*
* @param int $num Specifies how many entries should be picked.
* @return static
*/
public function random($num = 1)
{
return $this->createFrom($this->shuffle()->slice(0, $num));
}
/**
* Append new elements to the list.
*
* @param array $items Items to be appended. Existing keys will be overridden with the new values.
*
* @return static
*/
public function append($items)
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
}
/**
* Split collection into array of smaller collections.
*
* @param int $size
* @return static[]
*/
public function batch($size): array
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
}
/**
* Reorder collection.
*
* @param string $by
* @param string $dir
* @param array $manual
* @param string $sort_flags
*
* @return static
*/
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
}
/**
* Returns the items between a set of date ranges of either the page date field (default) or
* an arbitrary datetime page field where end date is optional
* Dates can be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
* @param string $startDate
* @param string|bool $endDate
* @param string|null $field
*
* @return static
* @throws \Exception
*/
public function dateRange($startDate, $endDate = false, $field = null)
{
$start = Utils::date2timestamp($startDate);
$end = $endDate ? Utils::date2timestamp($endDate) : false;
$entries = [];
foreach ($this as $key => $object) {
if (!$object) {
continue;
}
$date = $field ? strtotime($object->getNestedProperty($field)) : $object->date();
if ($date >= $start && (!$end || $date <= $end)) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only visible pages
*
* @return static The collection with only visible pages
*/
public function visible()
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && $object->visible()) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only non-visible pages
*
* @return static The collection with only non-visible pages
*/
public function nonVisible()
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && !$object->visible()) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only modular pages
*
* @return static The collection with only modular pages
*/
public function modular()
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && $object->modular()) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only non-modular pages
*
* @return static The collection with only non-modular pages
*/
public function nonModular()
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && !$object->modular()) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only published pages
*
* @return static The collection with only published pages
*/
public function published()
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && $object->published()) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only non-published pages
*
* @return static The collection with only non-published pages
*/
public function nonPublished()
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && !$object->published()) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only routable pages
*
* @return static The collection with only routable pages
*/
public function routable()
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && $object->routable()) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only non-routable pages
*
* @return static The collection with only non-routable pages
*/
public function nonRoutable()
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && !$object->routable()) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only pages of the specified type
*
* @param string $type
*
* @return static The collection
*/
public function ofType($type)
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && $object->template() === $type) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only pages of one of the specified types
*
* @param string[] $types
*
* @return static The collection
*/
public function ofOneOfTheseTypes($types)
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && \in_array($object->template(), $types, true)) {
$entries[$key] = $object;
}
}
return $this->createFrom($entries);
}
/**
* Creates new collection with only pages of one of the specified access levels
*
* @param array $accessLevels
*
* @return static The collection
*/
public function ofOneOfTheseAccessLevels($accessLevels)
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && isset($object->header()->access)) {
if (\is_array($object->header()->access)) {
//Multiple values for access
$valid = false;
foreach ($object->header()->access as $index => $accessLevel) {
if (\is_array($accessLevel)) {
foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
if (\in_array($innerAccessLevel, $accessLevels)) {
$valid = true;
}
}
} else {
if (\in_array($index, $accessLevels)) {
$valid = true;
}
}
}
if ($valid) {
$entries[$key] = $object;
}
} else {
//Single value for access
if (\in_array($object->header()->access, $accessLevels)) {
$entries[$key] = $object;
}
}
}
}
return $this->createFrom($entries);
}
/**
* Get the extended version of this Collection with each page keyed by route
*
* @return array
* @throws \Exception
*/
public function toExtendedArray(): array
{
$entries = [];
foreach ($this as $key => $object) {
if ($object) {
$entries[$object->route()] = $object->toArray();
}
}
return $entries;
}
/**
* @param array $options
* @return array
*/
public function getLevelListing(array $options): array
{
return $this->getIndex()->getLevelListing($options);
}
}

View File

@@ -0,0 +1,403 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Flex;
use Grav\Common\File\CompiledJsonFile;
use Grav\Common\Grav;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Utils;
use Grav\Framework\Flex\FlexDirectory;
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
use Grav\Framework\Flex\Interfaces\FlexStorageInterface;
use Grav\Framework\Flex\Pages\FlexPageIndex;
/**
* Class GravPageObject
* @package Grav\Plugin\FlexObjects\Types\GravPages
*/
class PageIndex extends FlexPageIndex
{
const VERSION = parent::VERSION . '.5';
const ORDER_LIST_REGEX = '/(\/\d+)\.[^\/]+/u';
const PAGE_ROUTE_REGEX = '/\/\d+\./u';
/** @var FlexObjectInterface */
protected $_root;
protected $_params;
/**
* @param array $entries
* @param FlexDirectory|null $directory
*/
public function __construct(array $entries = [], FlexDirectory $directory = null)
{
// Remove root if it's taken.
if (isset($entries[''])) {
$this->_root = $entries[''];
unset($entries['']);
}
parent::__construct($entries, $directory);
}
/**
* @param array $entries
* @param string|null $keyField
* @return $this|FlexPageIndex
*/
protected function createFrom(array $entries, string $keyField = null)
{
/** @var static $index */
$index = parent::createFrom($entries, $keyField);
$index->_root = $this->getRoot();
return $index;
}
/**
* @param FlexStorageInterface $storage
* @return array
*/
public static function loadEntriesFromStorage(FlexStorageInterface $storage) : array
{
// Load saved index.
$index = static::loadIndex($storage);
$timestamp = $index['timestamp'] ?? 0;
if ($timestamp && $timestamp > time() - 2) {
return $index['index'];
}
// Load up to date index.
$entries = parent::loadEntriesFromStorage($storage);
return static::updateIndexFile($storage, $index['index'], $entries, ['include_missing' => true]);
}
/**
* @param string $key
* @return FlexObjectInterface|PageInterface|null
*/
public function get($key)
{
if (mb_strpos($key, '|') !== false) {
[$key, $params] = explode('|', $key, 2);
}
$element = parent::get($key);
if (isset($params)) {
$element = $element->getTranslation(ltrim($params, '.'));
}
return $element;
}
/**
* @return FlexObjectInterface|PageInterface
*/
public function getRoot()
{
$root = $this->_root;
if (is_array($root)) {
$this->_root = $this->getFlexDirectory()->createObject(['__META' => $root], '/');
}
return $this->_root;
}
/**
* Get the collection params
*
* @return array
*/
public function getParams(): array
{
return $this->_params ?? [];
}
/**
* Set parameters to the Collection
*
* @param array $params
*
* @return $this
*/
public function setParams(array $params)
{
$this->_params = $this->_params ? array_merge($this->_params, $params) : $params;
return $this;
}
/**
* Get the collection params
*
* @return array
*/
public function params(): array
{
return $this->getParams();
}
/**
* @param array $options
* @return array
*/
public function getLevelListing(array $options): array
{
$options += [
'field' => null,
'route' => null,
'leaf_route' => null,
'sortby' => null,
'order' => SORT_ASC,
'lang' => null,
'filters' => [],
];
$options['filters'] += [
'type' => ['root', 'dir'],
];
return $this->getLevelListingRecurse($options);
}
/**
* @param array $options
* @return array
*/
protected function getLevelListingRecurse(array $options): array
{
$filters = $options['filters'];
$filter_type = (array)$filters['type'];
$field = $options['field'];
$route = $options['route'];
$leaf_route = $options['leaf_route'];
$sortby = $options['sortby'];
$order = $options['order'];
$language = $options['lang'];
$status = 'error';
$msg = null;
$response = [];
$children = null;
$sub_route = null;
$extra = null;
// Handle leaf_route
$leaf = null;
if ($leaf_route && $route !== $leaf_route) {
$nodes = explode('/', $leaf_route);
$sub_route = '/' . implode('/', array_slice($nodes, 1, $options['level']++));
$options['route'] = $sub_route;
[$status,,$leaf,$extra] = $this->getLevelListingRecurse($options);
}
// Handle no route, assume page tree root
if (!$route) {
$page = $this->getRoot();
} else {
$page = $this->get(trim($route, '/'));
}
$path = $page ? $page->path() : null;
if ($field) {
$blueprint = $page ? $page->getBlueprint() : $this->getFlexDirectory()->getBlueprint();
$settings = $blueprint->schema()->getProperty($field);
$filters = array_merge([], $filters, $settings['filters'] ?? []);
$filter_type = $filters['type'] ?? $filter_type;
}
if ($page) {
if ($page->root() && (!$filters['type'] || in_array('root', $filter_type, true))) {
if ($field) {
$response[] = [
'name' => '<root>',
'value' => '/',
'item-key' => '',
'filename' => '.',
'extension' => '',
'type' => 'root',
'modified' => $page->modified(),
'size' => 0,
'symlink' => false
];
} else {
$response[] = [
'item-key' => '',
'icon' => 'root',
'title' => '<root>',
'route' => '/',
'raw_route' => null,
'modified' => $page->modified(),
'child_count' => 0,
'extras' => [
'template' => null,
'langs' => [],
'published' => false,
'published_date' => null,
'unpublished_date' => null,
'visible' => false,
'routable' => false,
'tags' => ['non-routable'],
'actions' => [],
]
];
}
}
$status = 'success';
$msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND';
$children = $page->children();
/** @var PageObject $child */
foreach ($children as $child) {
$selected = $child->path() === $extra;
$includeChildren = \is_array($leaf) && !empty($leaf) && $selected;
if (!$selected && !$child->filterBy($filters)) {
continue;
}
if ($field) {
$payload = [
'name' => $child->title(),
'value' => $child->rawRoute(),
'item-key' => basename($child->rawRoute()),
'filename' => $child->folder(),
'extension' => $child->extension(),
'type' => 'dir',
'modified' => $child->modified(),
'size' => count($child->children()),
'symlink' => false
];
} else {
// TODO: all these features are independent from each other, we cannot just have one icon/color to catch all.
// TODO: maybe icon by home/modular/page/folder (or even from blueprints) and color by visibility etc..
if ($child->home()) {
$icon = 'home';
} elseif ($child->modular()) {
$icon = 'modular';
} elseif ($child->visible()) {
$icon = 'visible';
} elseif ($child->isPage()) {
$icon = 'page';
} else {
// TODO: add support
$icon = 'folder';
}
$tags = [
$child->published() ? 'published' : 'non-published',
$child->visible() ? 'visible' : 'non-visible',
$child->routable() ? 'routable' : 'non-routable'
];
$lang = $child->findTranslation($language) ?? 'n/a';
$child = $child->getTranslation($language) ?? $child;
$extras = [
'template' => $child->template(),
'lang' => $lang ?: null,
'translated' => $lang ? $child->hasTranslation($language, false) : null,
'langs' => $child->getAllLanguages(true) ?: null,
'published' => $child->published(),
'published_date' => $this->jsDate($child->publishDate()),
'unpublished_date' => $this->jsDate($child->unpublishDate()),
'visible' => $child->visible(),
'routable' => $child->routable(),
'tags' => $tags,
'actions' => null,
];
$extras = array_filter($extras, static function ($v) {
return $v !== null;
});
$payload = [
'item-key' => basename($child->rawRoute()),
'icon' => $icon,
'title' => $child->title(),
'route' => [
'display' => $child->getRoute()->toString(false) ?: '/',
'raw' => $child->rawRoute(),
],
'modified' => $this->jsDate($child->modified()),
'child_count' => count($child->children()) ?: null,
'extras' => $extras
];
$payload = array_filter($payload, static function ($v) {
return $v !== null;
});
}
// Add children if any
if ($includeChildren) {
$payload['children'] = array_values($leaf);
}
$response[] = $payload;
}
} else {
$msg = 'PLUGIN_ADMIN.PAGE_ROUTE_NOT_FOUND';
}
// Sorting
if ($sortby) {
$response = Utils::sortArrayByKey($response, $sortby, $order);
}
if ($field) {
$temp_array = [];
foreach ($response as $index => $item) {
$temp_array[$item['type']][$index] = $item;
}
$sorted = Utils::sortArrayByArray($temp_array, $filter_type);
$response = Utils::arrayFlatten($sorted);
}
return [$status, $msg ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED', $response, $path];
}
/**
* @param FlexStorageInterface $storage
* @return CompiledJsonFile|\Grav\Common\File\CompiledYamlFile|null
*/
protected static function getIndexFile(FlexStorageInterface $storage)
{
if (!method_exists($storage, 'isIndexed') || !$storage->isIndexed()) {
return null;
}
// Load saved index file.
$grav = Grav::instance();
$locator = $grav['locator'];
$filename = $locator->findResource('user-data://flex/indexes/pages.json', true, true);
return CompiledJsonFile::instance($filename);
}
/**
* @param int|null $timestamp
* @return string|null
*/
private function jsDate(int $timestamp = null): ?string
{
if (!$timestamp) {
return null;
}
$config = Grav::instance()['config'];
$dateFormat = $config->get('system.pages.dateformat.long');
return date($dateFormat, $timestamp) ?: null;
}
}

View File

@@ -0,0 +1,407 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Flex;
use Grav\Common\Data\Blueprint;
use Grav\Common\Grav;
use Grav\Common\Page\Flex\Traits\PageContentTrait;
use Grav\Common\Page\Flex\Traits\PageLegacyTrait;
use Grav\Common\Page\Flex\Traits\PageRoutableTrait;
use Grav\Common\Page\Flex\Traits\PageTranslateTrait;
use Grav\Common\Page\Pages;
use Grav\Common\Utils;
use Grav\Framework\Flex\FlexObject;
use Grav\Framework\Flex\Pages\FlexPageObject;
use Grav\Framework\Route\Route;
use Grav\Framework\Route\RouteFactory;
use Grav\Plugin\Admin\Admin;
use RocketTheme\Toolbox\Event\Event;
/**
* Class GravPageObject
* @package Grav\Plugin\FlexObjects\Types\GravPages
*
* @property string $name
* @property string $route
* @property string $folder
* @property int|false $order
* @property string $template
* @property string $language
*/
class PageObject extends FlexPageObject
{
use PageContentTrait;
use PageLegacyTrait;
use PageRoutableTrait;
use PageTranslateTrait;
/** @var string Language code, eg: 'en' */
protected $language;
/** @var string File format, eg. 'md' */
protected $format;
private $_initialized = false;
/**
* @return array
*/
public static function getCachedMethods(): array
{
return [
'path' => true,
'full_order' => true
] + parent::getCachedMethods();
}
public function initialize(): void
{
if (!$this->_initialized) {
Grav::instance()->fireEvent('onPageProcessed', new Event(['page' => $this]));
$this->_initialized = true;
}
}
/**
* @param string|array $query
* @return Route
*/
public function getRoute($query = []): Route
{
$route = RouteFactory::createFromString($this->route());
if (\is_array($query)) {
foreach ($query as $key => $value) {
$route = $route->withQueryParam($key, $value);
}
} else {
$route = $route->withAddedPath($query);
}
return $route;
}
/**
* @inheritdoc PageInterface
*/
public function getFormValue(string $name, $default = null, string $separator = null)
{
$test = new \stdClass();
$value = $this->pageContentValue($name, $test);
if ($value !== $test) {
return $value;
}
switch ($name) {
case 'name':
// TODO: this should not be template!
return $this->getProperty('template');
case 'route':
$key = dirname($this->hasKey() ? '/' . $this->getKey() : '/');
return $key !== '/' ? $key : null;
case 'full_route':
return $this->hasKey() ? '/' . $this->getKey() : '';
case 'full_order':
return $this->full_order();
case 'lang':
return $this->getLanguage() ?? '';
case 'translations':
return $this->getLanguages();
}
return parent::getFormValue($name, $default, $separator);
}
/**
* @param array|bool $reorder
* @return FlexObject|\Grav\Framework\Flex\Interfaces\FlexObjectInterface
*/
public function save($reorder = true)
{
// Reorder siblings.
if ($reorder === true) {
$reorder = $this->_reorder ?: false;
}
$siblings = is_array($reorder) ? $this->reorderSiblings($reorder) : [];
/** @var static $instance */
$instance = parent::save();
foreach ($siblings as $sibling) {
$sibling->save(false);
}
return $instance;
}
/**
* @param array $ordering
* @return PageCollection
*/
protected function reorderSiblings(array $ordering)
{
$storageKey = $this->getStorageKey();
$oldParentKey = ltrim(dirname("/$storageKey"), '/');
$newParentKey = $this->getProperty('parent_key');
$slug = basename($this->getKey());
$order = $oldParentKey === $newParentKey ? $this->order() : false;
$k = $slug !== '' ? array_search($slug, $ordering, true) : false;
if ($order === false) {
if ($k !== false) {
unset($ordering[$k]);
}
} elseif ($k === false) {
$ordering[999999] = $slug;
}
$ordering = array_values($ordering);
$parent = $this->parent();
/** @var PageCollection $siblings */
$siblings = $parent ? $parent->children() : null;
$siblings = $siblings ? $siblings->withVisible()->getCollection() : null;
if ($siblings) {
$ordering = array_flip($ordering);
if ($storageKey !== null) {
$siblings->remove($storageKey);
if (isset($ordering[$slug])) {
$siblings->set($storageKey, $this);
}
}
$count = count($ordering);
foreach ($siblings as $sibling) {
$newOrder = $ordering[basename($sibling->getKey())] ?? null;
$oldOrder = $sibling->order();
$sibling->order(null !== $newOrder ? $newOrder + 1 : $oldOrder + $count);
}
$siblings = $siblings->orderBy(['order' => 'ASC']);
$siblings->removeElement($this);
} else {
$siblings = $this->getFlexDirectory()->createCollection([]);
}
return $siblings;
}
public function full_order(): string
{
$path = $this->path();
return preg_replace(PageIndex::ORDER_LIST_REGEX, '\\1', $path . '/' . $this->folder());
}
/**
* @param string $name
* @return Blueprint
*/
protected function doGetBlueprint(string $name = ''): Blueprint
{
try {
// Make sure that pages has been initialized.
Pages::getTypes();
// TODO: We need to move raw blueprint logic to Grav itself to remove admin dependency here.
if ($name === 'raw') {
// Admin RAW mode.
/** @var Admin $admin */
$admin = Grav::instance()['admin'] ?? null;
if ($admin) {
$template = $this->modular() ? 'modular_raw' : 'raw';
return $admin->blueprints("admin/pages/{$template}");
}
}
$template = $this->getProperty('template') . ($name ? '.' . $name : '');
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
} catch (\RuntimeException $e) {
$template = 'default' . ($name ? '.' . $name : '');
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
}
return $blueprint;
}
/**
* @param array $options
* @return array
*/
public function getLevelListing(array $options): array
{
return $this->getFlexDirectory()->getIndex()->getLevelListing($options);
}
/**
* Filter page (true/false) by given filters. If filter value is either null or '', it will be skipped.
*
* - search: string
* - extension: string
* - modular: bool
* - visible: bool
* - routable: bool
* - published: bool
* - page: bool
* - translated: bool
*
* @param array $filters
* @return bool
*/
public function filterBy(array $filters): bool
{
foreach ($filters as $key => $value) {
if ($value === null || $value === '') {
continue;
}
switch ($key) {
case 'search':
$matches = $this->search((string)$value);
break;
case 'page_type': // filename
$matches = true;
break;
case 'extension': // .en.md
$matches = Utils::contains((string)$value, $this->extension());
break;
case 'modular': // _name (filename)
case 'visible': // order
case 'routable':
case 'published':
$matches = $this->{$key}() === (bool)$value;
break;
case 'page': // markdown in index
$matches = $this->isPage() === (bool)$value;
break;
case 'translated': // markdown in index
$matches = $this->hasTranslation() === (bool)$value;
break;
default:
$matches = true;
break;
}
if ($matches === false) {
return false;
}
}
return true;
}
/**
* @return array
*/
public function __debugInfo(): array
{
$list = parent::__debugInfo();
return $list + [
'_content_meta:private' => $this->getContentMeta(),
'_content:private' => $this->getRawContent()
];
}
/**
* @param array $elements
* @param bool $extended
*/
protected function filterElements(array &$elements, bool $extended = false): void
{
// Deal with ordering=bool and order=page1,page2,page3.
if (array_key_exists('ordering', $elements) && array_key_exists('order', $elements)) {
$ordering = (bool)($elements['ordering'] ?? false);
$slug = preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->getProperty('folder'));
$list = !empty($elements['order']) ? explode(',', $elements['order']) : [];
if ($ordering) {
$order = array_search($slug, $list, true);
if ($order !== false) {
$order++;
} else {
$order = $this->getProperty('order') ?: 1;
}
} else {
$order = false;
}
$this->_reorder = $list;
$elements['order'] = $order;
}
// Change storage location if needed.
if (array_key_exists('route', $elements) && isset($elements['folder'], $elements['name'])) {
$elements['template'] = $elements['name'];
$parentRoute = $elements['route'] ?? '';
// Figure out storage path to the new route.
$parentKey = trim($parentRoute, '/');
if ($parentKey !== '') {
// Make sure page isn't being moved under itself.
$key = $this->getKey();
if ($key === $parentKey || strpos($parentKey, $key . '/') === 0) {
throw new \RuntimeException(sprintf('Page %s cannot be moved to %s', '/' . $key, $parentRoute));
}
/** @var PageObject $parent */
$parent = $this->getFlexDirectory()->getObject($parentKey);
if (!$parent) {
// Page cannot be moved to non-existing location.
throw new \RuntimeException(sprintf('Page %s cannot be moved to non-existing path %s', '/' . $key, $parentRoute));
}
// If parent changes and page is visible, move it to be the last item.
if ($parent && !empty($elements['order']) && $parent !== $this->parent()) {
$siblings = $parent->children();
$siblings = $siblings ? $siblings->visible()->sort(['order' => 'ASC']) : null;
if ($siblings && $siblings->count()) {
$elements['order'] = ((int)$siblings->last()->order()) + 1;
} else {
$elements['order'] = 1;
}
}
$parentKey = $parent->getStorageKey();
}
$elements['parent_key'] = $parentKey;
}
parent::filterElements($elements, true);
}
/**
* @return array
*/
public function prepareStorage(): array
{
$meta = $this->getMetaData();
$oldLang = $meta['lang'] ?? '';
$newLang = $this->getProperty('lang');
// Always clone the page to the new language.
if ($oldLang !== $newLang) {
$meta['clone'] = true;
}
// Make sure that certain elements are always sent to the storage layer.
$elements = [
'__META' => $meta,
'storage_key' => $this->getStorageKey(),
'parent_key' => $this->getProperty('parent_key'),
'order' => $this->getProperty('order'),
'folder' => preg_replace('|^\d+\.|', '', $this->getProperty('folder')),
'template' => preg_replace('|modular/|', '', $this->getProperty('template')),
'lang' => $newLang
] + parent::prepareStorage();
return $elements;
}
}

View File

@@ -0,0 +1,590 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Flex;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Framework\File\MarkdownFile;
use Grav\Framework\File\YamlFile;
use Grav\Framework\Flex\Storage\FolderStorage;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* Class GravPageStorage
* @package Grav\Plugin\FlexObjects\Types\GravPages
*/
class PageStorage extends FolderStorage
{
protected $ignore_files;
protected $ignore_folders;
protected $ignore_hidden;
protected $include_default_lang_file_extension;
protected $recurse;
protected $base_path;
protected $flags;
protected $regex;
protected function initOptions(array $options): void
{
parent::initOptions($options);
$this->flags = \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
| \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS;
$grav = Grav::instance();
$config = $grav['config'];
$this->ignore_hidden = (bool)$config->get('system.pages.ignore_hidden');
$this->ignore_files = (array)$config->get('system.pages.ignore_files');
$this->ignore_folders = (array)$config->get('system.pages.ignore_folders');
$this->include_default_lang_file_extension = $config->get('system.languages.include_default_lang_file_extension', true);
$this->recurse = $options['recurse'] ?? true;
$this->regex = '/(\.([\w\d_-]+))?\.md$/D';
}
/**
* @param string $key
* @param bool $variations
* @return array
*/
public function parseKey(string $key, bool $variations = true): array
{
if (mb_strpos($key, '|') !== false) {
[$key, $params] = explode('|', $key, 2);
} else {
$params = '';
}
$key = ltrim($key, '/');
$keys = parent::parseKey($key, false) + ['params' => $params];
if ($variations) {
$keys += $this->parseParams($key, $params);
}
return $keys;
}
public function readFrontmatter(string $key): string
{
$path = $this->getPathFromKey($key);
$file = $this->getFile($path);
try {
if ($file instanceof MarkdownFile) {
$frontmatter = $file->frontmatter();
} else {
$frontmatter = $file->raw();
}
} catch (\RuntimeException $e) {
$frontmatter = 'ERROR: ' . $e->getMessage();
}
return $frontmatter;
}
public function readRaw(string $key): string
{
$path = $this->getPathFromKey($key);
$file = $this->getFile($path);
try {
$raw = $file->raw();
} catch (\RuntimeException $e) {
$raw = 'ERROR: ' . $e->getMessage();
}
return $raw;
}
/**
* @param array $keys
* @param bool $includeParams
* @return string
*/
public function buildStorageKey(array $keys, bool $includeParams = true): string
{
$key = $keys['key'] ?? null;
if (null === $key) {
$key = $keys['parent_key'] ?? '';
if ($key !== '') {
$key .= '/';
}
$order = $keys['order'] ?? 0;
$folder = $keys['folder'] ?? 'undefined';
$key .= $order ? sprintf('%02d.%s', $order, $folder) : $folder;
}
$params = $includeParams ? $this->buildStorageKeyParams($keys) : '';
return $params ? "{$key}|{$params}" : $key;
}
/**
* @param array $keys
* @return string
*/
public function buildStorageKeyParams(array $keys): string
{
$params = $keys['template'] ?? '';
$language = $keys['lang'] ?? '';
if ($language) {
$params .= '.' . $language;
}
return $params;
}
/**
* @param array $keys
* @return string
*/
public function buildFolder(array $keys): string
{
return $this->dataFolder . '/' . $this->buildStorageKey($keys, false);
}
/**
* @param array $keys
* @return string
*/
public function buildFilename(array $keys): string
{
$file = $this->buildStorageKeyParams($keys);
// Template is optional; if it is missing, we need to have to load the object metadata.
if ($file && $file[0] === '.') {
$meta = $this->getObjectMeta($this->buildStorageKey($keys, false));
$file = ($meta['template'] ?? 'folder') . $file;
}
return $file . $this->dataExt;
}
/**
* @param array $keys
* @return string
*/
public function buildFilepath(array $keys): string
{
return $this->buildFolder($keys) . '/' . $this->buildFilename($keys);
}
/**
* @param array $row
* @return array
*/
public function extractKeysFromRow(array $row): array
{
$meta = $row['__META'] ?? null;
$storageKey = $row['storage_key'] ?? $meta['storage_key'] ?? '';
$keyMeta = $storageKey !== '' ? $this->extractKeysFromStorageKey($storageKey) : null;
$parentKey = $row['parent_key'] ?? $meta['parent_key'] ?? $keyMeta['parent_key'] ?? '';
$order = $row['order'] ?? $meta['order'] ?? $keyMeta['order'] ?? '';
$folder = $row['folder'] ?? $meta['folder'] ?? $keyMeta['folder'] ?? '';
$template = $row['template'] ?? $meta['template'] ?? $keyMeta['template'] ?? '';
$lang = $row['lang'] ?? $meta['lang'] ?? $keyMeta['lang'] ?? '';
// Handle default language, if it should be saved without language extension.
if ($lang && !$this->include_default_lang_file_extension && empty($meta['markdown'][$lang])) {
$grav = Grav::instance();
/** @var Language $langauge */
$language = $grav['language'];
if ($lang === $language->getDefault()) {
$lang = '';
}
}
$keys = [
'key' => null,
'params' => null,
'parent_key' => $parentKey,
'order' => (int)$order,
'folder' => $folder,
'template' => $template,
'lang' => $lang
];
$keys['key'] = $this->buildStorageKey($keys, false);
$keys['params'] = $this->buildStorageKeyParams($keys);
return $keys;
}
/**
* @param string $key
* @return array
*/
public function extractKeysFromStorageKey(string $key): array
{
if (mb_strpos($key, '|') !== false) {
[$key, $params] = explode('|', $key, 2);
[$template, $language] = mb_strpos($params, '.') !== false ? explode('.', $params, 2) : [$params, ''];
} else {
$params = $template = $language = '';
}
$objectKey = basename($key);
if (preg_match('|^(\d+)\.(.+)$|', $objectKey, $matches)) {
[, $order, $folder] = $matches;
} else {
[$order, $folder] = ['', $objectKey];
}
$parentKey = ltrim(dirname('/' . $key), '/');
return [
'key' => $key,
'params' => $params,
'parent_key' => $parentKey,
'order' => (int)$order,
'folder' => $folder,
'template' => $template,
'lang' => $language
];
}
/**
* @param string $key
* @param string $params
* @return array
*/
protected function parseParams(string $key, string $params): array
{
if (mb_strpos($params, '.') !== false) {
[$template, $language] = explode('.', $params, 2);
} else {
$template = $params;
$language = '';
}
if ($template === '') {
$meta = $this->getObjectMeta($key);
$template = $meta['template'] ?? 'folder';
}
return [
'file' => $template . ($language ? '.' . $language : ''),
'template' => $template,
'lang' => $language
];
}
/**
* Prepares the row for saving and returns the storage key for the record.
*
* @param array $row
*/
protected function prepareRow(array &$row): void
{
// Remove keys used in the filesystem.
unset($row['parent_key'], $row['order'], $row['folder'], $row['template'], $row['lang']);
}
/**
* Page storage supports moving and copying the pages and their languages.
*
* $row['__META']['copy'] = true Use this if you want to copy the whole folder, otherwise it will be moved
* $row['__META']['clone'] = true Use this if you want to clone the file, otherwise it will be renamed
*
* @param string $key
* @param array $row
* @return array
*/
protected function saveRow(string $key, array $row): array
{
// Initialize all key-related variables.
$newKeys = $this->extractKeysFromRow($row);
$newKey = $this->buildStorageKey($newKeys);
$newFolder = $this->buildFolder($newKeys);
$newFilename = $this->buildFilename($newKeys);
$newFilepath = "{$newFolder}/{$newFilename}";
try {
$grav = Grav::instance();
/** @var Debugger $debugger */
$debugger = $grav['debugger'];
$debugger->addMessage("Save page: {$newKey}", 'debug');
// Check if the row already exists.
$oldKey = $row['__META']['storage_key'] ?? null;
if (is_string($oldKey)) {
// Initialize all old key-related variables.
$oldKeys = $this->extractKeysFromRow(['__META' => $row['__META']]);
$oldFolder = $this->buildFolder($oldKeys);
$oldFilename = $this->buildFilename($oldKeys);
// Check if folder has changed.
if ($oldFolder !== $newFolder && file_exists($oldFolder)) {
$isCopy = $row['__META']['copy'] ?? false;
if ($isCopy) {
$this->copyRow($oldKey, $newKey);
$debugger->addMessage("Page copied: {$oldFolder} => {$newFolder}", 'debug');
} else {
$this->renameRow($oldKey, $newKey);
$debugger->addMessage("Page moved: {$oldFolder} => {$newFolder}", 'debug');
}
}
// Check if filename has changed.
if ($oldFilename !== $newFilename) {
// Get instance of the old file (we have already copied/moved it).
$oldFilepath = "{$newFolder}/{$oldFilename}";
$file = $this->getFile($oldFilepath);
// Rename the file if we aren't supposed to clone it.
$isClone = $row['__META']['clone'] ?? false;
if (!$isClone && $file->exists()) {
/** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
$toPath = $locator->isStream($newFilepath) ? $locator->findResource($newFilepath, true, true) : $newFilepath;
$success = $file->rename($toPath);
if (!$success) {
throw new \RuntimeException("Changing page template failed: {$oldFilepath} => {$newFilepath}");
}
$debugger->addMessage("Page template changed: {$oldFilename} => {$newFilename}", 'debug');
} else {
$file = null;
$debugger->addMessage("Page template created: {$newFilename}", 'debug');
}
}
}
// Clean up the data to be saved.
$this->prepareRow($row);
unset($row['__META'], $row['__ERROR']);
if (!isset($file)) {
$file = $this->getFile($newFilepath);
}
// Compare existing file content to the new one and save the file only if content has been changed.
$file->free();
$oldRaw = $file->raw();
$file->content($row);
$newRaw = $file->raw();
if ($oldRaw !== $newRaw) {
$file->save($row);
$debugger->addMessage("Page content saved: {$newFilepath}", 'debug');
} else {
$debugger->addMessage('Page content has not been changed, do not update the file', 'debug');
}
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if ($locator->isStream($newFolder)) {
$locator->clearCache();
}
} catch (\RuntimeException $e) {
$name = isset($file) ? $file->filename() : $newKey;
throw new \RuntimeException(sprintf('Flex saveRow(%s): %s', $name, $e->getMessage()));
}
$row['__META'] = $this->getObjectMeta($newKey, true);
return $row;
}
protected function canDeleteFolder(string $key): bool
{
$keys = $this->extractKeysFromStorageKey($key);
if ($keys['lang']) {
return false;
}
return true;
}
/**
* Get key from the filesystem path.
*
* @param string $path
* @return string
*/
protected function getKeyFromPath(string $path): string
{
if ($this->base_path) {
$path = $this->base_path . '/' . $path;
}
return $path;
}
/**
* Returns list of all stored keys in [key => timestamp] pairs.
*
* @return array
*/
protected function buildIndex(): array
{
return $this->getIndexMeta();
}
/**
* @param string $key
* @param bool $reload
* @return array
*/
protected function getObjectMeta(string $key, bool $reload = false): array
{
$keys = $this->extractKeysFromStorageKey($key);
$key = $keys['key'];
if ($reload || !isset($this->meta[$key])) {
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if (mb_strpos($key, '@@') === false) {
$path = $locator->findResource($this->getStoragePath($key), true, true);
} else {
$path = null;
}
$modified = 0;
$markdown = [];
$children = [];
if ($path && file_exists($path)) {
$modified = filemtime($path);
$iterator = new \FilesystemIterator($path, $this->flags);
/** @var \SplFileInfo $info */
foreach ($iterator as $k => $info) {
// Ignore all hidden files if set.
if ($k === '' || ($this->ignore_hidden && $k[0] === '.')) {
continue;
}
if ($info->isDir()) {
// Ignore all folders in ignore list.
if ($this->ignore_folders && \in_array($k, $this->ignore_folders, true)) {
continue;
}
$children[$k] = false;
} else {
// Ignore all files in ignore list.
if ($this->ignore_files && \in_array($k, $this->ignore_files, true)) {
continue;
}
$timestamp = $info->getMTime();
// Page is the one that matches to $page_extensions list with the lowest index number.
if (preg_match($this->regex, $k, $matches)) {
$mark = $matches[2] ?? '';
$ext = $matches[1] ?? '';
$ext .= $this->dataExt;
$markdown[$mark][basename($k, $ext)] = $timestamp;
}
$modified = max($modified, $timestamp);
}
}
}
$rawRoute = trim(preg_replace(PageIndex::PAGE_ROUTE_REGEX, '/', "/{$key}"), '/');
$route = PageIndex::normalizeRoute($rawRoute);
ksort($markdown, SORT_NATURAL);
ksort($children, SORT_NATURAL);
$file = array_key_first($markdown[''] ?? reset($markdown) ?: []);
$meta = [
'key' => $route,
'storage_key' => $key,
'template' => $file,
'storage_timestamp' => $modified,
];
if ($markdown) {
$meta['markdown'] = $markdown;
}
if ($children) {
$meta['children'] = $children;
}
$meta['checksum'] = md5(json_encode($meta));
// Cache meta as copy.
$this->meta[$key] = $meta;
} else {
$meta = $this->meta[$key];
}
$params = $keys['params'];
if ($params) {
$language = $keys['lang'];
$template = $keys['template'] ?: array_key_first($meta['markdown'][$language]) ?? $meta['template'];
$meta['exists'] = ($template && !empty($meta['children'])) || isset($meta['markdown'][$language][$template]);
$meta['storage_key'] .= '|' . $params;
$meta['template'] = $template;
$meta['lang'] = $language;
}
return $meta;
}
protected function getIndexMeta(): array
{
$queue = [''];
$list = [];
do {
$current = array_pop($queue);
$meta = $this->getObjectMeta($current);
$storage_key = $meta['storage_key'];
if (!empty($meta['children'])) {
$prefix = $storage_key . ($storage_key !== '' ? '/' : '');
foreach ($meta['children'] as $child => $value) {
$queue[] = $prefix . $child;
}
}
$list[$storage_key] = $meta;
} while ($queue);
ksort($list, SORT_NATURAL);
// Update parent timestamps.
foreach (array_reverse($list) as $storage_key => $meta) {
if ($storage_key !== '') {
$storage_key = (string)$storage_key;
$parentKey = dirname($storage_key);
if ($parentKey === '.') {
$parentKey = '';
}
$parent = &$list[$parentKey];
$basename = basename($storage_key);
if (isset($parent['children'][$basename])) {
$timestamp = $meta['storage_timestamp'];
$parent['children'][$basename] = $timestamp;
if ($basename && $basename[0] === '_') {
$parent['storage_timestamp'] = max($parent['storage_timestamp'], $timestamp);
}
}
}
}
return $list;
}
/**
* @return string
*/
protected function getNewKey(): string
{
throw new \RuntimeException('Generating random key is disabled for pages');
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Flex\Traits;
use Grav\Common\Utils;
/**
* Implements PageContentInterface.
*/
trait PageContentTrait
{
/**
* @inheritdoc
*/
public function id($var = null): string
{
$property = 'id';
$value = null === $var ? $this->getProperty($property) : null;
if (null === $value) {
$value = $this->language() . ($var ?? ($this->modified() . md5($this->filePath())));
$this->setProperty($property, $value);
if ($this->doHasProperty($property)) {
$value = $this->getProperty($property);
}
}
return $value;
}
/**
* @inheritdoc
*/
public function date($var = null): int
{
return $this->loadHeaderProperty(
'date',
$var,
function ($value) {
$value = $value ? Utils::date2timestamp($value, $this->getProperty('dateformat')) : false;
if (!$value) {
// Get the specific translation updated date.
$meta = $this->getMetaData();
$language = $meta['lang'] ?? '';
$template = $this->getProperty('template');
$value = $meta['markdown'][$language][$template] ?? 0;
}
return $value ?: $this->modified();
}
);
}
/**
* @inheritdoc
*/
public function isPage(): bool
{
// FIXME: needs to be better
return !$this->exists() || !empty($this->getLanguages()) || $this->modular();
}
}

View File

@@ -0,0 +1,233 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Flex\Traits;
use Grav\Common\Grav;
use Grav\Common\Page\Collection;
use Grav\Common\Page\Interfaces\PageCollectionInterface;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Pages;
use Grav\Common\Utils;
/**
* Implements PageLegacyInterface.
*/
trait PageLegacyTrait
{
/**
* Returns children of this page.
*
* @return PageCollectionInterface|Collection
*/
public function children()
{
if (Utils::isAdminPlugin()) {
return parent::children();
}
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
return $pages->children($this->path());
}
/**
* Check to see if this item is the first in an array of sub-pages.
*
* @return bool True if item is first.
*/
public function isFirst(): bool
{
if (Utils::isAdminPlugin()) {
return parent::isFirst();
}
$parent = $this->parent();
$collection = $parent ? $parent->collection('content', false) : null;
if ($collection instanceof PageCollectionInterface) {
return $collection->isFirst($this->path());
}
return true;
}
/**
* Check to see if this item is the last in an array of sub-pages.
*
* @return bool True if item is last
*/
public function isLast(): bool
{
if (Utils::isAdminPlugin()) {
return parent::isLast();
}
$parent = $this->parent();
$collection = $parent ? $parent->collection('content', false) : null;
if ($collection instanceof PageCollectionInterface) {
return $collection->isLast($this->path());
}
return true;
}
/**
* Returns the adjacent sibling based on a direction.
*
* @param int $direction either -1 or +1
*
* @return PageInterface|bool the sibling page
*/
public function adjacentSibling($direction = 1)
{
if (Utils::isAdminPlugin()) {
return parent::adjacentSibling($direction);
}
$parent = $this->parent();
$collection = $parent ? $parent->collection('content', false) : null;
if ($collection instanceof PageCollectionInterface) {
return $collection->adjacentSibling($this->path(), $direction);
}
return false;
}
/**
* Helper method to return an ancestor page.
*
* @param string|null $lookup Name of the parent folder
*
* @return PageInterface|null page you were looking for if it exists
*/
public function ancestor($lookup = null)
{
if (Utils::isAdminPlugin()) {
return parent::ancestor($lookup);
}
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
return $pages->ancestor($this->getProperty('parent_route'), $lookup);
}
/**
* Method that contains shared logic for inherited() and inheritedField()
*
* @param string $field Name of the parent folder
*
* @return array
*/
protected function getInheritedParams($field): array
{
if (Utils::isAdminPlugin()) {
return parent::getInheritedParams($field);
}
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$inherited = $pages->inherited($this->getProperty('parent_route'), $field);
$inheritedParams = $inherited ? (array)$inherited->value('header.' . $field) : [];
$currentParams = (array)$this->getFormValue('header.' . $field);
if ($inheritedParams && is_array($inheritedParams)) {
$currentParams = array_replace_recursive($inheritedParams, $currentParams);
}
return [$inherited, $currentParams];
}
/**
* Helper method to return a page.
*
* @param string $url the url of the page
* @param bool $all
*
* @return PageInterface|null page you were looking for if it exists
*/
public function find($url, $all = false)
{
if (Utils::isAdminPlugin()) {
return parent::find($url, $all);
}
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
return $pages->find($url, $all);
}
/**
* Get a collection of pages in the current context.
*
* @param string|array $params
* @param bool $pagination
*
* @return Collection
* @throws \InvalidArgumentException
*/
public function collection($params = 'content', $pagination = true)
{
if (Utils::isAdminPlugin()) {
return parent::collection($params, $pagination);
}
if (is_string($params)) {
// Look into a page header field.
$params = (array)$this->getFormValue('header.' . $params);
} elseif (!is_array($params)) {
throw new \InvalidArgumentException('Argument should be either header variable name or array of parameters');
}
if (!$pagination) {
$params['pagination'] = false;
}
$context = [
'pagination' => $pagination,
'self' => $this
];
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
return $pages->getCollection($params, $context);
}
/**
* @param string|array $value
* @param bool $only_published
* @return Collection
*/
public function evaluate($value, $only_published = true)
{
if (Utils::isAdminPlugin()) {
return parent::collection($value, $only_published);
}
$params = [
'items' => $value,
'published' => $only_published
];
$context = [
'event' => false,
'pagination' => false,
'url_taxonomy_filters' => false,
'self' => $this
];
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
return $pages->getCollection($params, $context);
}
}

View File

@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Flex\Traits;
use Grav\Common\Grav;
use Grav\Common\Page\Interfaces\PageCollectionInterface;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Pages;
use Grav\Common\Utils;
/**
* Implements PageRoutableInterface.
*/
trait PageRoutableTrait
{
/**
* Gets and Sets the parent object for this page
*
* @param PageInterface|null $var the parent page object
*
* @return PageInterface|null the parent page object if it exists.
*/
public function parent(PageInterface $var = null)
{
if (Utils::isAdminPlugin()) {
return parent::parent();
}
if (null !== $var) {
throw new \RuntimeException('Not Implemented');
}
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
// FIXME: this does not work, needs to use $pages->get() with cached parent id!
$key = $this->getKey();
$parent_route = dirname('/' . $key);
return $parent_route !== '/' ? $pages->find($parent_route) : $pages->root();
}
/**
* Returns the item in the current position.
*
* @return int|null the index of the current page.
*/
public function currentPosition(): ?int
{
$parent = $this->parent();
$collection = $parent ? $parent->collection('content', false) : null;
if ($collection instanceof PageCollectionInterface) {
return $collection->currentPosition($this->path()) ?? null;
}
return 1;
}
/**
* Returns whether or not this page is the currently active page requested via the URL.
*
* @return bool True if it is active
*/
public function active(): bool
{
$grav = Grav::instance();
$uri_path = rtrim(urldecode($grav['uri']->path()), '/') ?: '/';
$routes = $grav['pages']->routes();
return isset($routes[$uri_path]) && $routes[$uri_path] === $this->path();
}
/**
* Returns whether or not this URI's URL contains the URL of the active page.
* Or in other words, is this page's URL in the current URL
*
* @return bool True if active child exists
*/
public function activeChild(): bool
{
$grav = Grav::instance();
$uri = $grav['uri'];
$pages = $grav['pages'];
$uri_path = rtrim(urldecode($uri->path()), '/');
$routes = $pages->routes();
if (isset($routes[$uri_path])) {
/** @var PageInterface $child_page */
$child_page = $pages->dispatch($uri->route(), false, false)->parent();
if ($child_page) {
while (!$child_page->root()) {
if ($this->path() === $child_page->path()) {
return true;
}
$child_page = $child_page->parent();
}
}
}
return false;
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Flex\Traits;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
use Grav\Common\Utils;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* Implements PageTranslateInterface
*/
trait PageTranslateTrait
{
/**
* Return an array with the routes of other translated languages
*
* @param bool $onlyPublished only return published translations
*
* @return array the page translated languages
*/
public function translatedLanguages($onlyPublished = false): array
{
if (Utils::isAdminPlugin()) {
return parent::translatedLanguages();
}
$translated = $this->getLanguageTemplates();
if (!$translated) {
return $translated;
}
$grav = Grav::instance();
/** @var Language $language */
$language = $grav['language'];
/** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
$languages = $language->getLanguages();
$languages[] = '';
$defaultCode = $language->getDefault();
if (isset($translated[$defaultCode])) {
unset($translated['']);
}
foreach ($translated as $key => &$template) {
$template .= $key !== '' ? ".{$key}.md" : '.md';
}
unset($template);
$translated = array_intersect_key($translated, array_flip($languages));
$folder = $this->getStorageFolder();
if (!$folder) {
return [];
}
$folder = $locator($folder);
$list = array_fill_keys($languages, null);
foreach ($translated as $languageCode => $languageFile) {
$languageExtension = $languageCode ? ".{$languageCode}.md" : '.md';
$path = "{$folder}/{$languageFile}";
// FIXME: use flex, also rawRoute() does not fully work?
$aPage = new Page();
$aPage->init(new \SplFileInfo($path), $languageExtension);
if ($onlyPublished && !$aPage->published()) {
continue;
}
$route = $aPage->header()->routes['default'] ?? $aPage->rawRoute();
if (!$route) {
$route = $aPage->route();
}
$list[$languageCode ?: $defaultCode] = $route ?? '';
}
return array_filter($list, static function ($var) {
return null !== $var;
});
}
}

View File

@@ -10,9 +10,18 @@
namespace Grav\Common\Page;
use RocketTheme\Toolbox\ArrayTraits\Constructor;
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
class Header implements \ArrayAccess
class Header implements \ArrayAccess, ExportInterface, \JsonSerializable
{
use NestedArrayAccess, Constructor;
use NestedArrayAccessWithGetters, Constructor, Export;
protected $items;
public function jsonSerialize()
{
return $this->toArray();
}
}

View File

@@ -0,0 +1,270 @@
<?php
/**
* @package Grav\Common\Page
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Page\Interfaces;
interface PageCollectionInterface extends \Traversable, \ArrayAccess, \Countable, \Serializable
{
/**
* Get the collection params
*
* @return array
*/
public function params();
/**
* Set parameters to the Collection
*
* @param array $params
*
* @return $this
*/
public function setParams(array $params);
/**
* Add a single page to a collection
*
* @param PageInterface $page
*
* @return $this
*/
public function addPage(PageInterface $page);
/**
* Add a page with path and slug
*
* @param string $path
* @param string $slug
* @return $this
*/
//public function add($path, $slug);
/**
*
* Create a copy of this collection
*
* @return static
*/
public function copy();
/**
*
* Merge another collection with the current collection
*
* @param PageCollectionInterface $collection
* @return $this
*/
public function merge(PageCollectionInterface $collection);
/**
* Intersect another collection with the current collection
*
* @param PageCollectionInterface $collection
* @return $this
*/
public function intersect(PageCollectionInterface $collection);
/**
* Split collection into array of smaller collections.
*
* @param int $size
* @return PageCollectionInterface[]
*/
public function batch($size);
/**
* Remove item from the list.
*
* @param PageInterface|string|null $key
*
* @return $this
* @throws \InvalidArgumentException
*/
//public function remove($key = null);
/**
* Reorder collection.
*
* @param string $by
* @param string $dir
* @param array $manual
* @param string $sort_flags
*
* @return $this
*/
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null);
/**
* Check to see if this item is the first in the collection.
*
* @param string $path
*
* @return bool True if item is first.
*/
public function isFirst($path): bool;
/**
* Check to see if this item is the last in the collection.
*
* @param string $path
*
* @return bool True if item is last.
*/
public function isLast($path): bool;
/**
* Gets the previous sibling based on current position.
*
* @param string $path
*
* @return PageInterface The previous item.
*/
public function prevSibling($path);
/**
* Gets the next sibling based on current position.
*
* @param string $path
*
* @return PageInterface The next item.
*/
public function nextSibling($path);
/**
* Returns the adjacent sibling based on a direction.
*
* @param string $path
* @param int $direction either -1 or +1
*
* @return PageInterface|PageCollectionInterface The sibling item.
*/
public function adjacentSibling($path, $direction = 1);
/**
* Returns the item in the current position.
*
* @param string $path the path the item
*
* @return int|null The index of the current page, null if not found.
*/
public function currentPosition($path): ?int;
/**
* Returns the items between a set of date ranges of either the page date field (default) or
* an arbitrary datetime page field where end date is optional
* Dates can be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
* @param string $startDate
* @param bool $endDate
* @param string|null $field
*
* @return $this
* @throws \Exception
*/
public function dateRange($startDate, $endDate = false, $field = null);
/**
* Creates new collection with only visible pages
*
* @return PageCollectionInterface The collection with only visible pages
*/
public function visible();
/**
* Creates new collection with only non-visible pages
*
* @return PageCollectionInterface The collection with only non-visible pages
*/
public function nonVisible();
/**
* Creates new collection with only modular pages
*
* @return PageCollectionInterface The collection with only modular pages
*/
public function modular();
/**
* Creates new collection with only non-modular pages
*
* @return PageCollectionInterface The collection with only non-modular pages
*/
public function nonModular();
/**
* Creates new collection with only published pages
*
* @return PageCollectionInterface The collection with only published pages
*/
public function published();
/**
* Creates new collection with only non-published pages
*
* @return PageCollectionInterface The collection with only non-published pages
*/
public function nonPublished();
/**
* Creates new collection with only routable pages
*
* @return PageCollectionInterface The collection with only routable pages
*/
public function routable();
/**
* Creates new collection with only non-routable pages
*
* @return PageCollectionInterface The collection with only non-routable pages
*/
public function nonRoutable();
/**
* Creates new collection with only pages of the specified type
*
* @param string $type
*
* @return PageCollectionInterface The collection
*/
public function ofType($type);
/**
* Creates new collection with only pages of one of the specified types
*
* @param string[] $types
*
* @return PageCollectionInterface The collection
*/
public function ofOneOfTheseTypes($types);
/**
* Creates new collection with only pages of one of the specified access levels
*
* @param array $accessLevels
*
* @return PageCollectionInterface The collection
*/
public function ofOneOfTheseAccessLevels($accessLevels);
/**
* Converts collection into an array.
*
* @return array
*/
public function toArray();
/**
* Get the extended version of this Collection with each page keyed by route
*
* @return array
* @throws \Exception
*/
public function toExtendedArray();
}

View File

@@ -9,7 +9,7 @@
namespace Grav\Common\Page\Interfaces;
use Grav\Common\Page\Media;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
/**
* Methods currently implemented in Flex Page emulation layer.
@@ -20,7 +20,6 @@ interface PageContentInterface
* Gets and Sets the header based on the YAML configuration at the top of the .md file
*
* @param object|array $var a YAML object representing the configuration for the file
*
* @return object the current YAML configuration
*/
public function header($var = null);
@@ -28,19 +27,23 @@ interface PageContentInterface
/**
* Get the summary.
*
* @param int $size Max summary size.
*
* @param int|null $size Max summary size.
* @param bool $textOnly Only count text size.
*
* @return string
*/
public function summary($size = null, $textOnly = false);
/**
* Sets the summary of the page
*
* @param string $summary Summary
*/
public function setSummary($summary);
/**
* Gets and Sets the content based on content portion of the .md file
*
* @param string $var Content
*
* @param string|null $var Content
* @return string Content
*/
public function content($var = null);
@@ -63,8 +66,7 @@ interface PageContentInterface
* Gets and Sets the Page raw content
*
* @param string|null $var
*
* @return null
* @return string
*/
public function rawMarkdown($var = null);
@@ -72,7 +74,7 @@ interface PageContentInterface
* Get value from a page variable (used mostly for creating edit forms).
*
* @param string $name Variable name.
* @param mixed $default
* @param mixed|null $default
*
* @return mixed
*/
@@ -81,18 +83,16 @@ interface PageContentInterface
/**
* Gets and sets the associated media as found in the page folder.
*
* @param Media $var Representation of associated media.
*
* @return Media Representation of associated media.
* @param MediaCollectionInterface|null $var New media object.
* @return MediaCollectionInterface Representation of associated media.
*/
public function media($var = null);
/**
* Gets and sets the title for this Page. If no title is set, it will use the slug() to get a name
*
* @param string $var the title of the Page
*
* @return string the title of the Page
* @param string|null $var New title of the Page
* @return string The title of the Page
*/
public function title($var = null);
@@ -100,45 +100,40 @@ interface PageContentInterface
* Gets and sets the menu name for this Page. This is the text that can be used specifically for navigation.
* If no menu field is set, it will use the title()
*
* @param string $var the menu field for the page
*
* @return string the menu field for the page
* @param string|null $var New menu field for the page
* @return string The menu field for the page
*/
public function menu($var = null);
/**
* Gets and Sets whether or not this Page is visible for navigation
*
* @param bool $var true if the page is visible
*
* @return bool true if the page is visible
* @param bool|null $var New value
* @return bool True if the page is visible
*/
public function visible($var = null);
/**
* Gets and Sets whether or not this Page is considered published
*
* @param bool $var true if the page is published
*
* @return bool true if the page is published
* @param bool|null $var New value
* @return bool True if the page is published
*/
public function published($var = null);
/**
* Gets and Sets the Page publish date
*
* @param string $var string representation of a date
*
* @return int unix timestamp representation of the date
* @param string|null $var String representation of the new date
* @return int Unix timestamp representation of the date
*/
public function publishDate($var = null);
/**
* Gets and Sets the Page unpublish date
*
* @param string $var string representation of a date
*
* @return int|null unix timestamp representation of the date
* @param string|null $var String representation of the new date
* @return int|null Unix timestamp representation of the date
*/
public function unpublishDate($var = null);
@@ -146,9 +141,8 @@ interface PageContentInterface
* Gets and Sets the process setup for this Page. This is multi-dimensional array that consists of
* a simple array of arrays with the form array("markdown"=>true) for example
*
* @param array $var an Array of name value pairs where the name is the process and value is true or false
*
* @return array an Array of name value pairs where the name is the process and value is true or false
* @param array|null $var New array of name value pairs where the name is the process and value is true or false
* @return array Array of name value pairs where the name is the process and value is true or false
*/
public function process($var = null);
@@ -156,63 +150,56 @@ interface PageContentInterface
* Gets and Sets the slug for the Page. The slug is used in the URL routing. If not set it uses
* the parent folder from the path
*
* @param string $var the slug, e.g. 'my-blog'
*
* @return string the slug
* @param string|null $var New slug, e.g. 'my-blog'
* @return string The slug
*/
public function slug($var = null);
/**
* Get/set order number of this page.
*
* @param int $var
*
* @return int|bool
* @param int|null $var New order as a number
* @return string|bool Order in a form of '02.' or false if not set
*/
public function order($var = null);
/**
* Gets and sets the identifier for this Page object.
*
* @param string $var the identifier
*
* @return string the identifier
* @param string|null $var New identifier
* @return string The identifier
*/
public function id($var = null);
/**
* Gets and sets the modified timestamp.
*
* @param int $var modified unix timestamp
*
* @return int modified unix timestamp
* @param int|null $var New modified unix timestamp
* @return int Modified unix timestamp
*/
public function modified($var = null);
/**
* Gets and sets the option to show the last_modified header for the page.
*
* @param boolean $var show last_modified header
*
* @return boolean show last_modified header
* @param bool|null $var New last_modified header value
* @return bool Show last_modified header
*/
public function lastModified($var = null);
/**
* Get/set the folder.
*
* @param string $var Optional path
*
* @return string|null
* @param string|null $var New folder
* @return string|null The folder
*/
public function folder($var = null);
/**
* Gets and sets the date for this Page object. This is typically passed in via the page headers
*
* @param string $var string representation of a date
*
* @return int unix timestamp representation of the date
* @param string|null $var New string representation of a date
* @return int Unix timestamp representation of the date
*/
public function date($var = null);
@@ -220,27 +207,24 @@ interface PageContentInterface
* Gets and sets the date format for this Page object. This is typically passed in via the page headers
* using typical PHP date string structure - http://php.net/manual/en/function.date.php
*
* @param string $var string representation of a date format
*
* @return string string representation of a date format
* @param string|null $var New string representation of a date format
* @return string String representation of a date format
*/
public function dateformat($var = null);
/**
* Gets and sets the taxonomy array which defines which taxonomies this page identifies itself with.
*
* @param array $var an array of taxonomies
*
* @return array an array of taxonomies
* @param array|null $var New array of taxonomies
* @return array An array of taxonomies
*/
public function taxonomy($var = null);
/**
* Gets the configured state of the processing method.
*
* @param string $process the process, eg "twig" or "markdown"
*
* @return bool whether or not the processing method is enabled for this Page
* @param string $process The process name, eg "twig" or "markdown"
* @return bool Whether or not the processing method is enabled for this Page
*/
public function shouldProcess($process);

View File

@@ -0,0 +1,29 @@
<?php
namespace Grav\Common\Page\Interfaces;
interface PageFormInterface
{
/**
* Return all the forms which are associated to this page.
*
* Forms are returned as [name => blueprint, ...], where blueprint follows the regular form blueprint format.
*
* @return array
*/
//public function getForms(): array;
/**
* Add forms to this page.
*
* @param array $new
* @return $this
*/
public function addForms(array $new/*, $override = true*/);
/**
* Alias of $this->getForms();
*
* @return array
*/
public function forms();//: array;
}

View File

@@ -14,6 +14,12 @@ use Grav\Common\Media\Interfaces\MediaInterface;
/**
* Class implements page interface.
*/
interface PageInterface extends PageContentInterface, PageRoutableInterface, PageTranslateInterface, MediaInterface, PageLegacyInterface
interface PageInterface extends
PageContentInterface,
PageFormInterface,
PageRoutableInterface,
PageTranslateInterface,
MediaInterface,
PageLegacyInterface
{
}

View File

@@ -51,13 +51,6 @@ interface PageLegacyInterface
public function httpHeaders();
/**
* Sets the summary of the page
*
* @param string $summary Summary
*/
public function setSummary($summary);
/**
* Get the contentMeta array and initialize content first if it's not already
*
@@ -69,7 +62,7 @@ interface PageLegacyInterface
* Add an entry to the page's contentMeta array
*
* @param string $name
* @param string $value
* @param mixed $value
*/
public function addContentMeta($name, $value);
@@ -229,9 +222,9 @@ interface PageLegacyInterface
* 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
* @param string|null $var
*
* @return null
* @return string
*/
public function templateFormat($var = null);
@@ -288,7 +281,7 @@ interface PageLegacyInterface
*
* @return bool show etag header
*/
public function eTag($var = null);
public function eTag($var = null): bool;
/**
* Gets and sets the path to the .md file for this Page object.

View File

@@ -119,7 +119,7 @@ interface PageRoutableInterface
* Gets and sets the path to the folder where the .md for this Page object resides.
* This is equivalent to the filePath but without the filename.
*
* @param string $var the path
* @param string|null $var the path
*
* @return string|null the path
*/
@@ -137,7 +137,7 @@ interface PageRoutableInterface
/**
* Gets and Sets the parent object for this page
*
* @param PageInterface $var the parent page object
* @param PageInterface|null $var the parent page object
*
* @return PageInterface|null the parent page object if it exists.
*/
@@ -153,7 +153,7 @@ interface PageRoutableInterface
/**
* Returns the item in the current position.
*
* @return int the index of the current page.
* @return int|null The index of the current page.
*/
public function currentPosition();

View File

@@ -108,12 +108,12 @@ class Excerpts
}
}
$url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
$url_parts['query'] = http_build_query($actions, '', '&', PHP_QUERY_RFC3986);
}
// If no query elements left, unset query.
if (empty($url_parts['query'])) {
unset ($url_parts['query']);
unset($url_parts['query']);
}
// Set path to / if not set.
@@ -162,7 +162,6 @@ class Excerpts
$filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? '');
$media = $this->page->getMedia();
} else {
$grav = Grav::instance();
@@ -185,7 +184,7 @@ class Excerpts
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
/** @var PageInterface $ext_page */
$ext_page = $grav['pages']->dispatch($page_route, true);
$ext_page = $grav['pages']->find($page_route, true);
if ($ext_page) {
$media = $ext_page->getMedia();
} else {
@@ -211,7 +210,6 @@ class Excerpts
$id = $element_excerpt['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);

View File

@@ -25,7 +25,7 @@ class Media extends AbstractMedia
/**
* @param string $path
* @param array $media_order
* @param array|null $media_order
* @param bool $load
*/
public function __construct($path, array $media_order = null, $load = true)
@@ -78,21 +78,21 @@ class Media extends AbstractMedia
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$config = Grav::instance()['config'];
$locator = Grav::instance()['locator'];
$exif_reader = isset(Grav::instance()['exif']) ? Grav::instance()['exif']->getReader() : false;
$media_types = array_keys(Grav::instance()['config']->get('media.types'));
$path = $this->getPath();
// Handle special cases where page doesn't exist in filesystem.
if (!is_dir($this->getPath())) {
if (!$path || !is_dir($path)) {
return;
}
$iterator = new \FilesystemIterator($this->getPath(), \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$iterator = new \FilesystemIterator($path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
$media = [];
/** @var \DirectoryIterator $info */
foreach ($iterator as $path => $info) {
foreach ($iterator as $file => $info) {
// Ignore folders and Markdown files.
if (!$info->isFile() || $info->getExtension() === 'md' || strpos($info->getFilename(), '.') === 0) {
continue;
@@ -106,9 +106,9 @@ class Media extends AbstractMedia
}
if ($type === 'alternative') {
$media["{$basename}.{$ext}"][$type][$extra] = ['file' => $path, 'size' => $info->getSize()];
$media["{$basename}.{$ext}"][$type][$extra] = ['file' => $file, 'size' => $info->getSize()];
} else {
$media["{$basename}.{$ext}"][$type] = ['file' => $path, 'size' => $info->getSize()];
$media["{$basename}.{$ext}"][$type] = ['file' => $file, 'size' => $info->getSize()];
}
}
@@ -118,12 +118,13 @@ class Media extends AbstractMedia
foreach ($types['alternative'] as $ratio => &$alt) {
$alt['file'] = MediumFactory::fromFile($alt['file']);
if (!$alt['file']) {
if (empty($alt['file'])) {
unset($types['alternative'][$ratio]);
} else {
$alt['file']->set('size', $alt['size']);
}
}
unset($alt);
}
$file_path = null;
@@ -141,7 +142,7 @@ class Media extends AbstractMedia
} else {
$medium = MediumFactory::fromFile($types['base']['file']);
$medium && $medium->set('size', $types['base']['size']);
$file_path = $medium->path();
$file_path = $medium ? $medium->path() : null;
}
if (empty($medium)) {
@@ -154,7 +155,6 @@ class Media extends AbstractMedia
if (file_exists($meta_path)) {
$types['meta']['file'] = $meta_path;
} elseif ($file_path && $exif_reader && $medium->get('mime') === 'image/jpeg' && empty($types['meta']) && $config->get('system.media.auto_metadata_exif')) {
$meta = $exif_reader->read($file_path);
if ($meta) {

View File

@@ -39,7 +39,7 @@ class AudioMedium extends Medium
*/
public function controls($display = true)
{
if($display) {
if ($display) {
$this->attributes['controls'] = true;
} else {
unset($this->attributes['controls']);
@@ -88,7 +88,7 @@ class AudioMedium extends Medium
*/
public function muted($status = false)
{
if($status) {
if ($status) {
$this->attributes['muted'] = true;
} else {
unset($this->attributes['muted']);
@@ -105,7 +105,7 @@ class AudioMedium extends Medium
*/
public function loop($status = false)
{
if($status) {
if ($status) {
$this->attributes['loop'] = true;
} else {
unset($this->attributes['loop']);
@@ -122,7 +122,7 @@ class AudioMedium extends Medium
*/
public function autoplay($status = false)
{
if($status) {
if ($status) {
$this->attributes['autoplay'] = true;
} else {
unset($this->attributes['autoplay']);

View File

@@ -17,7 +17,7 @@ class GlobalMedia extends AbstractMedia
/**
* Return media path.
*
* @return null
* @return string|null
*/
public function getPath()
{

View File

@@ -109,8 +109,8 @@ class ImageFile extends Image
* Gets the hash.
* @param string $type
* @param int $quality
* @param [] $extras
* @return null
* @param array $extras
* @return string
*/
public function getHash($type = 'guess', $quality = 80, $extras = [])
{
@@ -141,5 +141,4 @@ class ImageFile extends Image
$this->hash = sha1(serialize($datas));
}
}

View File

@@ -76,7 +76,7 @@ class ImageMedium extends Medium
* Construct.
*
* @param array $items
* @param Blueprint $blueprint
* @param Blueprint|null $blueprint
*/
public function __construct($items = [], Blueprint $blueprint = null)
{
@@ -626,6 +626,7 @@ class ImageMedium extends Medium
* Filter image by using user defined filter parameters.
*
* @param string $filter Filter to be used.
* @return $this
*/
public function filter($filter = 'image.filters.default')
{
@@ -635,6 +636,8 @@ class ImageMedium extends Medium
$method = array_shift($params);
$this->__call($method, $params);
}
return $this;
}
/**
@@ -646,10 +649,8 @@ class ImageMedium extends Medium
{
if ($this->alternatives) {
$max = reset($this->alternatives);
foreach($this->alternatives as $alternative)
{
if($alternative->quality() > $max->quality())
{
foreach ($this->alternatives as $alternative) {
if ($alternative->quality() > $max->quality()) {
$max = $alternative;
}
}
@@ -659,5 +660,4 @@ class ImageMedium extends Medium
return $this;
}
}

View File

@@ -34,10 +34,10 @@ class Link implements RenderableInterface
/**
* Get an element (is array) that can be rendered by the Parsedown engine
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $id
* @param string|null $title
* @param string|null $alt
* @param string|null $class
* @param string|null $id
* @param bool $reset
* @return array
*/

View File

@@ -32,7 +32,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
protected $mode = 'source';
/**
* @var Medium
* @var Medium|null
*/
protected $_thumbnail = null;
@@ -74,7 +74,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
* Construct.
*
* @param array $items
* @param Blueprint $blueprint
* @param Blueprint|null $blueprint
*/
public function __construct($items = [], Blueprint $blueprint = null)
{
@@ -281,7 +281,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
/**
* Get/set querystring for the file's url
*
* @param string $querystring
* @param string|null $querystring
* @param bool $withQuestionmark
* @return string
*/
@@ -345,10 +345,10 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
/**
* Get an element (is array) that can be rendered by the Parsedown engine
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $id
* @param string|null $title
* @param string|null $alt
* @param string|null $class
* @param string|null $id
* @param bool $reset
* @return array
*/
@@ -358,10 +358,11 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
$style = '';
foreach ($this->styleAttributes as $key => $value) {
if (is_numeric($key)) // Special case for inline style attributes, refer to style() method
if (is_numeric($key)) { // Special case for inline style attributes, refer to style() method
$style .= $value;
else
} else {
$style .= $key . ': ' . $value . ';';
}
}
if ($style) {
$attributes['style'] = $style;
@@ -478,7 +479,7 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
*
* @param string $mode
*
* @return $this
* @return $this|null
*/
public function display($mode = 'source')
{
@@ -675,5 +676,4 @@ class Medium extends Data implements RenderableInterface, MediaObjectInterface
return $this->_thumbnail;
}
}

View File

@@ -20,7 +20,7 @@ class MediumFactory
*
* @param string $file
* @param array $params
* @return Medium
* @return Medium|null
*/
public static function fromFile($file, array $params = [])
{
@@ -73,7 +73,7 @@ class MediumFactory
*
* @param FormFlashFile $uploadedFile
* @param array $params
* @return Medium
* @return Medium|null
*/
public static function fromUploadedFile(FormFlashFile $uploadedFile, array $params = [])
{
@@ -82,7 +82,7 @@ class MediumFactory
$ext = $parts['extension'];
$basename = $parts['filename'];
$file = $uploadedFile->getTmpFile();
$path = dirname($file);
$path = $file ? dirname($file) : '';
$config = Grav::instance()['config'];
@@ -104,7 +104,7 @@ class MediumFactory
'basename' => $basename,
'extension' => $ext,
'path' => $path,
'modified' => filemtime($file),
'modified' => $file ? filemtime($file) : 0,
'thumbnails' => []
];
@@ -149,7 +149,7 @@ class MediumFactory
/**
* Create a new ImageMedium by scaling another ImageMedium object.
*
* @param ImageMedium $medium
* @param ImageMedium|Medium $medium
* @param int $from
* @param int $to
* @return Medium|array

View File

@@ -22,10 +22,10 @@ trait ParsedownHtmlTrait
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $id
* @param string|null $title
* @param string|null $alt
* @param string|null $class
* @param string|null $id
* @param bool $reset
* @return string
*/

View File

@@ -14,23 +14,24 @@ interface RenderableInterface
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param string|null $title
* @param string|null $alt
* @param string|null $class
* @param string|null $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 Parsedown Element from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $id
* @param string|null $title
* @param string|null $alt
* @param string|null $class
* @param string|null $id
* @param bool $reset
* @return string
* @return array
*/
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true);
}

View File

@@ -35,11 +35,11 @@ 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 $id
* @param bool $reset
* @param string|null $title
* @param string|null $alt
* @param string|null $class
* @param string|null $id
* @param bool $reset
* @return array
*/
public function parsedownElement($title = null, $alt = null, $class = null, $id = null, $reset = true)
@@ -50,10 +50,10 @@ class ThumbnailImageMedium extends ImageMedium
/**
* Return HTML markup from the medium.
*
* @param string $title
* @param string $alt
* @param string $class
* @param string $id
* @param string|null $title
* @param string|null $alt
* @param string|null $class
* @param string|null $id
* @param bool $reset
* @return string
*/
@@ -119,7 +119,7 @@ class ThumbnailImageMedium extends ImageMedium
* @param string $method
* @param array $arguments
* @param bool $testLinked
* @return Medium
* @return Medium|Link|array
*/
protected function bubble($method, array $arguments = [], $testLinked = true)
{

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