Compare commits

...

585 Commits

Author SHA1 Message Date
Andy Miller
3408db0c9b typo in changelog date 2020-02-03 10:14:53 -07:00
Andy Miller
1bbf8dffeb prepare for rc.4 release 2020-02-03 10:10:26 -07:00
Andy Miller
887b34dd31 Use toolbox ~1.5 2020-02-03 10:09:22 -07:00
Andy Miller
0f5166d690 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2020-02-03 10:06:02 -07:00
Andy Miller
e38c5cac4a Merge tag '1.6.20' into develop
Release v1.6.20
2020-02-03 10:04:38 -07:00
Matias Griese
e5fe28b720 Added support for legacy access settings from admin 2020-02-03 13:20:33 +02:00
Matias Griese
defc70e656 Fixed some admin related ACL issues 2020-02-03 12:53:41 +02:00
Matias Griese
d589dbcbea Fix phpstan issues 2020-02-03 11:19:22 +02:00
Matias Griese
3ed8620d7a Remove publish from CRUD for now 2020-01-31 22:03:05 +02:00
Matias Griese
bc5501eecb Moved PluginsLoadedEvent into Plugins::init() call for better backwards compatibility 2020-01-31 12:23:27 +02:00
Matias Griese
2ae6bac390 Fixed loading group permissions if saved in CRUD mode 2020-01-31 11:55:18 +02:00
Matias Griese
118621cb8b Finish root page blueprints 2020-01-31 00:10:22 +02:00
Matias Griese
3118ed5f56 Added support for security@: or: [admin.super, admin.pages] in blueprints (nested AND/OR mode support) 2020-01-31 00:09:58 +02:00
Matias Griese
ac17fc8efd Minor code cleanup 2020-01-30 23:32:17 +02:00
Matias Griese
85c1ec67c2 Fixed filtering ignored (eg. security@: admin.super) fields and Flex Objects losing data on save 2020-01-30 23:31:32 +02:00
Matias Griese
959a2ec379 Clockwork fixes (beta) 2020-01-30 19:23:40 +02:00
Matias Griese
1372c9b1cc Renamed flex types for simplicity 2020-01-30 11:11:55 +02:00
Matias Griese
67dd5f256d Fixed unit test for parsedown link & 2020-01-30 09:15:26 +02:00
Matias Griese
2c3ced3fba Merge branch 'develop' of github.com:getgrav/grav into 1.7 2020-01-30 09:11:53 +02:00
Matias Griese
9123cb7796 Flex Pages: Allow only users with admin.configuration.pages to access page permissions 2020-01-29 22:39:53 +02:00
Matias Griese
df09c01a25 Minor improvement on please install flex admin pages 2020-01-29 20:28:37 +02:00
Matias Griese
b97301d82d Merge branch 'develop' of github.com:getgrav/grav into 1.7 2020-01-29 19:35:06 +02:00
Matias Griese
85de4ed0e3 Fixed regression (odd use case) 2020-01-29 15:08:58 +02:00
Matias Griese
80125ce298 Fixed system.translations: false breaking Inflector methods 2020-01-29 14:58:50 +02:00
Matias Griese
27542d4fa4 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	composer.json
	composer.lock
2020-01-29 13:24:04 +02:00
Matias Griese
471fb4da6f Changelog update 2020-01-29 11:50:45 +02:00
Matias Griese
a2a6888982 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	system/src/Grav/Common/Data/Data.php
2020-01-29 11:49:57 +02:00
Matias Griese
bdd30238bf Fixed fatal error with non-integer page param value, Grav 1.7 [#2803] 2020-01-29 11:35:19 +02:00
Matias Griese
528ce7131e Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	system/src/Grav/Common/Page/Page.php
2020-01-29 11:34:13 +02:00
Matias Griese
b10725cebc Fixed misleading 'Show sensitive data' configuration option wording 2020-01-29 11:18:14 +02:00
Djamil Legato
719cc5466a Removed validation for permissions groups 2020-01-28 17:10:55 -08:00
Djamil Legato
e373cf18e0 Fixed groups field 2020-01-28 15:17:40 -08:00
Matias Griese
7ef5fa5630 Validation: added new filter option for array keys 2020-01-28 22:23:49 +02:00
Matias Griese
d139b0388a Fixed bad result on testing isPage() when using Flex Pages 2020-01-28 20:57:54 +02:00
Matias Griese
014ab5d7ee Fixed PageStorage with multiple page locations 2020-01-28 14:49:34 +02:00
Matias Griese
64f3949967 Fixed Route instance in multi-site setups 2020-01-28 14:49:02 +02:00
Matias Griese
7913edd34b Improve Acl\Actions 2020-01-28 12:34:10 +02:00
Djamil Legato
2136dc34fe fixed new permissions field title/desc 2020-01-27 13:05:36 -08:00
Matias Griese
c559b42151 User/group authorize is session based 2020-01-27 19:59:54 +02:00
Matias Griese
a33d974c74 Changelog update 2020-01-27 15:44:05 +02:00
Matias Griese
ede7af6b9d Flex: Control authorization from the directory 2020-01-27 15:35:07 +02:00
Matias Griese
5cd4bf5c98 Flex: Fixed event firing too early 2020-01-27 14:24:48 +02:00
Matias Griese
045fae9b6f Flex: Blueprint updates 2020-01-27 14:03:56 +02:00
Matias Griese
bbd46644e0 Added flatten_array filter to form field validation 2020-01-24 22:10:48 +02:00
Matias Griese
3e67c0a878 Fixed Cannot use a scalar value as an array error in Utils::arrayUnflattenDotNotation(), ignore nested structure instead 2020-01-24 22:08:53 +02:00
Matias Griese
924f01158d Composer update 2020-01-23 22:29:14 +02:00
Djamil Legato
8d5dd60fb4 Added multi data_type for acl_picker (previously page_access). added dummy group2 for development 2020-01-22 14:26:39 -08:00
Djamil Legato
958ea586ec Removed old permissions reference 2020-01-22 12:46:30 -08:00
Matias Griese
5f6cc58186 Merge remote-tracking branch 'origin/1.7' into 1.7 2020-01-22 10:26:44 +02:00
Matias Griese
426f59e41a Added support for Flex Directory specific configuration 2020-01-22 10:26:34 +02:00
Matias Griese
fff9fa0ca5 Added Blueprint::getDefaultValue() method 2020-01-22 10:13:40 +02:00
Andy Miller
57aced69cf vendor updates 2020-01-21 17:30:34 -07:00
Matias Griese
afac6baa11 Twig url() takes now third parameter (true) to return URL on non-existing file instead of returning false 2020-01-21 22:35:02 +02:00
Matias Griese
b4630aeb38 Fixed Blueprint::filter() returning null instead of array if there is nothing to return 2020-01-21 21:38:36 +02:00
Matias Griese
cac02663e6 Fixed twig url() failing if stream has extra slash in it (e.g. user:///data) 2020-01-21 21:33:32 +02:00
Matias Griese
e59f1e5a82 Improve FlexFormFlash 2020-01-21 11:24:42 +02:00
Matias Griese
90d077d7c2 Start using $grav['flex'] 2020-01-21 10:09:48 +02:00
Matias Griese
a77d6bcfa0 Event improvements 2020-01-21 10:08:02 +02:00
Matias Griese
bee5abfbf0 Added $grav['flex'] to access all registered Flex Directories 2020-01-21 10:06:58 +02:00
Matias Griese
664447a67b Added Flex cache configuration options 2020-01-21 10:02:29 +02:00
Vladimir Tikunov
b850443090 Updated the parameter type in getCleanedHTML (#2793)
Co-authored-by: Andy Miller <1084697+rhukster@users.noreply.github.com>
2020-01-17 12:11:39 -07:00
Matias Griese
98d0c760a9 Flex types configure: use default values from the plugin config 2020-01-17 15:26:18 +02:00
Matias Griese
76bb7fe4af Added support for loading flex type configuration 2020-01-17 15:07:57 +02:00
Matias Griese
26584b9909 Update Flex type blueprints 2020-01-17 00:50:05 +02:00
Matias Griese
6a9724dd3e Fixed Data::filter() removing empty fields (such as empty list) by default 2020-01-16 20:56:13 +02:00
Matias Griese
c7a41ddfda Use spaceship operator in user sort functions 2020-01-16 19:22:24 +02:00
Matias Griese
2df05dd16d Set permissions for all flex types 2020-01-16 15:18:42 +02:00
Matias Griese
50c2c55554 Added Plugin::__debugInfo() and Plugins::__debugInfo() 2020-01-16 14:51:55 +02:00
Matias Griese
d72b99c5b2 Added PluginsLoadedEvent which triggers after plugins have been loaded but not yet initialized 2020-01-16 14:48:16 +02:00
Matias Griese
5adadfdb40 Added SessionStartEvent which triggers when session is started 2020-01-16 14:44:52 +02:00
Matias Griese
7574195ca2 Added support for custom permissions in configuration 2020-01-15 20:04:08 +02:00
Matias Griese
5b5ef98495 Implemented Permissions::hasAction() method 2020-01-15 16:10:36 +02:00
Matias Griese
226e5350b7 Improve handling user account type changes 2020-01-15 14:30:44 +02:00
Matias Griese
b9b83c3b16 Rename Flex Accounts to Flex User Accounts 2020-01-15 14:04:45 +02:00
Matias Griese
971fb2b19a Grav 1.7: Require Flex-Objects Plugin to edit Flex Accounts 2020-01-15 13:07:26 +02:00
Matias Griese
378b60783c Grav 1.7: Fixed Flex Pages unserialize issues if Flex-Objects Plugin has not been installed 2020-01-15 11:55:40 +02:00
Matias Griese
d901558481 Flex Pages: Added methods to track acl inheritance 2020-01-14 20:58:40 +02:00
Matias Griese
1515ee9193 Access: Added inheritance 2020-01-14 20:57:25 +02:00
Matias Griese
9c34471800 Composer update 2020-01-14 18:49:44 +02:00
Matias Griese
6f16f6f134 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2020-01-14 17:54:46 +02:00
Matias Griese
33790dbb33 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	system/src/Grav/Common/Data/BlueprintSchema.php
	system/src/Grav/Common/Data/Validation.php
	system/src/Grav/Framework/Session/Session.php
2020-01-14 15:11:50 +02:00
Matias Griese
927e99fcb3 Minor flex blueprint update 2020-01-14 11:35:35 +02:00
Djamil Legato
ef6ec100f2 Added custom field page_access for new access selector 2020-01-10 15:17:36 -08:00
Matias Griese
386a39e274 Fixed Permissions::getAccess() when there's no ACL to be passed 2020-01-10 20:53:38 +02:00
Matias Griese
38d3b5cf43 Added support for more advanced ACL 2020-01-10 20:25:27 +02:00
Matias Griese
5718299210 Added $grav->dispatchEvent() method for PSR-14 events 2020-01-10 20:23:39 +02:00
Matias Griese
2e64d560b1 Fixed twig {{ false|string }} to return '0' instead of '' 2020-01-10 13:18:14 +02:00
Djamil Legato
82270e0c13 Initial custom field based off of permissions field 2020-01-09 17:04:28 -08:00
Matias Griese
ca22b56148 Rework security tab for pages 2020-01-08 20:25:57 +02:00
Matias Griese
eca3896bfc Improve array of bools filtering 2020-01-08 16:03:26 +02:00
Matias Griese
1c2c38545a Minor improvement against latest admin (user/group permissions) 2020-01-08 15:43:36 +02:00
Matias Griese
464e1fc6c7 Added flex page permissions to the blueprints (for admin) 2020-01-08 15:37:30 +02:00
Matias Griese
02b8499b0c Blueprint validation: Added validate: value_type: bool|int|float|string|trim to array to filter all the values inside the array 2020-01-08 15:34:20 +02:00
Matias Griese
b5caa41386 Fixed ignore_empty: true not removing empty values in blueprint filtering 2020-01-08 12:33:05 +02:00
Matias Griese
a8ff0e3892 Fixed new Flex Users being stored with wrong filename, login issues [#2785] 2020-01-08 10:07:20 +02:00
Andy Miller
ebab884441 updated changelog 2020-01-07 12:05:43 -07:00
Andy Miller
2c95992eb1 Updated vendor 2020-01-06 17:10:16 -07:00
Matias Griese
1666774a1e Composer update 2020-01-06 17:20:04 +02:00
Matias Griese
3cc0dc08db Fixed PHP 7.4 compatibility issue with Stream 2020-01-06 15:05:48 +02:00
Matias Griese
f9bcf48700 Fixed blueprint loading issues [#2782] 2020-01-06 13:43:06 +02:00
Andy Miller
8a618fee64 prepare for rc.3 release 2020-01-02 16:54:53 -07:00
Andy Miller
a6f7637134 Updated copyright dates to 2020 2020-01-02 16:54:14 -07:00
Andy Miller
f04ef0b359 updated lock file with latest 2020-01-02 16:51:02 -07:00
Matias Griese
608457bd01 Fixed Flex object issues in Windows [#2773] 2020-01-02 15:29:17 +02:00
Matias Griese
508cf1ffdb Added option to ignore parent page ACL 2020-01-02 15:01:54 +02:00
Matias Griese
6f38933e81 Flex Pages: Added support for raw root editing 2019-12-20 20:59:47 +02:00
Matias Griese
8ff2ca4c5a Never translate root page 2019-12-20 20:18:48 +02:00
Matias Griese
6a0d5c69ab Display permissions tab only in Flex Pages 2019-12-20 15:43:49 +02:00
Matias Griese
2747877195 Added permissions tab to pages admin 2019-12-20 15:38:05 +02:00
Matias Griese
584b33d41a Added support for creating / updating root page 2019-12-20 14:56:45 +02:00
Matias Griese
f4330ff77d Use Flex Root page in frontend as well 2019-12-20 12:24:28 +02:00
Matias Griese
60c43184cb Added root page support for Flex Pages 2019-12-20 11:34:35 +02:00
Matias Griese
2fe2eb9f21 Composer update 2019-12-20 09:59:43 +02:00
Matias Griese
04ee52d1ad Added option to ignore parent page ACL 2019-12-19 14:19:06 +02:00
Matias Griese
99e047171f Fixed parent authorization with guest user 2019-12-19 13:51:10 +02:00
Matias Griese
ddc8668837 Fixed authorization with guest user 2019-12-19 13:48:34 +02:00
Matias Griese
e568b992a9 Fixed parent authorization for page author, update location of page settings 2019-12-19 13:15:58 +02:00
Matias Griese
23221a8f8f Added support against authorizing parent pages 2019-12-19 12:31:21 +02:00
Matias Griese
4eed10cd4b Move common logic from PageAuthorsTrait to Access 2019-12-19 12:15:33 +02:00
Matias Griese
da02c0992a Added method to get calculated value from Access class 2019-12-18 20:57:56 +02:00
Matias Griese
2437112f3c Fixed checking ACL for another user in a Flex Object or Directory, added support getting Flex page permissions 2019-12-18 18:23:50 +02:00
Matias Griese
276b4bb0ab Twig filter |yaml_serialize: added support for array-like objects 2019-12-18 18:11:51 +02:00
Matias Griese
acf271344d Twig filter |yaml_serialize: added support for JsonSerializable objects 2019-12-18 12:38:11 +02:00
Andy Miller
767ce29e39 minor update 2019-12-11 21:15:32 -07:00
Matias Griese
daad16ef1b Composer update 2019-12-10 19:15:54 +02:00
Matias Griese
1718135614 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	CHANGELOG.md
2019-12-10 19:09:02 +02:00
Matias Griese
3f9ed2f344 Flex User: do not use deprecated method 2019-12-10 12:46:46 +02:00
Andy Miller
5640baa175 Added a Remote Remote\Package::getChangelog() method 2019-12-09 10:59:35 -07:00
Matias Griese
f102a8cb3d Merge remote-tracking branch 'origin/1.7' into 1.7 2019-12-09 12:53:27 +02:00
Matias Griese
a65c468e81 Minor update on Twig tags 2019-12-09 12:53:17 +02:00
Andy Miller
4cc3f44caa updated vendor libs 2019-12-08 10:40:14 -07:00
Matias Griese
8afe004a7f Improve plugin testing 2019-12-06 18:49:10 +02:00
Matias Griese
185e9b5272 Fixed error on page initialization [#2753] 2019-12-06 18:48:55 +02:00
Andy Miller
78b76cf023 prepare for rc.2 release 2019-12-04 16:48:07 -07:00
Andy Miller
3606d3d0bd prepare for rc.2 release 2019-12-04 16:33:21 -07:00
Andy Miller
3be95e752f Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
#	system/src/Grav/Common/Page/Pages.php
2019-12-04 16:32:45 -07:00
Matias Griese
71bdb5e25e Frontend optimizations for Flex Pages 2019-12-04 21:03:58 +02:00
Matias Griese
b58f36b5af Fixed caching bug in Flex (regression) 2019-12-04 15:08:43 +02:00
Matias Griese
dc20f4f32a Optimize FlexDirectory::loadIndex() calls 2019-12-04 14:43:06 +02:00
Matias Griese
c765fff4b3 Minor phpstan update 2019-12-04 12:45:34 +02:00
Matias Griese
032f5654a4 Fixed Package::jsonSerialize() to return array 2019-12-04 11:16:10 +02:00
Matias Griese
0e01e52cac Some minor phpstan fixes 2019-12-04 11:15:09 +02:00
Matias Griese
37b2c97dc1 Grav Pages: Fixed search 2019-12-04 11:06:08 +02:00
Matias Griese
c61b07febb Flex pages: Fixed empty folder filter 2019-12-04 10:30:27 +02:00
Matias Griese
620fd7ed88 Added missing docblocks, fixed some typehints 2019-12-04 10:25:24 +02:00
Matias Griese
b314ea13aa Merge branch 'develop' of github.com:getgrav/grav into 1.7 2019-12-04 09:55:35 +02:00
Matias Griese
5ba7b4154c Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	CHANGELOG.md
	system/src/Grav/Common/Page/Pages.php
2019-12-03 20:21:22 +02:00
Matias Griese
8be5a2182b Fixed fatal error when calling {{ grav.undefined }} 2019-12-03 11:58:09 +02:00
Matias Griese
ee30694cf1 Flex Pages: Fixed filters being marked as hit when no filtering is applied 2019-12-03 11:41:56 +02:00
Matias Griese
37a9a30187 Minor rework to fix phpstan level 7 issue in MediaTrait::getMedia() 2019-12-03 11:00:06 +02:00
Matias Griese
de34910a6d Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	system/defines.php
	system/src/Grav/Framework/File/Formatter/CsvFormatter.php
2019-12-02 20:14:28 +02:00
Matias Griese
175cebc4a1 Fixed phpstan level 7 issue in PageLegacyTrait 2019-12-02 20:05:50 +02:00
Matias Griese
20aba0fd7c Fixed bad str_replace('...', null) parameter in CSVFormatter 2019-12-02 20:05:07 +02:00
Matias Griese
efc2da90d3 Composer update 2019-12-02 19:43:39 +02:00
Matias Griese
12ad800d1f Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	composer.lock
	system/src/Grav/Common/Page/Pages.php
2019-12-02 19:38:26 +02:00
Matias Griese
3e87b21a8e Flex page filtering improvements 2019-11-28 12:21:02 +02:00
Matias Griese
21309f1451 Flex pages: more logic for filtering counts 2019-11-27 21:59:57 +02:00
Matias Griese
225644f82b Making Djamil happy 2019-11-27 21:45:59 +02:00
Matias Griese
285fffc7df Flex Page admin: added selected if filters match 2019-11-27 21:42:43 +02:00
Matias Griese
7c760387f3 Grav Pages: Optimize filters 2019-11-27 13:49:57 +02:00
Matias Griese
94ff668044 Grav Pages: Admin filtering fixes 2019-11-27 13:31:37 +02:00
Matias Griese
270b1191f5 Improve FlexAuthorizeTrait, improve docblocks for Flex 2019-11-22 13:24:40 +02:00
Matias Griese
6208c516c2 Fixed logged in user being able to delete his own account from admin account manager 2019-11-22 12:04:27 +02:00
Matias Griese
b695c9591e Changed FlexAuthorizeInterface::isAuthorized() to return null having the same meaning as false if access is denied because of no matching rule 2019-11-22 12:04:04 +02:00
Matias Griese
911da0f34f Composer update 2019-11-22 11:56:59 +02:00
Matias Griese
14c2e257a0 Reverted $object->getStorageKey() interface as it was not a good idea, added getMasterKey() for pages 2019-11-21 22:35:56 +02:00
Matias Griese
52ec745ed0 Upgrade doc, upper case titles 2019-11-21 15:15:08 +02:00
Matias Griese
8f42dc3c33 Updated Grav 1.7 upgrade docs 2019-11-21 14:10:47 +02:00
Matias Griese
45530114a7 Added upgrade docs 2019-11-21 12:48:24 +02:00
Matias Griese
5b8baed0bc Optimized Flex Pages collection filtering 2019-11-21 12:48:02 +02:00
Matias Griese
936e95a146 Updated Symfony Components to 4.4 2019-11-21 10:24:12 +02:00
Matias Griese
61a3f3f7c1 Rename modular to module in flex pages 2019-11-20 10:21:37 +02:00
Matias Griese
b3545ccb8b Translations: rename MODULAR to MODULE everywhere 2019-11-20 09:52:50 +02:00
Matias Griese
d217f1e4fb Assets regression 2019-11-19 16:35:01 +02:00
Andy Miller
bb373061ae Merge branch '1.7' of github.com:getgrav/grav into 1.7 2019-11-18 13:48:07 -07:00
Andy Miller
c65cb83348 Output the current username that Scheduler is using if crontab not setup 2019-11-18 13:48:03 -07:00
Matias Griese
f56a5f0ad6 Added page specific permissions for Flex Pages 2019-11-18 13:24:58 +02:00
Matias Griese
9044721c89 Minor bug fixes 2019-11-18 12:05:31 +02:00
Matias Griese
ce5729ba53 Some phpstan fixes 2019-11-18 11:21:25 +02:00
Matias Griese
fc70a50fd6 Fixed regression: Installer->setZip(null) 2019-11-18 10:57:17 +02:00
Matias Griese
a8d61cb5de Add some better type checks 2019-11-18 10:56:32 +02:00
Matias Griese
d8af66cfd7 Remove some unused variables 2019-11-18 10:55:40 +02:00
Matias Griese
1aa69e61ac Add a lot of missing docblocks (variables) 2019-11-18 10:53:21 +02:00
Matias Griese
be3ecf1377 DEPRECATED $page->modular() in favor of $page->isModule() for better readability 2019-11-15 21:15:38 +02:00
Matias Griese
571674a4f5 Fixed phpstan issues in all code up to level 3 2019-11-15 14:03:52 +02:00
Matias Griese
a06520337f Fully comply phpstan level2! 2019-11-15 13:21:20 +02:00
Matias Griese
bf2b7dcf7b Rename Flex Object classes (changed my mind) 2019-11-15 13:05:51 +02:00
Matias Griese
86faceb94f Deprecated Grav\Common\User\Group in favor of $grav['user_groups'], which contains Flex UserGroup collection 2019-11-15 12:27:55 +02:00
Matias Griese
963e365da4 Imroved UserIndex:find() to only search user if query isn't empty 2019-11-15 12:08:30 +02:00
Matias Griese
4332ac35db Moved all Flex classes under Grav\Common\Flex 2019-11-15 11:57:22 +02:00
Matias Griese
7e5d58f94b Minor bug fixes 2019-11-15 09:04:44 +02:00
Matias Griese
6b3b6106bf Code quality improvements 2019-11-15 09:03:47 +02:00
Matias Griese
f0c12e6246 Fixed a few type hints 2019-11-15 09:00:48 +02:00
Matias Griese
741934b1da Flex User/Group: Cache authorize() calls 2019-11-14 21:57:06 +02:00
Matias Griese
35e7bfe506 Multiple small bug fixes / code quality improvements to Flex Pages 2019-11-14 15:13:52 +02:00
Matias Griese
dcc2ddfdb8 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	CHANGELOG.md
2019-11-14 12:22:50 +02:00
Matias Griese
9cb6ddf742 Minor bug fix to Access class where there are no ACL rules 2019-11-13 21:38:08 +02:00
Matias Griese
035a84b55d Fixed couple of minor flex page bugs 2019-11-13 16:20:24 +02:00
Matias Griese
d892343e57 Improve MediumFactory::fromUploadedFile() method 2019-11-13 15:51:49 +02:00
Matias Griese
aaa3976e5a Minor bug fixes/improvements on $user->authorize() 2019-11-13 15:17:37 +02:00
Matias Griese
0d4afd7a37 Code style fixes 2019-11-13 15:10:07 +02:00
Matias Griese
a3a8976fdf Changed UserInterface::authorize() to return null having the same meaning as false if access is denied because of no matching rule 2019-11-13 11:40:36 +02:00
Matias Griese
4edb1ca61b Improved twig authorize() function to work better with nested rule parameters 2019-11-13 11:36:10 +02:00
Matias Griese
d6374f259a Improved user and group ACL to support deny permissions (Flex Users only) 2019-11-13 11:35:41 +02:00
Matias Griese
7b4f40d3b1 Updated blueprint for Flex Pages 2019-11-13 11:22:01 +02:00
Matias Griese
9c4eb549b7 Added missing system.include_default_lang_file_extension configuration option 2019-11-13 11:17:14 +02:00
Matias Griese
c380fb638b Added Utils::isAssoc() and Utils::isNegative() helper methods 2019-11-13 11:16:25 +02:00
Matias Griese
908e993727 Flex Pages: Implemented recursive filterBy() 2019-11-13 11:14:29 +02:00
Matias Griese
4128864e39 Composer update 2019-11-13 09:08:57 +02:00
Matias Griese
304f800018 Improved twig |array filter to work with iterators and objects with toArray() method 2019-11-12 12:41:45 +02:00
Matias Griese
77887d83e9 Regression: Fixed Grav update bug [#2722] 2019-11-08 12:56:03 +02:00
Matias Griese
3514ff64fe Update installer versions 2019-11-08 10:32:34 +02:00
Matias Griese
b56ede81f0 Flex Pages: Implemented recursive filterBy() 2019-11-07 13:14:51 +02:00
Matias Griese
427c39e88e Changelog update (version) 2019-11-07 09:37:40 +02:00
Andy Miller
e450923409 Ability to force-run a Scheduled job #2720 2019-11-06 21:38:08 -07:00
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
040c34d693 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2019-08-16 09:53:44 -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
452 changed files with 29539 additions and 6378 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,3 +1,256 @@
# v1.7.0-rc.4
## 02/03/2020
1. [](#new)
* _POTENTIAL BREAKING CHANGE:_ Upgraded Parsedown to 1.7 for Parsedown-Extra 0.8. Plugins that extend Parsedown may need a fix to render as HTML
* Added `$grav['flex']` to access all registered Flex Directories
* Added `$grav->dispatchEvent()` method for PSR-14 events
* Added `FlexRegisterEvent` which triggers when `$grav['flex']` is being accessed the first time
* Added Flex cache configuration options
* Added `PluginsLoadedEvent` which triggers after plugins have been loaded but not yet initialized
* Added `SessionStartEvent` which triggers when session is started
* Added `PermissionsRegisterEvent` which triggers when `$grav['permissions']` is being accessed the first time
* Added support for Flex Directory specific configuration
* Added support for more advanced ACL
* Added `flatten_array` filter to form field validation
* Added support for `security@: or: [admin.super, admin.pages]` in blueprints (nested AND/OR mode support)
1. [](#improved)
* Blueprint validation: Added `validate: value_type: bool|int|float|string|trim` to `array` to filter all the values inside the array
* Twig `url()` takes now third parameter (`true`) to return URL on non-existing file instead of returning false
1. [](#bugfix)
* Grav 1.7: Fixed blueprint loading issues [#2782](https://github.com/getgrav/grav/issues/2782)
* Fixed PHP 7.4 compatibility issue with `Stream`
* Fixed new `Flex Users` being stored with wrong filename, login issues [#2785](https://github.com/getgrav/grav/issues/2785)
* Fixed `ignore_empty: true` not removing empty values in blueprint filtering
* Fixed `{{ false|string }}` twig to return '0' instead of ''
* Fixed twig `url()` failing if stream has extra slash in it (e.g. `user:///data`)
* Fixed `Blueprint::filter()` returning null instead of array if there is nothing to return
* Fixed `Cannot use a scalar value as an array` error in `Utils::arrayUnflattenDotNotation()`, ignore nested structure instead
* Fixed `Route` instance in multi-site setups
* Fixed `system.translations: false` breaking `Inflector` methods
* Fixed filtering ignored (eg. `security@: admin.super`) fields causing `Flex Objects` to lose data on save
* Grav 1.7: Fixed `Flex Pages` unserialize issues if Flex-Objects Plugin has not been installed
* Grav 1.7: Require Flex-Objects Plugin to edit Flex Accounts
* Grav 1.7: Fixed bad result on testing `isPage()` when using Flex Pages
# v1.7.0-rc.3
## 01/02/2020
1. [](#new)
* Added root page support for `Flex Pages`
1. [](#improved)
* Twig filter `|yaml_serialize`: added support for `JsonSerializable` objects and other array-like objects
* Added support for returning Flex Page specific permissions for admin and testing
* Updated copyright dates to `2020`
* Various vendor updates
1. [](#bugfix)
* Grav 1.7: Fixed error on page initialization [#2753](https://github.com/getgrav/grav/issues/2753)
* Fixed checking ACL for another user (who is not currently logged in) in a Flex Object or Directory
* Fixed bug in Windows where `Filesystem::dirname()` returns backslashes
* Fixed Flex object issues in Windows [#2773](https://github.com/getgrav/grav/issues/2773)
# v1.7.0-rc.2
## 12/04/2019
1. [](#new)
* Updated Symfony Components to 4.4
* Added support for page specific CRUD permissions (`Flex Pages` only)
* Added new `-r <job-id>` option for Scheduler CLI command to force-run a job [#2720](https://github.com/getgrav/grav/issues/2720)
* Added `Utils::isAssoc()` and `Utils::isNegative()` helper methods
* Changed `UserInterface::authorize()` to return `null` having the same meaning as `false` if access is denied because of no matching rule
* Changed `FlexAuthorizeInterface::isAuthorized()` to return `null` having the same meaning as `false` if access is denied because of no matching rule
* Moved all Flex type classes under `Grav\Common\Flex`
* DEPRECATED `Grav\Common\User\Group` in favor of `$grav['user_groups']`, which contains Flex UserGroup collection
* DEPRECATED `$page->modular()` in favor of `$page->isModule()` for better readability
* Fixed phpstan issues in all code up to level 3
1. [](#improved)
* Improved twig `|array` filter to work with iterators and objects with `toArray()` method
* Updated Flex `SimpleStorage` code to feature match the other storages
* Improved user and group ACL to support deny permissions (`Flex Users` only)
* Improved twig `authorize()` function to work better with nested rule parameters
* Output the current username that Scheduler is using if crontab not setup
* Translations: rename MODULAR to MODULE everywhere
* Optimized `Flex Pages` collection filtering
* Frontend optimizations for `Flex Pages`
1. [](#bugfix)
* Regression: Fixed Grav update bug [#2722](https://github.com/getgrav/grav/issues/2722)
* Fixed fatal error when calling `{{ grav.undefined }}`
* Grav 1.7: Reverted `$object->getStorageKey()` interface as it was not a good idea, added `getMasterKey()` for pages
* Grav 1.7: Fixed logged in user being able to delete his own account from admin account manager
# 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.20
## 03/02/2020

125
UPGRADE-1.7.md Normal file
View File

@@ -0,0 +1,125 @@
# UPGRADE FROM 1.6 TO 1.7
## ADMINISTRATORS
### YAML files
* Please run `bin/grav yamllinter` to find any YAML parsing errors in your site. You should run this command before and after upgrade. Grav falls back to older YAML parser if it detects an error, but it will slow down your site.
### Pages
* **BC BREAK** Fixed 404 error page when you go to non-routable page with routable child pages under it. Now you get redirected to the first routable child page instead. This is probably what you wanted in the first place. If you do not want this new behavior, you need to **TODO**
### Multi-language
* Improved language support
* **BC BREAK** Please check that your fallback languages are correct. Old implementation had a fallback to any other language, now only default language is being used unless you use `system.languages.content_fallback` configuration option to override the default behavior.
### CLI
* Added new `bin/grav server` CLI command to easily run Symfony or PHP built-in web servers
* Added new `bin/grav page-system-validator [-r|--record] [-c|--check]` CLI command to test Flex Pages
* Improved `Scheduler` cron command check and more useful CLI information
* Added new `-r <job-id>` option for Scheduler CLI command to force-run a job
* Improved `bin/grav yamllinter` CLI command by adding an option to find YAML Linting issues from the whole site or custom folder
### Configuration
* Added new configuration option `system.debugger.provider` to choose between debugbar and clockwork
* Added new configuration option `system.debugger.censored` to hide potentially sensitive information
* Added new configuration option `system.pages.type` to enable Flex Pages
* Added new configuration option `system.languages.include_default_lang_file_extension` to keep default language in `.md` files if set to `false`
* Added new configuration option `system.languages.content_fallback` to set fallback content languages individually for every language
* Added new configuration option `security.sanitize_svg` to remove potentially dangerous code from SVG files
### Debugging
* 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
## DEVELOPERS
### ACL
* `user.authorize()` now requires user to be authorized (passed 2FA check), unless the rule contains `login` in its name.
* **BC BREAK** `user.authorize()` and Flex `object.isAuthorized()` now have two deny states: `false` and `null`.
Make sure you do not have strict checks against false: `$user->authorize($action) === false` (PHP) or `user.authorize(action) is same as(false)` (Twig).
For the negative checks you should be using `!user->authorize($action)` (PHP) or `not user.authorize(action)` (Twig).
The change has been done to allow strong deny rules by chaining the actions if previous ones do not match: `user.authorize(action1) ?? user.authorize(action2) ?? user.authorize(action3)`.
Note that Twig function `authorize()` will still **keeps** the old behavior!
### Pages
* Added experimental support for `Flex Pages` (**Flex-Objects** plugin required)
* Added page specific permissions support for `Flex Pages`
* Fixed wrong `Pages::dispatch()` calls (with redirect) when we really meant to call `Pages::find()`
* Added `Pages::getCollection()` method
* Moved `collection()` and `evaluate()` logic from `Page` class into `Pages` class
* **DEPRECATED** `$page->modular()` in favor of `$page->isModule()`
* **BC BREAK** Fixed `Page::modular()` and `Page::modularTwig()` returning `null` for folders and other non-initialized pages. Should not affect your code unless you were checking against `false` or `null`.
### Users
* Improved `Flex Users`: obey blueprints and allow Flex to be used in admin only
* Improved `Flex Users`: user and group ACL now supports deny permissions
* Changed `UserInterface::authorize()` to return `null` having the same meaning as `false` if access is denied because of no matching rule
* **DEPRECATED** `Grav\Common\User\Group` in favor of `$grav['user_groups']`, which contains Flex UserGroup collection
### Flex
* 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
* Added `FlexStorage::getMetaData()` to get updated object meta information for listed keys
* `FlexDirectory::getObject()` can now be called without any parameters to create a new object
* **DEPRECATED** `FlexDirectory::update()` and `FlexDirectory::remove()`
* **BC BREAK** Moved all Flex type classes under `Grav\Common\Flex`
* **BC BREAK** `FlexStorageInterface::getStoragePath()` and `getMediaPath()` can now return null
* **BC BREAK** Flex objects no longer return temporary key if they do not have one; empty key is returned instead
### Templating
* Added support for Twig 2.12 (still using Twig 1.42)
* Added a new `{% cache %}` Twig tag eliminating need for `twigcache` extension.
* Added `array_diff()` twig function
* Added `template_from_string()` twig function
* Improved twig `|array` filter to work with iterators and objects with `toArray()` method
* Improved twig `authorize()` function to work better with nested rule parameters
### Multi-language
* Improved language support for `Route` class
* Translations: rename MODULAR to MODULE everywhere
* Added `Language::getPageExtensions()` to get full list of supported page language extensions
* **BC BREAK** Fixed `Language::getFallbackPageExtensions()` to fall back only to default language instead of going through all languages
### Events
* Use `Symfony EventDispatcher` directly instead of `rockettheme/toolbox` wrapper.
### Misc
* Added `Utils::isAssoc()` and `Utils::isNegative()` helper methods
* Added `Utils::simpleTemplate()` method for very simple variable templating
* Support customizable null character replacement in `CSVFormatter::decode()`
* Added new `Security::sanitizeSVG()` function
* Added `$grav->close()` method to properly terminate the request with a response
* **BC BREAK** Make `Route` objects immutable. This means that you need to do: `{% set route = route.withExtension('.html') %}` (for all `withX` methods) to keep the updated version.
### Composer dependencies
* Updated Symfony Components to 4.4, please update any deprecated features in your code
* **BC BREAK** Please run `bin/grav yamllinter -f user://` to find any YAML parsing errors in your site (including your plugins and themes).
### Admin
* **BC BREAK** Admin will not initialize frontend pages anymore, this has been done to greatly speed up Admin plugin.
Please call `$grav['admin']->enablePages()` or `{% do admin.enablePages() %}` if you need to access frontend pages. This call can be safely made multiple times.
If you're using `Flex Pages`, please use Flex Directory instead, it will make your code so much faster.

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,6 +2,7 @@
<?php
use Grav\Common\Composer;
use Grav\Events\PluginsLoadedEvent;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
@@ -55,7 +56,14 @@ $grav->setup($environment);
$grav['config']->init();
$grav['uri']->init();
$grav['users'];
$grav['plugins']->init();
$plugins = $grav['plugins'];
$plugins->init();
// Plugins Loaded Event
$event = new PluginsLoadedEvent($grav, $plugins);
$grav->dispatchEvent($event);
$grav['themes']->init();
$app = new Application('Grav Plugins Commands', GRAV_VERSION);

View File

@@ -27,14 +27,15 @@
"psr/http-server-middleware": "^1.0",
"kodus/psr7-server": "*",
"nyholm/psr7": "^1.0",
"twig/twig": "~1.40",
"erusev/parsedown": "1.6.4",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~4.2.0",
"symfony/console": "~4.2.0",
"symfony/event-dispatcher": "~4.2.0",
"symfony/var-dumper": "~4.2.0",
"symfony/process": "~4.2.0",
"twig/twig": "~1.0",
"erusev/parsedown": "^1.7",
"erusev/parsedown-extra": "~0.8",
"symfony/contracts": "~1.0",
"symfony/yaml": "~4.4.0",
"symfony/console": "~4.4.0",
"symfony/event-dispatcher": "~4.4.0",
"symfony/var-dumper": "~4.4.0",
"symfony/process": "~4.4.0",
"doctrine/cache": "^1.8",
"doctrine/collections": "^1.5",
"guzzlehttp/psr7": "^1.4",
@@ -44,20 +45,22 @@
"gregwar/image": "2.*",
"donatj/phpuseragentparser": "~0.10",
"pimple/pimple": "~3.2",
"rockettheme/toolbox": "~1.4.0",
"maximebf/debugbar": "~1.15",
"rockettheme/toolbox": "~1.5",
"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": "@beta",
"enshrined/svg-sanitize": "~0.1"
},
"require-dev": {
"codeception/codeception": "^2.4",
"phpstan/phpstan": "^0.11",
"phpstan/phpstan-deprecation-rules": "^0.11.0",
"phpstan/phpstan-deprecation-rules": "^0.11",
"phpunit/php-code-coverage": "~6.0",
"fzaninotto/faker": "^1.8",
"victorjonsson/markdowndocs": "dev-master"
@@ -75,10 +78,17 @@
"php": "7.1.3"
}
},
"repositories": [
{
"repositories": [{
"type": "vcs",
"url": "https://github.com/trilbymedia/PHP-Markdown-Documentation-Generator"
},
{
"type": "vcs",
"url": "https://github.com/itsgoingd/clockwork"
},
{
"type": "vcs",
"url": "https://github.com/rockettheme/toolbox"
}
],
"autoload": {
@@ -98,9 +108,9 @@
"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",
"phpstan-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=256M",
"phpstan": "vendor/bin/phpstan analyse -l 3 -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",
"phpstan-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"
},

278
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6fa1c3a012660bd9c614bcce56af871f",
"content-hash": "c8b230b285ee0b8b86c56f5eac7b6214",
"packages": [
{
"name": "antoligy/dom-string-iterators",
@@ -354,20 +354,62 @@
"time": "2017-01-23T04:29:33+00:00"
},
{
"name": "erusev/parsedown",
"version": "1.6.4",
"name": "enshrined/svg-sanitize",
"version": "0.13.3",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "fbe3fe878f4fe69048bb8a52783a09802004f548"
"url": "https://github.com/darylldoyle/svg-sanitizer.git",
"reference": "bc66593f255b7d2613d8f22041180036979b6403"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/fbe3fe878f4fe69048bb8a52783a09802004f548",
"reference": "fbe3fe878f4fe69048bb8a52783a09802004f548",
"url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/bc66593f255b7d2613d8f22041180036979b6403",
"reference": "bc66593f255b7d2613d8f22041180036979b6403",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*"
},
"require-dev": {
"codeclimate/php-test-reporter": "^0.1.2",
"phpunit/phpunit": "^6"
},
"type": "library",
"autoload": {
"psr-4": {
"enshrined\\svgSanitize\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0-or-later"
],
"authors": [
{
"name": "Daryll Doyle",
"email": "daryll@enshrined.co.uk"
}
],
"description": "An SVG sanitizer for PHP",
"time": "2020-01-20T01:34:17+00:00"
},
{
"name": "erusev/parsedown",
"version": "1.7.4",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.0"
},
"require-dev": {
@@ -396,24 +438,27 @@
"markdown",
"parser"
],
"time": "2017-11-14T20:44:03+00:00"
"time": "2019-12-30T22:54:17+00:00"
},
{
"name": "erusev/parsedown-extra",
"version": "0.7.1",
"version": "0.8.1",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown-extra.git",
"reference": "0db5cce7354e4b76f155d092ab5eb3981c21258c"
"reference": "91ac3ff98f0cea243bdccc688df43810f044dcef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/0db5cce7354e4b76f155d092ab5eb3981c21258c",
"reference": "0db5cce7354e4b76f155d092ab5eb3981c21258c",
"url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/91ac3ff98f0cea243bdccc688df43810f044dcef",
"reference": "91ac3ff98f0cea243bdccc688df43810f044dcef",
"shasum": ""
},
"require": {
"erusev/parsedown": "~1.4"
"erusev/parsedown": "^1.7.4"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
"type": "library",
"autoload": {
@@ -440,7 +485,7 @@
"parsedown",
"parser"
],
"time": "2015-11-01T10:19:22+00:00"
"time": "2019-12-30T23:20:37+00:00"
},
{
"name": "filp/whoops",
@@ -669,6 +714,67 @@
],
"time": "2019-07-01T23:21:34+00:00"
},
{
"name": "itsgoingd/clockwork",
"version": "v4.1-beta3",
"source": {
"type": "git",
"url": "https://github.com/itsgoingd/clockwork.git",
"reference": "0d56e354c0b957863154ae2c9b0a52c7161c2b52"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/0d56e354c0b957863154ae2c9b0a52c7161c2b52",
"reference": "0d56e354c0b957863154ae2c9b0a52c7161c2b52",
"shasum": ""
},
"require": {
"php": ">=5.5",
"psr/log": "1.*"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Clockwork\\Support\\Laravel\\ClockworkServiceProvider"
],
"aliases": {
"Clockwork": "Clockwork\\Support\\Laravel\\Facade"
}
}
},
"autoload": {
"psr-4": {
"Clockwork\\": "Clockwork/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "itsgoingd",
"email": "itsgoingd@luzer.sk",
"homepage": "https://twitter.com/itsgoingd"
}
],
"description": "php dev tools integrated to your browser",
"homepage": "https://underground.works/clockwork",
"keywords": [
"debugging",
"devtools",
"laravel",
"logging",
"lumen",
"profiling",
"slim"
],
"support": {
"source": "https://github.com/itsgoingd/clockwork/tree/v4.1-beta3",
"issues": "https://github.com/itsgoingd/clockwork/issues"
},
"time": "2020-01-29T20:41:24+00:00"
},
{
"name": "kodus/psr7-server",
"version": "1.0.1",
@@ -1734,27 +1840,34 @@
},
{
"name": "rockettheme/toolbox",
"version": "1.4.6",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/rockettheme/toolbox.git",
"reference": "e7010c2a956aff241dfa100ab8aef5b6cf83892a"
"reference": "f8d877b29856a42c8b21b2e12c8bec0012987314"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rockettheme/toolbox/zipball/e7010c2a956aff241dfa100ab8aef5b6cf83892a",
"reference": "e7010c2a956aff241dfa100ab8aef5b6cf83892a",
"url": "https://api.github.com/repos/rockettheme/toolbox/zipball/f8d877b29856a42c8b21b2e12c8bec0012987314",
"reference": "f8d877b29856a42c8b21b2e12c8bec0012987314",
"shasum": ""
},
"archive": {
"exclude": [
"tests"
]
},
"require": {
"ext-json": "*",
"php": ">=5.4.0",
"php": ">=5.6.0",
"pimple/pimple": "~3.0",
"symfony/event-dispatcher": ">2.5",
"symfony/yaml": ">2.5"
"symfony/event-dispatcher": "^3.4|^4.0",
"symfony/yaml": "^3.4|^4.0"
},
"require-dev": {
"phpunit/phpunit": "~6"
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-deprecation-rules": "^0.12",
"phpunit/phpunit": "~7.0"
},
"type": "library",
"autoload": {
@@ -1770,7 +1883,17 @@
"RocketTheme\\Toolbox\\StreamWrapper\\": "StreamWrapper/src"
}
},
"notification-url": "https://packagist.org/downloads/",
"scripts": {
"test": [
"vendor/bin/phpunit run unit"
],
"test-windows": [
"vendor\\bin\\phpunit run unit"
],
"phpstan": [
"vendor/bin/phpstan analyse -l 8 -c ./tests/phpstan/phpstan.neon . --memory-limit=128M"
]
},
"license": [
"MIT"
],
@@ -1780,7 +1903,11 @@
"php",
"rockettheme"
],
"time": "2019-03-20T19:59:17+00:00"
"support": {
"source": "https://github.com/rockettheme/toolbox/tree/1.5.0",
"issues": "https://github.com/rockettheme/toolbox/issues"
},
"time": "2020-02-03T16:49:48+00:00"
},
{
"name": "seld/cli-prompt",
@@ -1832,25 +1959,28 @@
},
{
"name": "symfony/console",
"version": "v4.2.12",
"version": "v4.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "fc2e274aade6567a750551942094b2145ade9b6c"
"reference": "f512001679f37e6a042b51897ed24a2f05eba656"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/fc2e274aade6567a750551942094b2145ade9b6c",
"reference": "fc2e274aade6567a750551942094b2145ade9b6c",
"url": "https://api.github.com/repos/symfony/console/zipball/f512001679f37e6a042b51897ed24a2f05eba656",
"reference": "f512001679f37e6a042b51897ed24a2f05eba656",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"symfony/contracts": "^1.0",
"symfony/polyfill-mbstring": "~1.0"
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php73": "^1.8",
"symfony/service-contracts": "^1.1|^2"
},
"conflict": {
"symfony/dependency-injection": "<3.4",
"symfony/event-dispatcher": "<4.3|>=5",
"symfony/lock": "<4.4",
"symfony/process": "<3.3"
},
"provide": {
@@ -1858,11 +1988,12 @@
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/event-dispatcher": "~3.4|~4.0",
"symfony/lock": "~3.4|~4.0",
"symfony/process": "~3.4|~4.0"
"symfony/config": "^3.4|^4.0|^5.0",
"symfony/dependency-injection": "^3.4|^4.0|^5.0",
"symfony/event-dispatcher": "^4.3",
"symfony/lock": "^4.4|^5.0",
"symfony/process": "^3.4|^4.0|^5.0",
"symfony/var-dumper": "^4.3|^5.0"
},
"suggest": {
"psr/log": "For using the console logger",
@@ -1873,7 +2004,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.2-dev"
"dev-master": "4.4-dev"
}
},
"autoload": {
@@ -1900,7 +2031,7 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2019-07-24T17:13:20+00:00"
"time": "2020-01-25T12:44:29+00:00"
},
{
"name": "symfony/contracts",
@@ -1981,31 +2112,37 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v4.2.12",
"version": "v4.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "852548c7c704f14d2f6700c8d872a05bd2028732"
"reference": "9e3de195e5bc301704dd6915df55892f6dfc208b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/852548c7c704f14d2f6700c8d872a05bd2028732",
"reference": "852548c7c704f14d2f6700c8d872a05bd2028732",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9e3de195e5bc301704dd6915df55892f6dfc208b",
"reference": "9e3de195e5bc301704dd6915df55892f6dfc208b",
"shasum": ""
},
"require": {
"php": "^7.1.3",
"symfony/contracts": "^1.0"
"symfony/event-dispatcher-contracts": "^1.1"
},
"conflict": {
"symfony/dependency-injection": "<3.4"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "1.1"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/expression-language": "~3.4|~4.0",
"symfony/stopwatch": "~3.4|~4.0"
"symfony/config": "^3.4|^4.0|^5.0",
"symfony/dependency-injection": "^3.4|^4.0|^5.0",
"symfony/expression-language": "^3.4|^4.0|^5.0",
"symfony/http-foundation": "^3.4|^4.0|^5.0",
"symfony/service-contracts": "^1.1|^2",
"symfony/stopwatch": "^3.4|^4.0|^5.0"
},
"suggest": {
"symfony/dependency-injection": "",
@@ -2014,7 +2151,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.2-dev"
"dev-master": "4.4-dev"
}
},
"autoload": {
@@ -2041,7 +2178,7 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2019-06-26T06:46:55+00:00"
"time": "2020-01-10T21:54:01+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -2334,16 +2471,16 @@
},
{
"name": "symfony/process",
"version": "v4.2.12",
"version": "v4.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "808a4be7e0dd7fcb6a2b1ed2ba22dd581402c5e2"
"reference": "f5697ab4cb14a5deed7473819e63141bf5352c36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/808a4be7e0dd7fcb6a2b1ed2ba22dd581402c5e2",
"reference": "808a4be7e0dd7fcb6a2b1ed2ba22dd581402c5e2",
"url": "https://api.github.com/repos/symfony/process/zipball/f5697ab4cb14a5deed7473819e63141bf5352c36",
"reference": "f5697ab4cb14a5deed7473819e63141bf5352c36",
"shasum": ""
},
"require": {
@@ -2352,7 +2489,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.2-dev"
"dev-master": "4.4-dev"
}
},
"autoload": {
@@ -2379,20 +2516,20 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2019-05-30T16:06:08+00:00"
"time": "2020-01-09T09:50:08+00:00"
},
{
"name": "symfony/var-dumper",
"version": "v4.2.12",
"version": "v4.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "4e18e041a477edbb8c54e053f179672f9413816c"
"reference": "46b53fd714568af343953c039ff47b67ce8af8d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/4e18e041a477edbb8c54e053f179672f9413816c",
"reference": "4e18e041a477edbb8c54e053f179672f9413816c",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/46b53fd714568af343953c039ff47b67ce8af8d6",
"reference": "46b53fd714568af343953c039ff47b67ce8af8d6",
"shasum": ""
},
"require": {
@@ -2406,9 +2543,9 @@
},
"require-dev": {
"ext-iconv": "*",
"symfony/console": "~3.4|~4.0",
"symfony/process": "~3.4|~4.0",
"twig/twig": "~1.34|~2.4"
"symfony/console": "^3.4|^4.0|^5.0",
"symfony/process": "^4.4|^5.0",
"twig/twig": "^1.34|^2.4|^3.0"
},
"suggest": {
"ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
@@ -2421,7 +2558,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.2-dev"
"dev-master": "4.4-dev"
}
},
"autoload": {
@@ -2455,20 +2592,20 @@
"debug",
"dump"
],
"time": "2019-07-27T06:42:33+00:00"
"time": "2020-01-25T12:44:29+00:00"
},
{
"name": "symfony/yaml",
"version": "v4.2.12",
"version": "v4.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "9468fef6f1c740b96935e9578560a9e9189ca154"
"reference": "cd014e425b3668220adb865f53bff64b3ad21767"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/9468fef6f1c740b96935e9578560a9e9189ca154",
"reference": "9468fef6f1c740b96935e9578560a9e9189ca154",
"url": "https://api.github.com/repos/symfony/yaml/zipball/cd014e425b3668220adb865f53bff64b3ad21767",
"reference": "cd014e425b3668220adb865f53bff64b3ad21767",
"shasum": ""
},
"require": {
@@ -2479,7 +2616,7 @@
"symfony/console": "<3.4"
},
"require-dev": {
"symfony/console": "~3.4|~4.0"
"symfony/console": "^3.4|^4.0|^5.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
@@ -2487,7 +2624,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.2-dev"
"dev-master": "4.4-dev"
}
},
"autoload": {
@@ -2514,7 +2651,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2019-07-24T14:47:26+00:00"
"time": "2020-01-21T11:12:16+00:00"
},
{
"name": "twig/twig",
@@ -5670,6 +5807,7 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"itsgoingd/clockwork": 10,
"victorjonsson/markdowndocs": 20
},
"prefer-stable": false,

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

@@ -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,8 @@
title: Flex User Accounts
description: Manage your User Accounts in Flex.
type: flex-objects
# Deprecated in Grav 1.7.0-rc.4: file was renamed.
extends@:
type: user-accounts
context: blueprints://flex

View File

@@ -0,0 +1,198 @@
title: Pages
description: Manage your Grav Pages in Flex.
type: flex-objects
# Extends a page (blueprint gets overridden inside the object)
extends@:
type: default
context: blueprints://pages
# Flex configuration
config:
# Administration Configuration (needs Flex Objects plugin)
admin:
# Admin router
router:
path: '/pages'
# Permissions
permissions:
# Primary permissions
admin.pages:
type: crudl
label: Pages
admin.configuration.pages:
type: default
label: Pages Configuration
# Admin menu
menu:
list:
route: '/pages'
title: PLUGIN_ADMIN.PAGES
icon: fa-file-text
authorize: ['admin.pages.list', 'admin.super']
priority: 5
# Admin template type (folder)
template: pages
# Allowed admin actions
actions:
list: true
create: true
read: true
update: true
delete: true
# List view
list:
# Fields shown in the list view
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
# Extra options
options:
# Default number of records for pagination
per_page: 20
# Default ordering
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
# Configure view
configure:
authorize: 'admin.configuration.pages'
# Site Configuration
site:
# Hide from flex types
hidden: true
templates:
collection:
# Lookup for the template layout files for collections of objects
paths:
- 'flex/{TYPE}/collection/{LAYOUT}{EXT}'
object:
# Lookup for the template layout files for objects
paths:
- 'flex/{TYPE}/object/{LAYOUT}{EXT}'
defaults:
# Default template {TYPE}; overridden by filename of this blueprint if template folder exists
type: pages
# Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates)
layout: default
# Default filters for frontend.
filter:
- withPublished
# Data Configuration
data:
object: 'Grav\Common\Flex\Pages\PageObject'
collection: 'Grav\Common\Flex\Pages\PageCollection'
index: 'Grav\Common\Flex\Pages\PageIndex'
storage:
class: 'Grav\Common\Flex\Pages\PageStorage'
options:
formatter:
class: 'Grav\Framework\File\Formatter\MarkdownFormatter'
folder: 'page://'
# Keep index file in filesystem to speed up lookups
indexed: true
# Set default ordering of the pages
ordering:
key: ASC
search:
# Search options
options:
contains: 1
# Fields to be searched
fields:
- key
- menu
- title
- name
# Regular form definition
form:
fields:
lang:
type: hidden
value: ''
tabs:
fields:
security:
type: tab
title: PLUGIN_ADMIN.SECURITY
import@:
type: partials/security
context: blueprints://pages

View File

@@ -0,0 +1,70 @@
form:
validation: loose
fields:
plugin_tabs:
type: tabs
fields:
cache:
type: tab
title: Caching
fields:
object.cache.index.enabled:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_ENABLED
highlight: 1
config-default@: system.flex.cache.index.enabled
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
object.cache.index.lifetime:
type: text
toggleable: true
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_LIFETIME
config-default@: system.flex.cache.index.lifetime
validate:
type: int
object.cache.object.enabled:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_ENABLED
highlight: 1
config-default@: system.flex.cache.object.enabled
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
object.cache.object.lifetime:
type: text
toggleable: true
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_LIFETIME
config-default@: system.flex.cache.object.lifetime
validate:
type: int
object.cache.render.enabled:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_ENABLED
highlight: 1
config-default@: system.flex.cache.render.enabled
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
object.cache.render.lifetime:
type: text
toggleable: true
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_LIFETIME
config-default@: system.flex.cache.render.lifetime
validate:
type: int

View File

@@ -0,0 +1,128 @@
title: User Accounts
description: Manage your User Accounts in Flex.
type: flex-objects
# Extends user account
extends@:
type: account
context: blueprints://user
# Flex configuration
config:
# Administration Configuration (needs Flex Objects plugin)
admin:
# Admin router
router:
path: '/accounts/users'
actions:
configure:
path: '/accounts/configure'
redirects:
'/user': '/accounts/users'
'/accounts': '/accounts/users'
# Permissions
permissions:
# Primary permissions
admin.users:
type: crudl
label: User Accounts
admin.configuration.users:
type: default
label: Accounts Configuration
# Admin menu
menu:
base:
location: '/accounts'
route: '/accounts/users'
index: 0
title: PLUGIN_ADMIN.ACCOUNTS
icon: fa-users
authorize: ['admin.users.list', 'admin.super']
priority: 6
# Admin template type (folder)
template: user-accounts
# List view
list:
# Fields shown in the list view
fields:
username:
link: edit
search: true
email:
search: true
fullname:
search: true
# Extra options
options:
per_page: 20
order:
by: username
dir: asc
# Edit view
edit:
title:
template: '{{ object.fullname ?? object.username }} &lt;{{ object.email }}&gt;'
# Configure view
configure:
hidden: true
authorize: 'admin.configuration.users'
form: 'accounts'
title:
template: "{{ 'PLUGIN_ADMIN.ACCOUNTS'|tu }} {{ 'PLUGIN_ADMIN.CONFIGURATION'|tu }}"
# Site Configuration
site:
# Hide from flex types
hidden: true
templates:
collection:
# Lookup for the template layout files for collections of objects
paths:
- 'flex/{TYPE}/collection/{LAYOUT}{EXT}'
object:
# Lookup for the template layout files for objects
paths:
- 'flex/{TYPE}/object/{LAYOUT}{EXT}'
defaults:
# Default template {TYPE}; overridden by filename of this blueprint if template folder exists
type: user-accounts
# Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates)
layout: default
# Data Configuration
data:
object: 'Grav\Common\Flex\Users\UserObject'
collection: 'Grav\Common\Flex\Users\UserCollection'
index: 'Grav\Common\Flex\Users\UserIndex'
storage:
class: 'Grav\Common\Flex\Users\Storage\UserFileStorage'
options:
formatter:
class: 'Grav\Framework\File\Formatter\YamlFormatter'
folder: 'account://'
pattern: '{FOLDER}/{KEY}{EXT}'
key: username
indexed: true
search:
options:
contains: 1
fields:
- key
- email
# Regular form definition
form:
fields:
username:
flex-disabled@: exists
disabled: false
flex-readonly@: exists
readonly: false
validate:
required: true

View File

@@ -0,0 +1,115 @@
title: User Groups
description: Manage your User Groups in Flex.
type: flex-objects
# Extends user group
extends@:
type: group
context: blueprints://user
# Flex configuration
config:
# Administration Configuration (needs Flex Objects plugin)
admin:
# Admin router
router:
path: '/accounts/groups'
actions:
configure:
path: '/accounts/configure'
redirects:
'/accounts': '/accounts/groups'
# Permissions
permissions:
# Primary permissions
admin.users:
type: crudl
label: User Accounts
admin.configuration.users:
type: default
label: Accounts Configuration
# Admin menu
menu:
base:
location: '/accounts'
route: '/accounts/groups'
index: 1
title: PLUGIN_ADMIN.ACCOUNTS
icon: fa-users
authorize: ['admin.users.list', 'admin.super']
priority: 6
# Admin template type (folder)
template: user-groups
# List view
list:
# Fields shown in the list view
fields:
groupname:
link: edit
search: true
readableName:
search: true
description:
search: true
# Extra options
options:
per_page: 20
order:
by: groupname
dir: asc
# Edit view
edit:
title:
template: '{{ object.readableName ?? object.groupname }}'
# Configure view
configure:
hidden: true
authorize: 'admin.configuration.users'
form: 'accounts'
title:
template: "{{ 'PLUGIN_ADMIN.ACCOUNTS'|tu }} {{ 'PLUGIN_ADMIN.CONFIGURATION'|tu }}"
# Site Configuration
site:
# Hide from flex types
hidden: true
templates:
collection:
# Lookup for the template layout files for collections of objects
paths:
- 'flex/{TYPE}/collection/{LAYOUT}{EXT}'
object:
# Lookup for the template layout files for objects
paths:
- 'flex/{TYPE}/object/{LAYOUT}{EXT}'
defaults:
# Default template {TYPE}; overridden by filename of this blueprint if template folder exists
type: user-groups
# Default template {LAYOUT}; can be overridden in render calls (usually Twig in templates)
layout: default
# Data Configuration
data:
object: 'Grav\Common\Flex\UserGroups\UserGroupObject'
collection: 'Grav\Common\Flex\UserGroups\UserGroupCollection'
index: 'Grav\Common\Flex\UserGroups\UserGroupIndex'
storage:
class: 'Grav\Framework\Flex\Storage\SimpleStorage'
options:
formatter:
class: 'Grav\Framework\File\Formatter\YamlFormatter'
folder: 'user://config/groups.yaml'
key: groupname
search:
options:
contains: 1
fields:
- key
- groupname
- description

View File

@@ -1,4 +1,4 @@
title: PLUGIN_ADMIN.MODULAR
title: PLUGIN_ADMIN.MODULE
extends@: default
form:
@@ -13,7 +13,7 @@ form:
modular_title:
type: spacer
title: PLUGIN_ADMIN.MODULAR_SETUP
title: PLUGIN_ADMIN.MODULE_SETUP
header.content.items:
type: text
@@ -34,5 +34,3 @@ form:
help: '"desc" or "asc" are valid values'
placeholder: desc
size: small

View File

@@ -0,0 +1,71 @@
form:
fields:
_site:
type: section
title: PLUGIN_ADMIN.PAGE_ACCESS
underline: true
fields:
header.visibility_requires_access:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.PAGE_VISIBILITY_REQUIRES_ACCESS
help: PLUGIN_ADMIN.PAGE_VISIBILITY_REQUIRES_ACCESS_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
header.access:
type: acl_picker
label: PLUGIN_ADMIN.PAGE_ACCESS
help: PLUGIN_ADMIN.PAGE_ACCESS_HELP
ignore_empty: true
data_type: access
validate:
type: array
value_type: bool
_admin:
security@: {or: [admin.super, admin.configuration.pages]}
type: section
title: PLUGIN_ADMIN.PAGE PERMISSIONS
underline: true
fields:
header.permissions.inherit:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.PAGE_INHERIT_PERMISSIONS
help: PLUGIN_ADMIN.PAGE_INHERIT_PERMISSIONS_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
header.permissions.authors:
type: list
toggleable: true
label: PLUGIN_ADMIN.PAGE_AUTHORS
help: PLUGIN_ADMIN.PAGE_AUTHORS_HELP
fields:
value:
type: text
placeholder: PLUGIN_ADMIN.USERNAME
style: vertical
header.permissions.groups:
ignore@: true
type: acl_picker
label: PLUGIN_ADMIN.PAGE_GROUPS
help: PLUGIN_ADMIN.PAGE_GROUPS_HELP
ignore_empty: true
data_type: permissions

View File

@@ -0,0 +1,16 @@
title: PLUGIN_ADMIN.ROOT
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
tabs:
type: tabs
active: 1

View File

@@ -121,7 +121,9 @@ form:
access:
security@: admin.super
type: permissions
check_authorize: true
label: PLUGIN_ADMIN.PERMISSIONS
ignore_empty: true
validate:
type: array
value_type: bool

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

@@ -3,21 +3,19 @@ form:
validation: loose
fields:
spacer:
type: spacer
text: '<br>'
groupname:
type: text
size: large
label: PLUGIN_ADMIN.NAME
disabled: true
readonly: true
label: PLUGIN_ADMIN.GROUP_NAME
flex-disabled@: exists
flex-readonly@: exists
validate:
required: true
readableName:
type: text
size: large
label: PLUGIN_ADMIN_PRO.READABLE_NAME
label: PLUGIN_ADMIN.DISPLAY_NAME
description:
type: text
@@ -27,11 +25,24 @@ form:
icon:
type: text
size: small
label: PLUGIN_ADMIN_PRO.ICON
label: PLUGIN_ADMIN.ICON
enabled:
type: toggle
label: PLUGIN_ADMIN.ENABLED
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
access:
type: permissions
check_authorize: false
label: PLUGIN_ADMIN.PERMISSIONS
ignore_empty: true
validate:
type: array
value_type: bool

View File

@@ -0,0 +1,53 @@
actions:
site:
type: access
label: Site
admin:
type: access
label: Admin
admin.pages:
type: access
label: Pages
admin.users:
type: access
label: User Accounts
types:
default:
type: access
crud:
type: compact
letters:
c:
action: create
label: PLUGIN_ADMIN.CREATE
r:
action: read
label: PLUGIN_ADMIN.READ
u:
action: update
label: PLUGIN_ADMIN.UPDATE
d:
action: delete
label: PLUGIN_ADMIN.DELETE
crudp:
type: crud
letters:
p:
action: publish
label: PLUGIN_ADMIN.PUBLISH
crudl:
type: crud
letters:
l:
action: list
label: PLUGIN_ADMIN.LIST
crudpl:
type: crud
use:
- crudp
- crudl

View File

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

View File

@@ -15,8 +15,9 @@ languages:
supported: [] # List of languages supported. eg: [en, fr, de]
default_lang: # Default is the first supported language. Must be one of the supported languages
include_default_lang: true # Include the default lang prefix in all URLs
include_default_lang_file_extension: true # If true, include language code for the default language in file extension: default.en.md
pages_fallback_only: false # Only fallback to find page content through supported languages
translations: true # Enable translations by default
translations: true # If false, translation keys are used instead of translated strings
translations_fallback: true # Fallback through supported translations if active lang doesn't exist
session_store_active: false # Store active language in session
http_accept_language: false # Attempt to set the language based on http_accept_language header in the browser
@@ -27,6 +28,7 @@ home:
hide_in_urls: false # Hide the home route in URLs
pages:
type: regular # EXPERIMENTAL: Page type: regular or flex
theme: quark # Default theme (defaults to "quark" theme)
order:
by: default # Order pages by "default", "alpha" or "date"
@@ -125,6 +127,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 +158,27 @@ 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: regular # EXPERIMENTAL: Account type: regular or flex
storage: file # EXPERIMENTAL: Flex storage type: file or folder
flex:
cache:
index:
enabled: true # Set to true to enable Flex index caching. Is used to cache timestamps in files
lifetime: 60 # Lifetime of cached index in seconds (0 = infinite)
object:
enabled: true # Set to true to enable Flex object caching. Is used to cache object data
lifetime: 600 # Lifetime of cached objects in seconds (0 = infinite)
render:
enabled: true # Set to true to enable Flex render caching. Is used to cache rendered output
lifetime: 600 # Lifetime of cached HTML in seconds (0 = infinite)
strict_mode:
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility

View File

@@ -2,14 +2,14 @@
/**
* @package Grav\Core
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.6.20');
define('GRAV_TESTING', false);
define('GRAV_VERSION', '1.7.0-rc.4');
define('GRAV_TESTING', true);
define('DS', '/');
if (!defined('GRAV_PHP_MIN')) {

View File

@@ -2,7 +2,7 @@
/**
* @package Grav\Core
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -10,8 +10,6 @@ if (!defined('GRAV_ROOT')) {
die();
}
use Grav\Installer\Install;
require_once __DIR__ . '/src/Grav/Installer/Install.php';
return Install::instance();
return Grav\Installer\Install::instance();

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Core
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -20,6 +20,7 @@ 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 ($grav_basedir) {
$grav_index = ltrim($grav_basedir, '/') . DIRECTORY_SEPARATOR . $grav_index;

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -37,29 +37,43 @@ class Assets extends PropertyObject
/** @const Regex to match JavaScript files */
const JS_REGEX = '/.\.js$/i';
/** @var string */
protected $assets_dir;
/** @var string */
protected $assets_url;
/** @var array */
protected $assets_css = [];
/** @var array */
protected $assets_js = [];
// Config Options
// Following variables come from the configuration:
/** @var bool */
protected $css_pipeline;
/** @var bool */
protected $css_pipeline_include_externals;
/** @var bool */
protected $css_pipeline_before_excludes;
/** @var bool */
protected $js_pipeline;
/** @var bool */
protected $js_pipeline_include_externals;
/** @var bool */
protected $js_pipeline_before_excludes;
/** @var array */
protected $pipeline_options = [];
/** @var \Closure|string */
protected $fetch_command;
/** @var string */
protected $autoload;
/** @var bool */
protected $enable_asset_timestamp;
/** @var array|null */
protected $collections;
/** @var string */
protected $timestamp;
/**
* Initialization called in the Grav lifecycle to initialize the Assets with appropriate configuration
*/
@@ -202,7 +216,6 @@ class Assets extends PropertyObject
}
return $this;
}
/**
@@ -212,7 +225,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 +279,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 +292,11 @@ class Assets extends PropertyObject
}
return false;
}
}
if ($asset[$key] === $value) return true;
if ($asset[$key] === $value) {
return true;
}
return false;
});
@@ -297,11 +310,8 @@ class Assets extends PropertyObject
protected function sortAssets($assets)
{
uasort ($assets, function($a, $b) {
if ($a['priority'] == $b['priority']) {
return $a['order'] - $b['order'];
}
return $b['priority'] - $a['priority'];
uasort($assets, static function ($a, $b) {
return $b['priority'] <=> $a['priority'] ?: $a['order'] <=> $b['order'];
});
return $assets;
}

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -25,29 +25,46 @@ abstract class BaseAsset extends PropertyObject
/** @const Regex to match CSS import content */
protected const CSS_IMPORT_REGEX = '{@import(.*?);}';
/** @var string|false */
protected $asset;
/** @var string */
protected $asset_type;
/** @var int */
protected $order;
/** @var string */
protected $group;
/** @var string */
protected $position;
/** @var int */
protected $priority;
/** @var array */
protected $attributes = [];
/** @var string */
protected $timestamp;
/** @var int|false */
protected $modified;
/** @var bool */
protected $remote;
/** @var string */
protected $query = '';
// Private Bits
private $base_url;
private $fetch_command;
/** @var bool */
private $css_rewrite = false;
/** @var bool */
private $css_minify = false;
/**
* @return string
*/
abstract function render();
/**
* BaseAsset constructor.
* @param array $elements
* @param string|null $key
*/
public function __construct(array $elements = [], $key = null)
{
$base_config = [
@@ -64,6 +81,11 @@ abstract class BaseAsset extends PropertyObject
parent::__construct($elements, $key);
}
/**
* @param string|false $asset
* @param array $options
* @return $this|false
*/
public function init($asset, $options)
{
$config = Grav::instance()['config'];
@@ -88,7 +110,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'];
@@ -122,19 +143,30 @@ abstract class BaseAsset extends PropertyObject
return $this;
}
/**
* @return string|false
*/
public function getAsset()
{
return $this->asset;
}
/**
* @return bool
*/
public function getRemote()
{
return $this->remote;
}
/**
* @param string $position
* @return $this
*/
public function setPosition($position)
{
$this->position = $position;
return $this;
}
@@ -163,7 +195,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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,6 +13,11 @@ use Grav\Common\Utils;
class Css extends BaseAsset
{
/**
* Css constructor.
* @param array $elements
* @param string|null $key
*/
public function __construct(array $elements = [], $key = null)
{
$base_options = [
@@ -28,10 +33,13 @@ class Css extends BaseAsset
parent::__construct($merged_attributes, $key);
}
/**
* @return string
*/
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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,6 +13,11 @@ use Grav\Common\Utils;
class InlineCss extends BaseAsset
{
/**
* InlineCss constructor.
* @param array $elements
* @param string|null $key
*/
public function __construct(array $elements = [], $key = null)
{
$base_options = [
@@ -25,6 +30,9 @@ class InlineCss extends BaseAsset
parent::__construct($merged_attributes, $key);
}
/**
* @return string
*/
public function render()
{
return '<style' . $this->renderAttributes(). ">\n" . trim($this->asset) . "\n</style>\n";

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,6 +13,11 @@ use Grav\Common\Utils;
class InlineJs extends BaseAsset
{
/**
* InlineJs constructor.
* @param array $elements
* @param string|null $key
*/
public function __construct(array $elements = [], $key = null)
{
$base_options = [
@@ -25,6 +30,9 @@ class InlineJs extends BaseAsset
parent::__construct($merged_attributes, $key);
}
/**
* @return string
*/
public function render()
{
return '<script' . $this->renderAttributes(). ">\n" . trim($this->asset) . "\n</script>\n";

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,6 +13,11 @@ use Grav\Common\Utils;
class Js extends BaseAsset
{
/**
* Js constructor.
* @param array $elements
* @param string|null $key
*/
public function __construct(array $elements = [], $key = null)
{
$base_options = [
@@ -24,10 +29,13 @@ class Js extends BaseAsset
parent::__construct($merged_attributes, $key);
}
/**
* @return string
*/
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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Assets
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -35,35 +35,40 @@ class Pipeline extends PropertyObject
protected const FIRST_FORWARDSLASH_REGEX = '{^\/{1}\w}';
protected $css_minify;
protected $css_minify_windows;
protected $css_rewrite;
// Following variables come from the configuration:
/** @var bool */
protected $css_minify = false;
/** @var bool */
protected $css_minify_windows = false;
/** @var bool */
protected $css_rewrite = false;
/** @var bool */
protected $css_pipeline_include_externals = true;
/** @var bool */
protected $js_minify = false;
/** @var bool */
protected $js_minify_windows = false;
/** @var bool */
protected $js_pipeline_include_externals = true;
protected $js_minify;
protected $js_minify_windows;
protected $base_url;
/** @var string */
protected $assets_dir;
/** @var string */
protected $assets_url;
/** @var string */
protected $timestamp;
/** @var array */
protected $attributes;
protected $query;
/** @var string */
protected $query = '';
/** @var string */
protected $asset;
/**
* Closure used by the pipeline to fetch assets.
*
* Useful when file_get_contents() function is not available in your PHP
* installation or when you want to apply any kind of preprocessing to
* your assets before they get pipelined.
*
* The closure will receive as the only parameter a string with the path/URL of the asset and
* it should return the content of the asset file as a string.
*
* @var \Closure
* Pipeline constructor.
* @param array $elements
* @param string|null $key
*/
protected $fetch_command;
public function __construct(array $elements = [], ?string $key = null)
{
parent::__construct($elements, $key);
@@ -88,7 +93,6 @@ class Pipeline extends PropertyObject
* @param array $assets
* @param string $group
* @param array $attributes
*
* @return bool|string URL or generated content if available, else false
*/
public function renderCss($assets, $group, $attributes = [])
@@ -152,7 +156,6 @@ class Pipeline extends PropertyObject
* @param array $assets
* @param string $group
* @param array $attributes
*
* @return bool|string URL or generated content if available, else false
*/
public function renderJs($assets, $group, $attributes = [])
@@ -217,7 +220,6 @@ class Pipeline extends PropertyObject
* @param string $file the css source file
* @param string $dir , $local relative path to the css file
* @param bool $local is this a local or remote asset
*
* @return mixed
*/
protected function cssRewrite($file, $dir, $local)
@@ -244,16 +246,16 @@ class Pipeline extends PropertyObject
$new_url = ($local ? $this->base_url: '') . $old_url;
$fixed = str_replace($matches[2], $new_url, $matches[0]);
return $fixed;
return str_replace($matches[2], $new_url, $matches[0]);
}, $file);
return $file;
}
/**
* @param string $type
* @return bool
*/
private function shouldMinify($type = 'css')
{
$check = $type . '_minify';

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Assets\Traits
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -14,6 +14,23 @@ use Grav\Common\Utils;
trait AssetUtilsTrait
{
/**
* @var \Closure|null
*
* Closure used by the pipeline to fetch assets.
*
* Useful when file_get_contents() function is not available in your PHP
* installation or when you want to apply any kind of preprocessing to
* your assets before they get pipelined.
*
* The closure will receive as the only parameter a string with the path/URL of the asset and
* it should return the content of the asset file as a string.
*/
protected $fetch_command;
/** @var string */
protected $base_url;
/**
* Determine whether a link is local or remote.
* Understands both "http://" and "https://" as well as protocol agnostic links "//"
@@ -38,7 +55,6 @@ trait AssetUtilsTrait
*
* @param array $assets
* @param bool $css
*
* @return string
*/
protected function gatherLinks(array $assets, $css = true)
@@ -69,7 +85,8 @@ trait AssetUtilsTrait
$link = ROOT_DIR . $relative_path;
}
$file = ($this->fetch_command instanceof \Closure) ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
// TODO: looks like this is not being used.
$file = $this->fetch_command instanceof \Closure ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
// No file found, skip it...
if ($file === false) {
@@ -102,7 +119,6 @@ trait AssetUtilsTrait
* Moves @import statements to the top of the file per the CSS specification
*
* @param string $file the file containing the combined CSS files
*
* @return string the modified file with any @imports at the top of the file
*/
protected function moveImports($file)

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Assets\Traits
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,7 +13,6 @@ use Grav\Common\Assets;
trait LegacyAssetsTrait
{
/**
* @param array $args
* @param string $type
@@ -39,12 +38,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);
@@ -61,13 +60,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);
}
@@ -75,6 +74,11 @@ trait LegacyAssetsTrait
return $arguments;
}
/**
* @param array $args
* @param array $defaults
* @return array
*/
protected function createArgumentsFromLegacy(array $args, array $defaults)
{
// Remove arguments with old default values.
@@ -97,8 +101,7 @@ trait LegacyAssetsTrait
* @param int $priority
* @param bool $pipeline
* @param string $group name of the group
*
* @return \Grav\Common\Assets
* @return Assets
* @deprecated Please use dynamic method with ['loading' => 'async'].
*/
public function addAsyncJs($asset, $priority = 10, $pipeline = true, $group = 'head')
@@ -115,8 +118,7 @@ trait LegacyAssetsTrait
* @param int $priority
* @param bool $pipeline
* @param string $group name of the group
*
* @return \Grav\Common\Assets
* @return Assets
* @deprecated Please use dynamic method with ['loading' => 'defer'].
*/
public function addDeferJs($asset, $priority = 10, $pipeline = true, $group = 'head')
@@ -125,5 +127,4 @@ trait LegacyAssetsTrait
return $this->addJs($asset, $priority, $pipeline, 'defer', $group);
}
}

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Assets\Traits
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -17,7 +17,6 @@ trait TestingAssetsTrait
* Determines if an asset exists as a collection, CSS or JS reference
*
* @param string $asset
*
* @return bool
*/
public function exists($asset)
@@ -39,7 +38,6 @@ trait TestingAssetsTrait
* Set the array of collections explicitly
*
* @param array $collections
*
* @return $this
*/
public function setCollection($collections)
@@ -54,7 +52,7 @@ trait TestingAssetsTrait
* If a $key is provided, it will try to return only that asset
* else it will return null
*
* @param null|string $key the asset key
* @param string|null $key the asset key
* @return array
*/
public function getCss($key = null)
@@ -73,7 +71,7 @@ trait TestingAssetsTrait
* If a $key is provided, it will try to return only that asset
* else it will return null
*
* @param null|string $key the asset key
* @param string|null $key the asset key
* @return array
*/
public function getJs($key = null)
@@ -91,7 +89,6 @@ trait TestingAssetsTrait
* Set the whole array of CSS assets
*
* @param array $css
*
* @return $this
*/
public function setCss($css)
@@ -105,7 +102,6 @@ trait TestingAssetsTrait
* Set the whole array of JS assets
*
* @param array $js
*
* @return $this
*/
public function setJs($js)
@@ -119,7 +115,6 @@ trait TestingAssetsTrait
* Removes an item from the CSS array if set
*
* @param string $key The asset key
*
* @return $this
*/
public function removeCss($key)
@@ -136,7 +131,6 @@ trait TestingAssetsTrait
* Removes an item from the JS array if set
*
* @param string $key The asset key
*
* @return $this
*/
public function removeJs($key)
@@ -153,7 +147,6 @@ trait TestingAssetsTrait
* Sets the state of CSS Pipeline
*
* @param bool $value
*
* @return $this
*/
public function setCssPipeline($value)
@@ -167,7 +160,6 @@ trait TestingAssetsTrait
* Sets the state of JS Pipeline
*
* @param bool $value
*
* @return $this
*/
public function setJsPipeline($value)
@@ -230,7 +222,7 @@ trait TestingAssetsTrait
* Get the timestamp for assets
*
* @param bool $include_join
* @return string
* @return string|null
*/
public function getTimestamp($include_join = true)
{
@@ -246,7 +238,6 @@ trait TestingAssetsTrait
*
* @param string $directory Relative to the Grav root path, or a stream identifier
* @param string $pattern (regex)
*
* @return $this
*/
public function addDir($directory, $pattern = self::DEFAULT_REGEX)
@@ -296,7 +287,6 @@ trait TestingAssetsTrait
* Add all JavaScript assets within $directory
*
* @param string $directory Relative to the Grav root path, or a stream identifier
*
* @return $this
*/
public function addDirJs($directory)
@@ -308,7 +298,6 @@ trait TestingAssetsTrait
* Add all CSS assets within $directory
*
* @param string $directory Relative to the Grav root path, or a stream identifier
*
* @return $this
*/
public function addDirCss($directory)
@@ -322,13 +311,14 @@ trait TestingAssetsTrait
* @param string $directory
* @param string $pattern (regex)
* @param string $ltrim Will be trimmed from the left of the file path
*
* @return array
*/
protected function rglob($directory, $pattern, $ltrim = null)
{
$iterator = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory,
\FilesystemIterator::SKIP_DOTS)), $pattern);
$iterator = new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(
$directory,
\FilesystemIterator::SKIP_DOTS
)), $pattern);
$offset = \strlen($ltrim);
$files = [];
@@ -338,6 +328,4 @@ trait TestingAssetsTrait
return $files;
}
}

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Backup
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -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
{
@@ -27,9 +27,11 @@ class Backups
protected const BACKUP_DATE_FORMAT = 'YmdHis';
/** @var string */
protected static $backup_dir;
protected static $backups = null;
/** @var array|null */
protected static $backups;
public function init()
{
@@ -47,6 +49,9 @@ class Backups
}
}
/**
* @param Event $event
*/
public function onSchedulerInitialized(Event $event)
{
/** @var Scheduler $scheduler */
@@ -60,38 +65,57 @@ 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');
}
}
/**
* @param string $backup
* @param string $base_url
* @return string
*/
public function getBackupDownloadUrl($backup, $base_url)
{
$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;
}
/**
* @return array
*/
public static function getBackupProfiles()
{
return Grav::instance()['config']->get('backups.profiles');
}
/**
* @return array
*/
public static function getPurgeConfig()
{
return Grav::instance()['config']->get('backups.purge');
}
/**
* @return array
*/
public function getBackupNames()
{
return array_column(static::getBackupProfiles(), 'name');
}
/**
* @return float|int
*/
public static function getTotalBackupsSize()
{
$backups = static::getAvailableBackups();
@@ -100,6 +124,10 @@ class Backups
return $size ?? 0;
}
/**
* @param bool $force
* @return array|null
*/
public static function getAvailableBackups($force = false)
{
if ($force || null === static::$backups) {
@@ -113,7 +141,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();
@@ -137,10 +164,9 @@ class Backups
/**
* Backup
*
* @param int $id
* @param int $id
* @param callable|null $status
*
* @return null|string
* @return string|null
*/
public static function backup($id = 0, callable $status = null)
{
@@ -158,7 +184,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)) {
@@ -214,19 +240,21 @@ class Backups
return $destination;
}
/**
* @throws \Exception
*/
public static function purge()
{
$purge_config = static::getPurgeConfig();
$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;
@@ -253,6 +281,10 @@ class Backups
}
}
/**
* @param string $exclude
* @return array
*/
protected static function convertExclude($exclude)
{
$lines = preg_split("/[\s,]+/", $exclude);

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -14,6 +14,7 @@ namespace Grav\Common;
*/
class Browser
{
/** @var string[] */
protected $useragent = [];
/**
@@ -108,7 +109,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()
{
@@ -135,7 +136,7 @@ class Browser
return true;
}
/**
* Determine if “Do Not Track” is set by browser
* @see https://www.w3.org/TR/tracking-dnt/

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -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.
@@ -29,36 +29,34 @@ use RocketTheme\Toolbox\Event\EventDispatcher;
*/
class Cache extends Getters
{
/**
* @var string Cache key.
*/
/** @var string Cache key. */
protected $key;
/** @var int */
protected $lifetime;
/** @var int */
protected $now;
/** @var Config $config */
protected $config;
/**
* @var DoctrineCache\CacheProvider
*/
/** @var DoctrineCache\CacheProvider */
protected $driver;
/**
* @var CacheInterface
*/
/** @var CacheInterface */
protected $simpleCache;
/** @var string */
protected $driver_name;
/** @var string */
protected $driver_setting;
/**
* @var bool
*/
/** @var bool */
protected $enabled;
/** @var string */
protected $cache_dir;
protected static $standard_remove = [
@@ -260,8 +258,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 +272,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 +292,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 +323,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 +338,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 +450,6 @@ class Cache extends Getters
} else {
$remove_paths = self::$standard_remove_no_images;
}
}
// Delete entries in the doctrine cache if required
@@ -459,11 +462,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 +532,6 @@ class Cache extends Getters
if (function_exists('opcache_reset')) {
@opcache_reset();
}
}
/**
@@ -634,7 +637,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 +648,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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,44 +13,28 @@ use RocketTheme\Toolbox\File\PhpFile;
abstract class CompiledBase
{
/**
* @var int Version number for the compiled file.
*/
/** @var int Version number for the compiled file. */
public $version = 1;
/**
* @var string Filename (base name) of the compiled configuration.
*/
/** @var string Filename (base name) of the compiled configuration. */
public $name;
/**
* @var string|bool Configuration checksum.
*/
/** @var string|bool Configuration checksum. */
public $checksum;
/**
* @var string Timestamp of compiled configuration
*/
public $timestamp;
/** @var int Timestamp of compiled configuration */
public $timestamp = 0;
/**
* @var string Cache folder to be used.
*/
/** @var string Cache folder to be used. */
protected $cacheFolder;
/**
* @var array List of files to load.
*/
/** @var array List of files to load. */
protected $files;
/**
* @var string
*/
/** @var string */
protected $path;
/**
* @var mixed Configuration object.
*/
/** @var mixed Configuration object. */
protected $object;
/**
@@ -68,7 +52,6 @@ abstract class CompiledBase
$this->path = $path ? rtrim($path, '\\/') . '/' : '';
$this->cacheFolder = $cacheFolder;
$this->files = $files;
$this->timestamp = 0;
}
/**
@@ -89,7 +72,9 @@ abstract class CompiledBase
/**
* Function gets called when cached configuration is saved.
*/
public function modified() {}
public function modified()
{
}
/**
* Get timestamp of compiled configuration
@@ -136,6 +121,9 @@ abstract class CompiledBase
return $this->checksum;
}
/**
* @return string
*/
protected function createFilename()
{
return "{$this->cacheFolder}/{$this->name()->name}.php";
@@ -157,7 +145,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 +185,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)
) {
@@ -256,6 +243,9 @@ abstract class CompiledBase
$this->modified();
}
/**
* @return array
*/
protected function getState()
{
return $this->object->toArray();

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -19,6 +19,12 @@ use Grav\Common\Grav;
*/
class CompiledBlueprints extends CompiledBase
{
/**
* CompiledBlueprints constructor.
* @param string $cacheFolder
* @param array $files
* @param string $path
*/
public function __construct($cacheFolder, array $files, $path)
{
parent::__construct($cacheFolder, $files, $path);
@@ -45,7 +51,7 @@ class CompiledBlueprints extends CompiledBase
/**
* Create configuration object.
*
* @param array $data
* @param array $data
*/
protected function createObject(array $data = [])
{
@@ -112,6 +118,9 @@ class CompiledBlueprints extends CompiledBase
return true;
}
/**
* @return array
*/
protected function getState()
{
return $this->object->getState();

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,16 +13,18 @@ use Grav\Common\File\CompiledYamlFile;
class CompiledConfig extends CompiledBase
{
/**
* @var callable Blueprints loader.
*/
/** @var callable Blueprints loader. */
protected $callable;
/**
* @var bool
*/
protected $withDefaults;
/** @var bool */
protected $withDefaults = false;
/**
* CompiledConfig constructor.
* @param string $cacheFolder
* @param array $files
* @param string $path
*/
public function __construct($cacheFolder, array $files, $path)
{
parent::__construct($cacheFolder, $files, $path);

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,6 +13,12 @@ use Grav\Common\File\CompiledYamlFile;
class CompiledLanguages extends CompiledBase
{
/**
* CompiledLanguages constructor.
* @param string $cacheFolder
* @param array $files
* @param string $path
*/
public function __construct($cacheFolder, array $files, $path)
{
parent::__construct($cacheFolder, $files, $path);

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -17,6 +17,7 @@ use Grav\Common\Utils;
class Config extends Data
{
/** @var string */
public $environment;
/** @var string */
@@ -28,6 +29,9 @@ class Config extends Data
/** @var bool */
protected $modified = false;
/**
* @return string
*/
public function key()
{
if (null === $this->key) {
@@ -37,6 +41,10 @@ class Config extends Data
return $this->key;
}
/**
* @param string|null $checksum
* @return string|null
*/
public function checksum($checksum = null)
{
if ($checksum !== null) {
@@ -46,6 +54,10 @@ class Config extends Data
return $this->checksum;
}
/**
* @param null $modified
* @return bool
*/
public function modified($modified = null)
{
if ($modified !== null) {
@@ -55,6 +67,10 @@ class Config extends Data
return $this->modified;
}
/**
* @param null $timestamp
* @return int
*/
public function timestamp($timestamp = null)
{
if ($timestamp !== null) {
@@ -64,6 +80,9 @@ class Config extends Data
return $this->timestamp;
}
/**
* @return $this
*/
public function reload()
{
$grav = Grav::instance();

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,6 +13,7 @@ use Grav\Common\Filesystem\Folder;
class ConfigFileFinder
{
/** @var string */
protected $base = '';
/**

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -14,22 +14,19 @@ use Grav\Common\Utils;
class Languages extends Data
{
/**
* @var string|null
*/
/** @var string|null */
protected $checksum;
/**
* @var string|null
*/
protected $modified;
/** @var bool */
protected $modified = false;
/** @var int */
protected $timestamp = 0;
/**
* @var string|null
* @param string|null $checksum
* @return string
*/
protected $timestamp;
public function checksum($checksum = null)
{
if ($checksum !== null) {
@@ -39,6 +36,10 @@ class Languages extends Data
return $this->checksum;
}
/**
* @param bool|null $modified
* @return bool
*/
public function modified($modified = null)
{
if ($modified !== null) {
@@ -48,6 +49,10 @@ class Languages extends Data
return $this->modified;
}
/**
* @param int|null $timestamp
* @return int
*/
public function timestamp($timestamp = null)
{
if ($timestamp !== null) {
@@ -65,17 +70,28 @@ class Languages extends Data
}
}
/**
* @param array $data
*/
public function mergeRecursive(array $data)
{
$this->items = Utils::arrayMergeRecursiveUnique($this->items, $data);
}
/**
* @param string $lang
* @return array
*/
public function flattenByLang($lang)
{
$language = $this->items[$lang];
return Utils::arrayFlattenDotNotation($language);
}
/**
* @param array $array
* @return array
*/
public function unflatten($array)
{
return Utils::arrayUnflattenDotNotation($array);

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Config
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -32,6 +32,7 @@ class Setup extends Data
*/
public static $environment;
/** @var array */
protected $streams = [
'system' => [
'type' => 'ReadOnlyStream',

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -20,14 +20,19 @@ class Blueprint extends BlueprintForm
/** @var string */
protected $context = 'blueprints://';
/** @var string|null */
protected $scope;
/** @var BlueprintSchema */
/** @var BlueprintSchema|null */
protected $blueprintSchema;
/** @var array */
/** @var object|null */
protected $object;
/** @var array|null */
protected $defaults;
/** @var array */
protected $handlers = [];
public function __clone()
@@ -37,11 +42,22 @@ class Blueprint extends BlueprintForm
}
}
/**
* @param string $scope
*/
public function setScope($scope)
{
$this->scope = $scope;
}
/**
* @param object $object
*/
public function setObject($object)
{
$this->object = $object;
}
/**
* Set default values for field types.
*
@@ -57,6 +73,29 @@ class Blueprint extends BlueprintForm
return $this;
}
/**
* @param string $name
* @return array|mixed|null
* @since 1.7
*/
public function getDefaultValue(string $name)
{
$path = explode('.', $name) ?: [];
$current = $this->getDefaults();
foreach ($path as $field) {
if (\is_object($current) && isset($current->{$field})) {
$current = $current->{$field};
} elseif (\is_array($current) && isset($current[$field])) {
$current = $current[$field];
} else {
return null;
}
}
return $current;
}
/**
* Get nested structure containing default values defined in the blueprints.
*
@@ -111,6 +150,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];
@@ -193,7 +233,7 @@ class Blueprint extends BlueprintForm
{
$this->initInternals();
return $this->blueprintSchema->filter($data, $missingValuesAsNull, $keepEmptyValues);
return $this->blueprintSchema->filter($data, $missingValuesAsNull, $keepEmptyValues) ?? [];
}
@@ -223,6 +263,10 @@ class Blueprint extends BlueprintForm
return $this->blueprintSchema;
}
/**
* @param string $name
* @param callable $callable
*/
public function addDynamicHandler(string $name, callable $callable): void
{
$this->handlers[$name] = $callable;
@@ -250,12 +294,12 @@ class Blueprint extends BlueprintForm
/**
* @param string $filename
* @return string
* @return array
*/
protected function loadFile($filename)
{
$file = CompiledYamlFile::instance($filename);
$content = $file->content();
$content = (array)$file->content();
$file->free();
return $content;
@@ -380,12 +424,31 @@ class Blueprint extends BlueprintForm
/** @var UserInterface|null $user */
$user = $grav['user'] ?? null;
foreach ($actions as $action) {
if (!$user || !$user->authorize($action)) {
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
return;
$success = null !== $user;
if ($success) {
$success = $this->resolveActions($user, $actions);
}
if (!$success) {
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
}
}
protected function resolveActions(UserInterface $user, array $actions, string $op = 'and')
{
$c = $i = count($actions);
foreach ($actions as $key => $action) {
if (!is_int($key) && is_array($actions)) {
$i -= $this->resolveActions($user, $action, $key);
} elseif ($user->authorize($action)) {
$i--;
}
}
if ($op === 'and') {
return $i === 0;
}
return $c !== $i;
}
/**
@@ -411,6 +474,11 @@ class Blueprint extends BlueprintForm
}
}
/**
* @param array $field
* @param string $property
* @param mixed $value
*/
protected function addPropertyRecursive(array &$field, $property, $value)
{
if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -18,6 +18,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{
use Export;
/** @var array */
protected $ignoreFormKeys = [
'title' => true,
'help' => true,
@@ -54,7 +55,6 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{
try {
$messages = $this->validateArray($data, $this->nested, $this->items['']['form'] ?? []);
} catch (\RuntimeException $e) {
throw (new ValidationException($e->getMessage(), $e->getCode(), $e))->setMessages();
}
@@ -84,7 +84,9 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
*/
public function filter(array $data, $missingValuesAsNull = false, $keepEmptyValues = false)
{
return $this->filterArray($data, $this->nested, $missingValuesAsNull, $keepEmptyValues);
$this->buildIgnoreNested($this->nested);
return $this->filterArray($data, $this->nested, '', $missingValuesAsNull, $keepEmptyValues) ?? [];
}
/**
@@ -164,45 +166,51 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
/**
* @param array $data
* @param array $rules
* @param string $parent
* @param bool $missingValuesAsNull
* @param bool $keepEmptyValues
* @return array
*/
protected function filterArray(array $data, array $rules, $missingValuesAsNull, $keepEmptyValues)
protected function filterArray(array $data, array $rules, string $parent, bool $missingValuesAsNull, bool $keepEmptyValues)
{
$results = [];
if ($missingValuesAsNull) {
// First pass is to fill up all the fields with null. This is done to lock the ordering of the fields.
foreach ($rules as $key => $rule) {
if ($key && !isset($results[$key])) {
$val = $rules[$key] ?? $rules['*'] ?? null;
$rule = \is_string($val) ? $this->items[$val] : null;
// First pass is to fill up all the fields with null.
foreach ($rules as $key => $val) {
if ($key && $key !== '*') {
$rule = $this->items[$parent . $key] ?? null;
if (empty($rule['disabled']) && empty($rule['validate']['ignore'])) {
if (!$rule || !empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
continue;
}
$results[$key] = null;
}
}
}
foreach ($data as $key => $field) {
$val = $rules[$key] ?? $rules['*'] ?? null;
$rule = \is_string($val) ? $this->items[$val] : null;
$rule = \is_string($val) ? $this->items[$val] : $this->items[$parent . $key] ?? null;
if ($rule) {
// Item has been defined in blueprints.
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
// Skip any data in the ignored field.
unset($results[$key]);
continue;
}
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
// Skip any data in the ignored field.
unset($results[$key]);
continue;
}
if ($rule && $rule['type'] !== '_parent') {
$field = Validation::filter($field, $rule);
} elseif (\is_array($field) && \is_array($val)) {
// Array has been defined in blueprints.
$field = $this->filterArray($field, $val, $missingValuesAsNull, $keepEmptyValues);
$field = $this->filterArray($field, $val, $parent . $key . '.', $missingValuesAsNull, $keepEmptyValues);
if (null === $field) {
// Nested parent has no values.
unset($results[$key]);
continue;
}
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
// Skip any extra data.
continue;
@@ -216,6 +224,24 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
return $results ?: null;
}
protected function buildIgnoreNested(array $nested, $parent = '')
{
$ignore = true;
foreach ($nested as $key => $val) {
$key = $parent . $key;
if (is_array($val)) {
$ignore = $this->buildIgnoreNested($val, $key . '.') && $ignore; // Keep the order!
} else {
$child = $this->items[$key] ?? null;
$ignore = $ignore && (!$child || !empty($child['disabled']) || !empty($child['validate']['ignore']));
}
}
if ($ignore) {
$key = trim($parent, '.');
$this->items[$key]['validate']['ignore'] = true;
}
}
/**
* @param array|null $data
* @param array $toggles
@@ -245,8 +271,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;
}
@@ -280,10 +306,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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,7 +13,6 @@ use RocketTheme\Toolbox\ArrayTraits\Countable;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\FileInterface;
class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable, ExportInterface
@@ -22,25 +21,21 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
/** @var string */
protected $gettersVariable = 'items';
/** @var array */
protected $items;
/** @var Blueprint */
/** @var Blueprint|null */
protected $blueprints;
/** @var File */
/** @var FileInterface|null */
protected $storage;
/** @var bool */
private $missingValuesAsNull = false;
/** @var bool */
private $keepEmptyValues = true;
/**
* @param array $items
* @param Blueprint|callable $blueprints
* @param Blueprint|callable|null $blueprints
*/
public function __construct(array $items = [], $blueprints = null)
{
@@ -255,7 +250,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.
@@ -308,8 +303,8 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
/**
* Set or get the data storage.
*
* @param FileInterface $storage Optionally enter a new storage.
* @return FileInterface
* @param FileInterface|null $storage Optionally enter a new storage.
* @return FileInterface|null
*/
public function file(FileInterface $storage = null)
{

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -153,6 +153,12 @@ class Validation
return true;
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return string
*/
protected static function filterText($value, array $params, array $field)
{
if (!\is_string($value) && !is_numeric($value)) {
@@ -180,21 +186,43 @@ class Validation
return $value === $field_value ? $value : null;
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return array|array[]|false|string[]
*/
protected static function filterCommaList($value, array $params, array $field)
{
return \is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return bool
*/
public static function typeCommaList($value, array $params, array $field)
{
return \is_array($value) ? true : self::typeText($value, $params, $field);
}
/**
* @param mixed $value
* @param array $params
* @return string
*/
protected static function filterLower($value, array $params)
{
return strtolower($value);
}
/**
* @param mixed $value
* @param array $params
* @return string
*/
protected static function filterUpper($value, array $params)
{
return strtoupper($value);
@@ -260,6 +288,12 @@ class Validation
return self::typeArray((array) $value, $params, $field);
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return array|null
*/
protected static function filterCheckboxes($value, array $params, array $field)
{
return self::filterArray($value, $params, $field);
@@ -324,6 +358,12 @@ class Validation
return self::typeArray((array)$value, $params, $field);
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return array
*/
protected static function filterFile($value, array $params, array $field)
{
return (array)$value;
@@ -369,11 +409,23 @@ class Validation
return !(isset($params['step']) && fmod($value - $min, $params['step']) === 0);
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return float|int
*/
protected static function filterNumber($value, array $params, array $field)
{
return (string)(int)$value !== (string)(float)$value ? (float) $value : (int) $value;
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return string
*/
protected static function filterDateTime($value, array $params, array $field)
{
$format = Grav::instance()['config']->get('system.pages.dateformat.default');
@@ -384,7 +436,6 @@ class Validation
return $value;
}
/**
* HTML5 input: range
*
@@ -398,6 +449,12 @@ class Validation
return self::typeNumber($value, $params, $field);
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return float|int
*/
protected static function filterRange($value, array $params, array $field)
{
return self::filterNumber($value, $params, $field);
@@ -604,6 +661,25 @@ class Validation
return !($options && array_diff($value, $options));
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return array|null
*/
protected static function filterFlatten_array($value, $params, $field)
{
$value = static::filterArray($value, $params, $field);
return Utils::arrayUnflattenDotNotation($value);
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return array|null
*/
protected static function filterArray($value, $params, $field)
{
$values = (array) $value;
@@ -633,25 +709,87 @@ class Validation
}
}
if (isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty'])) {
foreach ($values as $key => $val) {
if ($val === '') {
$ignoreEmpty = isset($field['ignore_empty']) && Utils::isPositive($field['ignore_empty']);
$valueType = $params['value_type'] ?? null;
$keyType = $params['key_type'] ?? null;
if ($ignoreEmpty || $valueType || $keyType) {
$values = static::arrayFilterRecurse($values, ['value_type' => $valueType, 'key_type' => $keyType, 'ignore_empty' => $ignoreEmpty]);
}
return $values;
}
/**
* @param array $values
* @param array $params
* @return array
*/
protected static function arrayFilterRecurse(array $values, array $params): array
{
foreach ($values as $key => &$val) {
if ($params['key_type']) {
switch ($params['key_type']) {
case 'int':
$result = is_int($key);
break;
case 'string':
$result = is_string($key);
break;
default:
$result = false;
}
if (!$result) {
unset($values[$key]);
} elseif (\is_array($val)) {
foreach ($val as $inner_key => $inner_value) {
if ($inner_value === '') {
unset($val[$inner_key]);
}
}
}
if (\is_array($val)) {
$val = static::arrayFilterRecurse($val, $params);
if ($params['ignore_empty'] && empty($val)) {
unset($values[$key]);
}
} else {
if ($params['value_type'] && $val !== '' && $val !== null) {
switch ($params['value_type']) {
case 'bool':
if (Utils::isPositive($val)) {
$val = true;
} elseif (Utils::isNegative($val)) {
$val = false;
} else {
// Ignore invalid bool values.
$val = null;
}
break;
case 'int':
$val = (int)$val;
break;
case 'float':
$val = (float)$val;
break;
case 'string':
$val = (string)$val;
break;
case 'trim':
$val = trim($val);
break;
}
}
$values[$key] = $val;
if ($params['ignore_empty'] && ($val === '' || $val === null)) {
unset($values[$key]);
}
}
}
return $values;
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return bool
*/
public static function typeList($value, array $params, array $field)
{
if (!\is_array($value)) {
@@ -671,11 +809,22 @@ class Validation
return true;
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return array
*/
protected static function filterList($value, array $params, array $field)
{
return (array) $value;
}
/**
* @param mixed $value
* @param array $params
* @return array
*/
public static function filterYaml($value, $params)
{
if (!\is_string($value)) {
@@ -683,7 +832,6 @@ class Validation
}
return (array) Yaml::parse($value);
}
/**
@@ -699,6 +847,12 @@ class Validation
return true;
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return mixed
*/
public static function filterIgnore($value, array $params, array $field)
{
return $value;
@@ -717,6 +871,12 @@ class Validation
return true;
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return null
*/
public static function filterUnset($value, array $params, array $field)
{
return null;
@@ -724,6 +884,11 @@ class Validation
// HTML5 attributes (min, max and range are handled inside the types)
/**
* @param mixed $value
* @param bool $params
* @return bool
*/
public static function validateRequired($value, $params)
{
if (is_scalar($value)) {
@@ -733,79 +898,155 @@ class Validation
return (bool) $params !== true || !empty($value);
}
/**
* @param mixed $value
* @param string $params
* @return bool
*/
public static function validatePattern($value, $params)
{
return (bool) preg_match("`^{$params}$`u", $value);
}
// Internal types
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function validateAlpha($value, $params)
{
return ctype_alpha($value);
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function validateAlnum($value, $params)
{
return ctype_alnum($value);
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function typeBool($value, $params)
{
return \is_bool($value) || $value == 1 || $value == 0;
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function validateBool($value, $params)
{
return \is_bool($value) || $value == 1 || $value == 0;
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
protected static function filterBool($value, $params)
{
return (bool) $value;
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function validateDigit($value, $params)
{
return ctype_digit($value);
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function validateFloat($value, $params)
{
return \is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
}
/**
* @param mixed $value
* @param mixed $params
* @return float
*/
protected static function filterFloat($value, $params)
{
return (float) $value;
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function validateHex($value, $params)
{
return ctype_xdigit($value);
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function validateInt($value, $params)
{
return is_numeric($value) && (int)$value == $value;
}
/**
* @param mixed $value
* @param mixed $params
* @return int
*/
protected static function filterInt($value, $params)
{
return (int)$value;
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function validateArray($value, $params)
{
return \is_array($value) || ($value instanceof \ArrayAccess && $value instanceof \Traversable && $value instanceof \Countable);
}
/**
* @param mixed $value
* @param mixed $params
* @return array
*/
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);
}));
}
/**
* @param mixed $value
* @param mixed $params
* @return bool
*/
public static function validateJson($value, $params)
{
return (bool) (@json_decode($value));

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Data
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,9 +13,15 @@ use Grav\Common\Grav;
class ValidationException extends \RuntimeException
{
/** @var array */
protected $messages = [];
public function setMessages(array $messages = []) {
/**
* @param array $messages
* @return $this
*/
public function setMessages(array $messages = [])
{
$this->messages = $messages;
$language = Grav::instance()['language'];
@@ -31,6 +37,9 @@ class ValidationException extends \RuntimeException
return $this;
}
/**
* @return array
*/
public function getMessages()
{
return $this->messages;

View File

@@ -3,12 +3,19 @@
/**
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
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;
@@ -19,70 +26,90 @@ use DebugBar\DataCollector\RequestDataCollector;
use DebugBar\DataCollector\TimeDataCollector;
use DebugBar\DebugBar;
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 Grav $grav */
/** @var static */
protected static $instance;
/** @var Grav|null */
protected $grav;
/** @var Config $config */
/** @var Config|null */
protected $config;
/** @var JavascriptRenderer $renderer */
/** @var JavascriptRenderer|null */
protected $renderer;
/** @var StandardDebugBar $debugbar */
/** @var DebugBar|null */
protected $debugbar;
/** @var bool */
protected $enabled;
/** @var Clockwork|null */
protected $clockwork;
/** @var bool */
protected $enabled = false;
/** @var bool */
protected $initialized = false;
/** @var array */
protected $timers = [];
/** @var array $deprecations */
/** @var array */
protected $deprecations = [];
/** @var callable */
/** @var callable|null */
protected $errorHandler;
/** @var float */
protected $requestTime;
/** @var float */
protected $currentTime;
/** @var int */
protected $profiling = 0;
/** @var bool */
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 +127,232 @@ 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) {
$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);
$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
* @return DataCollectorInterface|null
* @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,74 @@ 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 = '';
}
if (is_bool($isString)) {
$isString = [];
}
if (!is_array($isString)) {
$type = gettype($isString);
$isString = [$type => $isString];
}
$this->clockwork->log($label, $message, $isString);
}
}
return $this;
}
/**
* @param string $name
* @param object $event
* @param EventDispatcherInterface $dispatcher
* @return $this
*/
public function addEvent(string $name, $event, EventDispatcherInterface $dispatcher)
{
if ($this->enabled) {
if ($this->clockwork) {
$data = null;
if ($event && method_exists($event, '__debugInfo')) {
$data = $event;
}
$listeners = [];
foreach ($dispatcher->getListeners($name) as $listener) {
$listeners[] = $this->resolveCallable($listener);
}
$this->clockwork->addEvent($name, $data, microtime(true), ['listeners' => $listeners]);
}
}
return $this;
@@ -319,13 +758,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 +804,7 @@ class Debugger
return true;
}
if (!$this->enabled()) {
if (!$this->enabled) {
return true;
}
@@ -540,6 +989,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 +1067,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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Errors
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,20 +13,17 @@ use Whoops\Handler\Handler;
class BareHandler extends Handler
{
/**
* @return int|null
* @return int
*/
public function handle()
{
$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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Errors
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -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

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Errors
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -15,8 +15,10 @@ use Whoops\Util\TemplateHelper;
class SimplePageHandler extends Handler
{
private $searchPaths = array();
private $resourceCache = array();
/** @var array */
private $searchPaths = [];
/** @var array */
private $resourceCache = [];
public function __construct()
{
@@ -25,7 +27,7 @@ class SimplePageHandler extends Handler
}
/**
* @return int|null
* @return int
*/
public function handle()
{
@@ -36,9 +38,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();
@@ -60,7 +61,6 @@ class SimplePageHandler extends Handler
/**
* @param string $resource
*
* @return string
* @throws \RuntimeException
*/
@@ -90,6 +90,9 @@ class SimplePageHandler extends Handler
);
}
/**
* @param string $path
*/
public function addResourcePath($path)
{
if (!is_dir($path)) {
@@ -101,6 +104,9 @@ class SimplePageHandler extends Handler
array_unshift($this->searchPaths, $path);
}
/**
* @return array
*/
public function getResourcePaths()
{
return $this->searchPaths;

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Errors
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -11,11 +11,11 @@ namespace Grav\Common\Errors;
class SystemFacade extends \Whoops\Util\SystemFacade
{
/** @var callable */
protected $whoopsShutdownHandler;
/**
* @param callable $function
*
* @return void
*/
public function registerShutdownFunction(callable $function)

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\File
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -17,7 +17,7 @@ trait CompiledFile
* Get/set parsed file contents.
*
* @param mixed $var
* @return string
* @return string|array
*/
public function content($var = null)
{
@@ -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);
}
@@ -86,6 +84,8 @@ trait CompiledFile
/**
* Serialize file.
*
* @return array
*/
public function __sleep()
{

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\File
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -20,7 +20,7 @@ class CompiledJsonFile extends JsonFile
*
* @param string $var
* @param bool $assoc
* @return array mixed
* @return array
*/
protected function decode($var, $assoc = true)
{

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\File
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\File
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,13 +13,19 @@ use Grav\Common\Utils;
abstract class Archiver
{
/** @var array */
protected $options = [
'exclude_files' => ['.DS_Store'],
'exclude_paths' => []
];
/** @var string */
protected $archive_file;
/**
* @param string $compression
* @return ZipArchiver
*/
public static function create($compression)
{
if ($compression === 'zip') {
@@ -29,12 +35,20 @@ abstract class Archiver
return new ZipArchiver();
}
/**
* @param string $archive_file
* @return $this
*/
public function setArchive($archive_file)
{
$this->archive_file = $archive_file;
return $this;
}
/**
* @param array $options
* @return $this
*/
public function setOptions($options)
{
// Set infinite PHP execution time if possible.
@@ -46,12 +60,31 @@ abstract class Archiver
return $this;
}
public abstract function compress($folder, callable $status = null);
/**
* @param string $folder
* @param callable|null $status
* @return $this
*/
abstract public function compress($folder, callable $status = null);
public abstract function extract($destination, callable $status = null);
/**
* @param string $destination
* @param callable|null $status
* @return $this
*/
abstract public function extract($destination, callable $status = null);
public abstract function addEmptyFolders($folders, callable $status = null);
/**
* @param array $folders
* @param callable|null $status
* @return $this
*/
abstract public function addEmptyFolders($folders, callable $status = null);
/**
* @param string $rootPath
* @return \RecursiveIteratorIterator
*/
protected function getArchiveFiles($rootPath)
{
$exclude_paths = $this->options['exclude_paths'];
@@ -62,5 +95,4 @@ abstract class Archiver
return $files;
}
}

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -55,7 +55,6 @@ abstract class Folder
*
* @param string $path
* @param string $extensions which files to search for specifically
*
* @return int
*/
public static function lastModifiedFile($path, $extensions = 'md|yaml')
@@ -126,9 +125,8 @@ abstract class Folder
/**
* Get relative path between target and base path. If path isn't relative, return full path.
*
* @param string $path
* @param mixed|string $base
*
* @param string $path
* @param string $base
* @return string
*/
public static function getRelativePath($path, $base = GRAV_ROOT)
@@ -207,7 +205,7 @@ abstract class Folder
*/
public static function all($path, array $params = [])
{
if ($path === false) {
if (!$path) {
throw new \RuntimeException("Path doesn't exist.");
}
if (!file_exists($path)) {

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -11,8 +11,11 @@ namespace Grav\Common\Filesystem;
class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
{
/** @var string */
protected static $root;
/** @var array */
protected static $ignore_folders;
/** @var array */
protected static $ignore_files;
/**
@@ -57,6 +60,9 @@ class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
return false;
}
/**
* @return RecursiveDirectoryFilterIterator|\RecursiveFilterIterator
*/
public function getChildren()
{
/** @var RecursiveDirectoryFilterIterator $iterator */

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,6 +13,7 @@ use Grav\Common\Grav;
class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
{
/** @var array */
protected static $ignore_folders;
/**

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Filesystem
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -11,7 +11,11 @@ namespace Grav\Common\Filesystem;
class ZipArchiver extends Archiver
{
/**
* @param string $destination
* @param callable|null $status
* @return $this
*/
public function extract($destination, callable $status = null)
{
$zip = new \ZipArchive();
@@ -31,6 +35,11 @@ class ZipArchiver extends Archiver
throw new \RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file);
}
/**
* @param string $source
* @param callable|null $status
* @return $this
*/
public function compress($source, callable $status = null)
{
if (!extension_loaded('zip')) {
@@ -81,6 +90,11 @@ class ZipArchiver extends Archiver
return $this;
}
/**
* @param array $folders
* @param callable|null $status
* @return $this
*/
public function addEmptyFolders($folders, callable $status = null)
{
if (!extension_loaded('zip')) {
@@ -97,7 +111,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

@@ -0,0 +1,583 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Pages;
use Grav\Common\Page\Interfaces\PageCollectionInterface;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Utils;
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
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
{
/** @var array|null */
protected $_params;
/**
* @return array
*/
public static function getCachedMethods(): array
{
return [
// Collection specific methods
'getRoot' => false,
'getParams' => false,
'setParams' => false,
'params' => false,
'addPage' => false,
'merge' => false,
'intersect' => false,
'prev' => false,
'nth' => false,
'random' => false,
'append' => false,
'batch' => false,
'order' => false,
// Collection filtering
'dateRange' => true,
'visible' => true,
'nonVisible' => true,
'modular' => true,
'nonModular' => true,
'published' => true,
'nonPublished' => true,
'routable' => true,
'nonRoutable' => true,
'ofType' => true,
'ofOneOfTheseTypes' => true,
'ofOneOfTheseAccessLevels' => true,
'withModules' => true,
'withPages' => true,
'withTranslation' => true,
'filterBy' => true,
'toExtendedArray' => false,
'getLevelListing' => false,
] + parent::getCachedMethods();
}
/**
* @return PageInterface|FlexObjectInterface
*/
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 = (string)$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|false $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 = [];
/**
* @var int|string $key
* @var PageInterface|null $object
*/
foreach ($this as $key => $object) {
if ($object && $object->isModule()) {
$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 = [];
/**
* @var int|string $key
* @var PageInterface|null $object
*/
foreach ($this as $key => $object) {
if ($object && !$object->isModule()) {
$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);
}
/**
* @param bool $bool
* @return FlexCollectionInterface|FlexPageCollection
*/
public function withModules(bool $bool = true)
{
$list = array_keys(array_filter($this->call('isModule', [$bool])));
return $this->select($list);
}
/**
* @param bool $bool
* @return FlexCollectionInterface|FlexPageCollection
*/
public function withPages(bool $bool = true)
{
$list = array_keys(array_filter($this->call('isPage', [$bool])));
return $this->select($list);
}
/**
* @param bool $bool
* @param string|null $languageCode
* @param bool|null $fallback
* @return FlexCollectionInterface|FlexPageCollection
*/
public function withTranslation(bool $bool = true, string $languageCode = null, bool $fallback = null)
{
$list = array_keys(array_filter($this->call('hasTranslation', [$languageCode, $fallback])));
return $bool ? $this->select($list) : $this->unselect($list);
}
/**
* Filter pages by given filters.
*
* - search: string
* - page_type: string|string[]
* - modular: bool
* - visible: bool
* - routable: bool
* - published: bool
* - page: bool
* - translated: bool
*
* @param array $filters
* @param bool $recursive
* @return FlexCollectionInterface
*/
public function filterBy(array $filters, bool $recursive = false)
{
$list = array_keys(array_filter($this->call('filterBy', [$filters, $recursive])));
return $this->select($list);
}
/**
* 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
{
/** @var PageIndex $index */
$index = $this->getIndex();
return method_exists($index, 'getLevelListing') ? $index->getLevelListing($options) : [];
}
}

View File

@@ -0,0 +1,540 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Pages;
use Grav\Common\Debugger;
use Grav\Common\File\CompiledJsonFile;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Grav\Framework\Flex\FlexDirectory;
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
use Grav\Framework\Flex\Interfaces\FlexStorageInterface;
use Grav\Framework\Flex\Pages\FlexPageIndex;
/**
* Class GravPageObject
* @package Grav\Plugin\FlexObjects\Types\GravPages
*
* @method PageIndex dateRange($startDate, $endDate = false, $field = null)
* @method PageIndex visible()
* @method PageIndex nonVisible()
* @method PageIndex modular()
* @method PageIndex nonModular()
* @method PageIndex published()
* @method PageIndex nonPublished()
* @method PageIndex routable()
* @method PageIndex nonRoutable()
* @method PageIndex ofType(string $type)
* @method PageIndex ofOneOfTheseTypes(array $types)
* @method PageIndex ofOneOfTheseAccessLevels(array $accessLevels)
* @method PageIndex withModules(bool $bool = true)
* @method PageIndex withPages(bool $bool = true)
* @method PageIndex withTranslation(bool $bool = true, string $languageCode = null, bool $fallback = null)
*/
class PageIndex extends FlexPageIndex
{
public const VERSION = parent::VERSION . '.5';
public const ORDER_LIST_REGEX = '/(\/\d+)\.[^\/]+/u';
public const PAGE_ROUTE_REGEX = '/\/\d+\./u';
/** @var PageObject|array */
protected $_root;
/** @var array|null */
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 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 PageObject|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 PageObject
*/
public function getRoot()
{
$root = $this->_root;
if (is_array($root)) {
$directory = $this->getFlexDirectory();
$storage = $directory->getStorage();
$defaults = [
'header' => [
'routable' => false,
'permissions' => [
'inherit' => false
]
]
];
$row = $storage->readRows(['' => null])[''] ?? null;
if (null !== $row) {
if (isset($row['__ERROR'])) {
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];
$message = sprintf('Flex Pages: root page is broken in storage: %s', $row['__ERROR']);
$debugger->addException(new \RuntimeException($message));
$debugger->addMessage($message, 'error');
$row = ['__META' => $root];
}
} else {
$row = ['__META' => $root];
}
$row = array_merge_recursive($defaults, $row);
/** @var PageObject $root */
$root = $this->getFlexDirectory()->createObject($row, '/', false);
$root->name('root.md');
$root->root(true);
$this->_root = $root;
}
return $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();
}
/**
* Filter pages by given filters.
*
* - search: string
* - page_type: string|string[]
* - modular: bool
* - visible: bool
* - routable: bool
* - published: bool
* - page: bool
* - translated: bool
*
* @param array $filters
* @param bool $recursive
* @return FlexCollectionInterface
*/
public function filterBy(array $filters, bool $recursive = false)
{
if (!$filters) {
return $this;
}
if ($recursive) {
return $this->__call('filterBy', [$filters, true]);
}
$list = [];
$index = $this;
foreach ($filters as $key => $value) {
switch ($key) {
case 'search':
$index = $index->search((string)$value);
break;
case 'page_type':
if (!is_array($value)) {
$value = is_string($value) && $value !== '' ? explode(',', $value) : [];
}
$index = $index->ofOneOfTheseTypes($value);
break;
case 'routable':
$index = $index->withRoutable((bool)$value);
break;
case 'published':
$index = $index->withPublished((bool)$value);
break;
case 'visible':
$index = $index->withVisible((bool)$value);
break;
case 'module':
$index = $index->withModules((bool)$value);
break;
case 'page':
$index = $index->withPages((bool)$value);
break;
case 'folder':
$index = $index->withPages(!$value);
break;
case 'translated':
$index = $index->withTranslation((bool)$value);
break;
default:
$list[$key] = $value;
}
}
return $list ? $index->filterByParent($list) : $index;
}
/**
* @param array $filters
* @return FlexCollectionInterface
*/
protected function filterByParent(array $filters)
{
return parent::filterBy($filters);
}
/**
* @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 $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 array $options
* @return array
*/
protected function getLevelListingRecurse(array $options): array
{
$filters = $options['filters'] ?? [];
$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) {
// Get forced filters from the field.
$blueprint = $page ? $page->getBlueprint() : $this->getFlexDirectory()->getBlueprint();
$settings = $blueprint->schema()->getProperty($field);
$filters = array_merge([], $filters, $settings['filters'] ?? []);
}
// Clean up filter.
$filter_type = (array)($filters['type'] ?? []);
unset($filters['type']);
$filters = array_filter($filters, static function($val) { return $val !== null && $val !== ''; });
if ($page) {
if ($page->root() && (!$filter_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' => '-root-',
'icon' => 'root',
'title' => 'Root', // FIXME
'route' => [
'display' => '&lt;root&gt;', // FIXME
'raw' => '_root',
],
'modified' => $page->modified(),
'extras' => [
'template' => $page->template(),
//'lang' => null,
//'translated' => null,
'langs' => [],
'published' => false,
'visible' => false,
'routable' => false,
'tags' => ['root', 'non-routable'],
'actions' => ['edit'], // FIXME
]
];
}
}
$status = 'success';
$msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND';
/** @var PageIndex $children */
$children = $page->children()->getIndex();
$selectedChildren = $children->filterBy($filters, true);
/** @var PageObject $child */
foreach ($selectedChildren as $child) {
$selected = $child->path() === $extra;
$includeChildren = \is_array($leaf) && !empty($leaf) && $selected;
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->isModule()) {
$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';
/** @var PageObject $child */
$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;
});
$tmp = $child->children()->getIndex();
$child_count = $tmp->count();
$count = $filters ? $tmp->filterBy($filters, true)->count() : null;
$payload = [
'item-key' => basename($child->rawRoute() ?? $child->getKey()),
'icon' => $icon,
'title' => $child->title(),
'route' => [
'display' => $child->getRoute()->toString(false) ?: '/',
'raw' => $child->rawRoute(),
],
'modified' => $this->jsDate($child->modified()),
'child_count' => $child_count ?: null,
'count' => $count ?? null,
'filters_hit' => $filters ? ($child->filterBy($filters, false) ?: null) : 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,441 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Pages;
use Grav\Common\Data\Blueprint;
use Grav\Common\Grav;
use Grav\Common\Flex\Pages\Traits\PageContentTrait;
use Grav\Common\Flex\Pages\Traits\PageLegacyTrait;
use Grav\Common\Flex\Pages\Traits\PageRoutableTrait;
use Grav\Common\Flex\Pages\Traits\PageTranslateTrait;
use Grav\Common\Page\Pages;
use Grav\Common\Utils;
use Grav\Framework\Filesystem\Filesystem;
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;
/** @var bool */
private $_initialized = false;
/**
* @return array
*/
public static function getCachedMethods(): array
{
return [
'path' => true,
'full_order' => true,
'filterBy' => 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':
$filesystem = Filesystem::getInstance(false);
$key = $filesystem->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 && !$this->root()) {
$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)
{
$filesystem = Filesystem::getInstance(false);
$storageKey = $this->getStorageKey();
$oldParentKey = ltrim($filesystem->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|null $siblings */
$siblings = $parent ? $parent->children() : null;
/** @var PageCollection|null $siblings */
$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);
}
/** @var PageCollection $siblings */
$siblings = $siblings->orderBy(['order' => 'ASC']);
$siblings->removeElement($this);
} else {
/** @var PageCollection $siblings */
$siblings = $this->getFlexDirectory()->createCollection([]);
}
return $siblings;
}
/**
* @return string
*/
public function full_order(): string
{
$route = $this->path() . '/' . $this->folder();
return preg_replace(PageIndex::ORDER_LIST_REGEX, '\\1', $route) ?? $route;
}
/**
* @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|null $admin */
$admin = Grav::instance()['admin'] ?? null;
if ($admin) {
$template = $this->isModule() ? 'modular_raw' : (!$this->root() ? 'raw' : 'root_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
{
$index = $this->getFlexDirectory()->getIndex();
return method_exists($index, 'getLevelListing') ? $index->getLevelListing($options) : [];
}
/**
* Filter page (true/false) by given filters.
*
* - search: string
* - extension: string
* - module: bool
* - visible: bool
* - routable: bool
* - published: bool
* - page: bool
* - translated: bool
*
* @param array $filters
* @param bool $recursive
* @return bool
*/
public function filterBy(array $filters, bool $recursive = false): bool
{
foreach ($filters as $key => $value) {
switch ($key) {
case 'search':
$matches = $this->search((string)$value) > 0.0;
break;
case 'page_type':
$types = $value ? explode(',', $value) : [];
$matches = in_array($this->template(), $types, true);
break;
case 'extension':
$matches = Utils::contains((string)$value, $this->extension());
break;
case 'routable':
$matches = $this->isRoutable() === (bool)$value;
break;
case 'published':
$matches = $this->isPublished() === (bool)$value;
break;
case 'visible':
$matches = $this->isVisible() === (bool)$value;
break;
case 'module':
$matches = $this->isModule() === (bool)$value;
break;
case 'page':
$matches = $this->isPage() === (bool)$value;
break;
case 'folder':
$matches = $this->isPage() === !$value;
break;
case 'translated':
$matches = $this->hasTranslation() === (bool)$value;
break;
default:
$matches = true;
break;
}
// If current filter does not match, we still may have match as a parent.
if ($matches === false) {
return $recursive && $this->children()->getIndex()->filterBy($filters, true)->count() > 0;
}
}
return true;
}
/**
* {@inheritdoc}
* @see FlexObjectInterface::exists()
*/
public function exists(): bool
{
return $this->root ?: parent::exists();
}
/**
* @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(static::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|null $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 (!empty($elements['order']) && $parent !== $this->parent()) {
/** @var PageCollection $children */
$children = $parent->children();
$siblings = $children->visible()->sort(['order' => 'ASC']);
if ($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,648 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Pages;
use Grav\Common\Debugger;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Framework\Filesystem\Filesystem;
use Grav\Framework\Flex\Storage\FolderStorage;
use RocketTheme\Toolbox\File\MarkdownFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* Class GravPageStorage
* @package Grav\Plugin\FlexObjects\Types\GravPages
*/
class PageStorage extends FolderStorage
{
/** @var bool */
protected $ignore_hidden;
/** @var array */
protected $ignore_files;
/** @var array */
protected $ignore_folders;
/** @var bool */
protected $include_default_lang_file_extension;
/** @var bool */
protected $recurse;
/** @var string */
protected $base_path;
/** @var int */
protected $flags;
/** @var string */
protected $regex;
/**
* @param array $options
*/
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 = (bool)$config->get('system.languages.include_default_lang_file_extension', true);
$this->recurse = (bool)($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;
}
/**
* @param string $key
* @return string
*/
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;
}
/**
* @param string $key
* @return string
*/
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
{
$folder = $this->buildFolder($keys);
$filename = $this->buildFilename($keys);
return rtrim($folder, '/') !== $folder ? $folder . $filename : $folder . '/' . $filename;
}
/**
* @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];
}
$filesystem = Filesystem::getInstance(false);
$parentKey = ltrim($filesystem->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']);
}
/**
* @param string $key
* @return array
*/
protected function loadRow(string $key): ?array
{
$data = parent::loadRow($key);
// Special case for root page.
if ($key === '' && null !== $data) {
$data['root'] = true;
}
return $data;
}
/**
* 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 = rtrim($newFolder, '/') !== $newFolder ? $newFolder . $newFilename : $newFolder . '/' . $newFilename;
try {
if ($key === '' && empty($row['root'])) {
throw new \RuntimeException('No storage key given');
}
$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;
}
/**
* @param string $key
* @return bool
*/
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 = $this->getStoragePath($key);
$path = $path ? $locator->findResource($path) : null;
} else {
$path = null;
}
$modified = 0;
$markdown = [];
$children = [];
if (is_string($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;
}
/**
* @return array
*/
protected function getIndexMeta(): array
{
$queue = [''];
$list = [];
do {
$current = array_pop($queue);
if ($current === null) {
break;
}
$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 !== '') {
$filesystem = Filesystem::getInstance(false);
$storage_key = (string)$storage_key;
$parentKey = $filesystem->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,75 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Pages\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->getKey())));
$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
* @param bool $bool
*/
public function isPage(bool $bool = true): bool
{
$meta = $this->getMetaData();
return empty($meta['markdown']) !== $bool;
}
}

View File

@@ -0,0 +1,233 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Pages\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;
use Grav\Framework\Flex\Interfaces\FlexIndexInterface;
/**
* Implements PageLegacyInterface.
*/
trait PageLegacyTrait
{
/**
* Returns children of this page.
*
* @return FlexIndexInterface|PageCollectionInterface|Collection
*/
public function children()
{
if (Utils::isAdminPlugin()) {
return parent::children();
}
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$path = $this->path() ?? '';
return $pages->children($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();
}
$path = $this->path();
$parent = $this->parent();
$collection = $parent ? $parent->collection('content', false) : null;
if (null !== $path && $collection instanceof PageCollectionInterface) {
return $collection->isFirst($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();
}
$path = $this->path();
$parent = $this->parent();
$collection = $parent ? $parent->collection('content', false) : null;
if (null !== $path && $collection instanceof PageCollectionInterface) {
return $collection->isLast($path);
}
return true;
}
/**
* Returns the adjacent sibling based on a direction.
*
* @param int $direction either -1 or +1
* @return PageInterface|false the sibling page
*/
public function adjacentSibling($direction = 1)
{
if (Utils::isAdminPlugin()) {
return parent::adjacentSibling($direction);
}
$path = $this->path();
$parent = $this->parent();
$collection = $parent ? $parent->collection('content', false) : null;
if (null !== $path && $collection instanceof PageCollectionInterface) {
return $collection->adjacentSibling($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 PageCollectionInterface|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 PageCollectionInterface|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,118 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Pages\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;
use Grav\Framework\Filesystem\Filesystem;
/**
* 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');
}
if ($this->root()) {
return null;
}
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$filesystem = Filesystem::getInstance(false);
// FIXME: this does not work, needs to use $pages->get() with cached parent id!
$key = $this->getKey();
$parent_route = $filesystem->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
{
$path = $this->path();
$parent = $this->parent();
$collection = $parent ? $parent->collection('content', false) : null;
if (null !== $path && $collection instanceof PageCollectionInterface) {
return $collection->currentPosition($path);
}
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])) {
$page = $pages->dispatch($uri->route(), false, false);
/** @var PageInterface|null $child_page */
$child_page = $page ? $page->parent() : null;
while ($child_page && !$child_page->root()) {
if ($this->path() === $child_page->path()) {
return true;
}
$child_page = $child_page->parent();
}
}
return false;
}
}

View File

@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Pages\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;
}
$header = $aPage->header();
$routes = isset($header->routes) ? $header->routes : [];
$route = $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

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\UserGroups;
use Grav\Framework\Flex\FlexCollection;
class UserGroupCollection extends FlexCollection
{
/**
* @return array
*/
public static function getCachedMethods(): array
{
return [
'authorize' => 'session',
] + parent::getCachedMethods();
}
/**
* Checks user authorization to the action.
*
* @param string $action
* @param string|null $scope
* @return bool|null
*/
public function authorize(string $action, string $scope = null): ?bool
{
$authorized = null;
/** @var UserGroupObject $object */
foreach ($this as $object) {
$auth = $object->authorize($action, $scope);
if ($auth === true) {
$authorized = true;
} elseif ($auth === false) {
return false;
}
}
return $authorized;
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\UserGroups;
use Grav\Framework\Flex\FlexIndex;
/**
* Class GroupIndex
* @package Grav\Common\User\FlexUser
*
* @method bool|null authorize(string $action, string $scope = null)
*/
class UserGroupIndex extends FlexIndex
{
}

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\UserGroups;
use Grav\Common\User\Access;
use Grav\Common\User\Interfaces\UserGroupInterface;
use Grav\Framework\Flex\FlexObject;
/**
* Flex User Group
*
* @package Grav\Common\User
*
* @property string $groupname
* @property Access $access
*/
class UserGroupObject extends FlexObject implements UserGroupInterface
{
/** @var Access|null */
protected $_access;
/** @var array|null */
protected $access;
/**
* @return array
*/
public static function getCachedMethods(): array
{
return [
'authorize' => 'session',
] + parent::getCachedMethods();
}
/**
* Checks user authorization to the action.
*
* @param string $action
* @param string|null $scope
* @return bool|null
*/
public function authorize(string $action, string $scope = null): ?bool
{
if ($scope === 'test') {
$scope = null;
} else {
if (!$this->getProperty('enabled', true)) {
return null;
}
}
$access = $this->getAccess();
$authorized = $access->authorize($action, $scope);
if (is_bool($authorized)) {
return $authorized;
}
return $access->authorize('admin.super') ? true : null;
}
/**
* @return Access
*/
protected function getAccess(): Access
{
if (null === $this->_access) {
$this->getProperty('access');
}
return $this->_access;
}
/**
* @param mixed $value
* @return array
*/
protected function offsetLoad_access($value): array
{
if (!$value instanceof Access) {
$value = new Access($value);
}
$this->_access = $value;
return $value->jsonSerialize();
}
/**
* @param mixed $value
* @return array
*/
protected function offsetPrepare_access($value): array
{
return $this->offsetLoad_access($value);
}
/**
* @param array|null $value
* @return array|null
*/
protected function offsetSerialize_access(?array $value): ?array
{
return $value;
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Users\Storage;
use Grav\Framework\Flex\Storage\FileStorage;
class UserFileStorage extends FileStorage
{
/**
* {@inheritdoc}
* @see FlexStorageInterface::getMediaPath()
*/
public function getMediaPath(string $key = null): ?string
{
// There is no media support for file storage (fallback to common location).
return null;
}
/**
* Prepares the row for saving and returns the storage key for the record.
*
* @param array $row
*/
protected function prepareRow(array &$row): void
{
parent::prepareRow($row);
$access = $row['access'] ?? [];
unset($row['access']);
if ($access) {
$row['access'] = $access;
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Flex\Users\Storage;
use Grav\Framework\Flex\Storage\FolderStorage;
class UserFolderStorage extends FolderStorage
{
/**
* Prepares the row for saving and returns the storage key for the record.
*
* @param array $row
*/
protected function prepareRow(array &$row): void
{
parent::prepareRow($row);
$access = $row['access'] ?? [];
unset($row['access']);
if ($access) {
$row['access'] = $access;
}
}
}

View File

@@ -1,28 +1,39 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\User
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\User\FlexUser;
namespace Grav\Common\Flex\Users;
use Grav\Common\User\Interfaces\UserCollectionInterface;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\Utils;
use Grav\Framework\Flex\FlexCollection;
class UserCollection extends FlexCollection implements UserCollectionInterface
{
/**
* @return array
*/
public static function getCachedMethods(): array
{
return [
'authorize' => 'session',
] + parent::getCachedMethods();
}
/**
* Load user account.
*
* Always creates user object. To check if user exists, use $this->exists().
*
* @param string $username
* @return User
* @return UserObject
*/
public function load($username): UserInterface
{
@@ -38,7 +49,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
$directory = $this->getFlexDirectory();
/** @var User $object */
/** @var UserObject $object */
$object = $directory->createObject(
[
'username' => $username,
@@ -54,25 +65,27 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
* Find a user by username, email, etc
*
* @param string $query the query to search for
* @param array $fields the fields to search
* @return User
* @param string|string[] $fields the fields to search
* @return UserObject
*/
public function find($query, $fields = ['username', 'email']): UserInterface
{
foreach ((array)$fields as $field) {
if ($field === 'key') {
$user = $this->get($query);
} elseif ($field === 'storage_key') {
$user = $this->withKeyField('storage_key')->get($query);
} elseif ($field === 'flex_key') {
$user = $this->withKeyField('flex_key')->get($query);
} elseif ($field === 'username') {
$user = $this->get(mb_strtolower($query));
} else {
$user = parent::find($query, $field);
}
if ($user) {
return $user;
if (is_string($query) && $query !== '') {
foreach ((array)$fields as $field) {
if ($field === 'key') {
$user = $this->get($query);
} elseif ($field === 'storage_key') {
$user = $this->withKeyField('storage_key')->get($query);
} elseif ($field === 'flex_key') {
$user = $this->withKeyField('flex_key')->get($query);
} elseif ($field === 'username') {
$user = $this->get(mb_strtolower($query));
} else {
$user = parent::find($query, $field);
}
if ($user) {
return $user;
}
}
}
@@ -83,7 +96,6 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
* Delete user account.
*
* @param string $username
*
* @return bool True if user account was found and was deleted.
*/
public function delete($username): bool

View File

@@ -1,13 +1,15 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\User
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\User\FlexUser;
namespace Grav\Common\Flex\Users;
use Grav\Common\Debugger;
use Grav\Common\File\CompiledYamlFile;
@@ -23,7 +25,7 @@ class UserIndex extends FlexIndex
* @param FlexStorageInterface $storage
* @return array
*/
public static function loadEntriesFromStorage(FlexStorageInterface $storage) : array
public static function loadEntriesFromStorage(FlexStorageInterface $storage): array
{
// Load saved index.
$index = static::loadEntriesFromIndex($storage);
@@ -40,8 +42,7 @@ class UserIndex extends FlexIndex
* Always creates user object. To check if user exists, use $this->exists().
*
* @param string $username
*
* @return User
* @return UserObject
*/
public function load($username): UserInterface
{
@@ -57,7 +58,7 @@ class UserIndex extends FlexIndex
$directory = $this->getFlexDirectory();
/** @var User $object */
/** @var UserObject $object */
$object = $directory->createObject(
[
'username' => $username,
@@ -74,38 +75,48 @@ class UserIndex extends FlexIndex
*
* @param string $query the query to search for
* @param array $fields the fields to search
* @return User
* @return UserObject
*/
public function find($query, $fields = ['username', 'email']): UserInterface
{
foreach ((array)$fields as $field) {
if ($field === 'key') {
$user = $this->get($query);
} elseif ($field === 'storage_key') {
$user = $this->withKeyField('storage_key')->get($query);
} elseif ($field === 'flex_key') {
$user = $this->withKeyField('flex_key')->get($query);
} elseif ($field === 'email') {
$user = $this->withKeyField('email')->get($query);
} elseif ($field === 'username') {
$user = $this->get(mb_strtolower($query));
} else {
$user = $this->__call('find', [$query, $field]);
}
if ($user) {
return $user;
if (is_string($query) && $query !== '') {
foreach ((array)$fields as $field) {
if ($field === 'key') {
$user = $this->get($query);
} elseif ($field === 'storage_key') {
$user = $this->withKeyField('storage_key')->get($query);
} elseif ($field === 'flex_key') {
$user = $this->withKeyField('flex_key')->get($query);
} elseif ($field === 'email') {
$user = $this->withKeyField('email')->get($query);
} elseif ($field === 'username') {
$user = $this->get(mb_strtolower($query));
} else {
$user = $this->__call('find', [$query, $field]);
}
if ($user) {
return $user;
}
}
}
return $this->load('');
}
/**
* @param array $entry
* @param array $data
*/
protected static function updateIndexData(array &$entry, array $data)
{
$entry['key'] = mb_strtolower($entry['key']);
$entry['email'] = isset($data['email']) ? mb_strtolower($data['email']) : null;
}
/**
* @param FlexStorageInterface $storage
* @return CompiledYamlFile|null
*/
protected static function getIndexFile(FlexStorageInterface $storage)
{
// Load saved index file.
@@ -116,6 +127,12 @@ class UserIndex extends FlexIndex
return CompiledYamlFile::instance($filename);
}
/**
* @param array $entries
* @param array $added
* @param array $updated
* @param array $removed
*/
protected static function onChanges(array $entries, array $added, array $updated, array $removed)
{
$message = sprintf('Flex: User index updated, %d objects (%d added, %d updated, %d removed).', \count($entries), \count($added), \count($updated), \count($removed));

View File

@@ -1,34 +1,42 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Common\User
* @package Grav\Common\Flex
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\User\FlexUser;
namespace Grav\Common\Flex\Users;
use Grav\Common\Data\Blueprint;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Page\Media;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Medium\MediumFactory;
use Grav\Common\Page\Medium\StaticImageMedium;
use Grav\Common\User\Access;
use Grav\Common\User\Authentication;
use Grav\Common\Flex\UserGroups\UserGroupCollection;
use Grav\Common\Flex\UserGroups\UserGroupIndex;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\User\Traits\UserTrait;
use Grav\Common\Utils;
use Grav\Framework\File\Formatter\JsonFormatter;
use Grav\Framework\File\Formatter\YamlFormatter;
use Grav\Framework\Flex\Flex;
use Grav\Framework\Flex\FlexDirectory;
use Grav\Framework\Flex\FlexObject;
use Grav\Framework\Flex\Storage\FileStorage;
use Grav\Framework\Flex\Traits\FlexAuthorizeTrait;
use Grav\Framework\Flex\Traits\FlexMediaTrait;
use Grav\Framework\Form\FormFlashFile;
use Grav\Framework\Media\Interfaces\MediaManipulationInterface;
use Psr\Http\Message\UploadedFileInterface;
use RocketTheme\Toolbox\File\FileInterface;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* Flex User
@@ -49,25 +57,36 @@ use RocketTheme\Toolbox\File\FileInterface;
* @property bool $authenticated
* @property bool $authorized
*/
class User extends FlexObject implements UserInterface, MediaManipulationInterface, \Countable
class UserObject extends FlexObject implements UserInterface, MediaManipulationInterface, \Countable
{
use FlexMediaTrait;
use FlexAuthorizeTrait;
use FlexMediaTrait {
getMedia as private getFlexMedia;
getMediaFolder as private getFlexMediaFolder;
}
use UserTrait;
/** @var array|null */
protected $_uploads_original;
/**
* @var FileInterface|null
*/
/** @var FileInterface|null */
protected $_storage;
/** @var UserGroupCollection|UserGroupIndex|null */
protected $_groups;
/** @var Access|null */
protected $_access;
/** @var array|null */
protected $access;
/**
* @return array
*/
public static function getCachedMethods(): array
{
return [
'authorize' => 'session',
'load' => false,
'find' => false,
'remove' => false,
@@ -78,6 +97,13 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
] + parent::getCachedMethods();
}
/**
* UserObject constructor.
* @param array $elements
* @param string $key
* @param FlexDirectory $directory
* @param bool $validate
*/
public function __construct(array $elements, $key, FlexDirectory $directory, bool $validate = false)
{
// User can only be authenticated via login.
@@ -155,6 +181,56 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
return $this;
}
/**
* Checks user authorization to the action.
*
* @param string $action
* @param string|null $scope
* @return bool|null
*/
public function authorize(string $action, string $scope = null): ?bool
{
if ($scope === 'test') {
// Special scope to test user permissions.
$scope = null;
} else {
// User needs to be enabled.
if ($this->getProperty('state') !== 'enabled') {
return false;
}
// User needs to be logged in.
if (!$this->getProperty('authenticated')) {
return false;
}
if (strpos($action, 'login') === false && !$this->getProperty('authorized')) {
// User needs to be authorized (2FA).
return false;
}
// Workaround bug in Login::isUserAuthorizedForPage() <= Login v3.0.4
if ((string)(int)$action === $action) {
return false;
}
}
// Check user access.
$access = $this->getAccess();
$authorized = $access->authorize($action, $scope);
if (is_bool($authorized)) {
return $authorized;
}
// If specific rule isn't hit, check if user is super user.
if ($access->authorize('admin.super') === true) {
return true;
}
// Check group access.
return $this->getGroups()->authorize($action, $scope);
}
/**
* Get value from a page variable (used mostly for creating edit forms).
*
@@ -214,7 +290,6 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
*
* @param int $inline The level where you switch to inline YAML.
* @param int $indent The amount of spaces to use for indentation of nested nodes.
*
* @return string A YAML string representing the object.
*/
public function toYaml($inline = 5, $indent = 2)
@@ -296,7 +371,7 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
$old = $this->get($name, null, $separator);
if ($old !== null) {
$value = $this->getBlueprint()->mergeData($value, $old, $name, $separator);
$value = $this->getBlueprint()->mergeData($value, $old, $name, $separator ?? '.');
}
$this->setNestedProperty($name, $value, $separator);
@@ -333,7 +408,7 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
}
// Return joined data.
return $this->getBlueprint()->mergeData($old, $value, $name, $separator);
return $this->getBlueprint()->mergeData($old, $value, $name, $separator ?? '.');
}
/**
@@ -383,31 +458,6 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
return $this->getBlueprint()->extra($this->toArray());
}
/**
* @param string $name
* @return Blueprint
*/
public function getBlueprint(string $name = '')
{
$blueprint = clone parent::getBlueprint($name);
$blueprint->addDynamicHandler('flex', function (array &$field, $property, array &$call) {
$params = (array)$call['params'];
$method = array_shift($params);
if (method_exists($this, $method)) {
$value = $this->{$method}(...$params);
if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
$field[$property] = array_merge_recursive($field[$property], $value);
} else {
$field[$property] = $value;
}
}
});
return $blueprint->init();
}
/**
* Return unmodified data as raw string.
*
@@ -425,8 +475,8 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
/**
* Set or get the data storage.
*
* @param FileInterface $storage Optionally enter a new storage.
* @return FileInterface
* @param FileInterface|null $storage Optionally enter a new storage.
* @return FileInterface|null
*/
public function file(FileInterface $storage = null)
{
@@ -437,6 +487,9 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
return $this->_storage;
}
/**
* @return bool
*/
public function isValid(): bool
{
return $this->getProperty('state') !== null;
@@ -467,18 +520,21 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
return parent::save();
}
public function isAuthorized(string $action, string $scope = null, UserInterface $user = null): bool
/**
* @param UserInterface $user
* @param string $action
* @param string $scope
* @param bool $isMe
* @return bool|null
*/
protected function isAuthorizedOverride(UserInterface $user, string $action, string $scope, bool $isMe = false): ?bool
{
if (null === $user) {
/** @var UserInterface $user */
$user = Grav::instance()['user'] ?? null;
if ($user instanceof self && $user->getStorageKey() === $this->getStorageKey()) {
// User cannot delete his own account, otherwise he has full access.
return $action !== 'delete';
}
if ($user instanceof User && $user->getStorageKey() === $this->getStorageKey()) {
return true;
}
return parent::isAuthorized($action, $scope, $user);
return parent::isAuthorizedOverride($user, $action, $scope, $isMe);
}
/**
@@ -513,7 +569,7 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
/**
* Return media object for the User's avatar.
*
* @return ImageMedium|null
* @return ImageMedium|StaticImageMedium|null
* @deprecated 1.6 Use ->getAvatarImage() method instead.
*/
public function getAvatarMedia()
@@ -548,7 +604,7 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->authorize() method instead', E_USER_DEPRECATED);
return $this->authorize($action);
return $this->authorize($action) ?? false;
}
/**
@@ -564,6 +620,117 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
return \count($this->jsonSerialize());
}
/**
* @return UserGroupIndex|UserGroupCollection
*/
protected function getGroups()
{
if (null === $this->_groups) {
$this->_groups = $this->getUserGroups()->select((array)$this->getProperty('groups'));
}
return $this->_groups;
}
/**
* @return Access
*/
protected function getAccess(): Access
{
if (null === $this->_access) {
$this->getProperty('access');
}
return $this->_access;
}
/**
* @param mixed $value
* @return array
*/
protected function offsetLoad_access($value): array
{
if (!$value instanceof Access) {
$value = new Access($value);
}
$this->_access = $value;
return $value->jsonSerialize();
}
/**
* @param mixed $value
* @return array
*/
protected function offsetPrepare_access($value): array
{
return $this->offsetLoad_access($value);
}
/**
* @param array|null $value
* @return array|null
*/
protected function offsetSerialize_access(?array $value): ?array
{
return $value;
}
/**
* @return MediaCollectionInterface
*/
public function getMedia()
{
/** @var Media $media */
$media = $this->getFlexMedia();
// Deal with shared avatar folder.
$path = $this->getAvatarFile();
if ($path && !$media[$path] && is_file($path)) {
$medium = MediumFactory::fromFile($path);
if ($medium) {
$media->add($path, $medium);
$name = basename($path);
if ($name !== $path) {
$media->add($name, $medium);
}
}
}
return $media;
}
/**
* @return string|null
*/
public function getMediaFolder(): ?string
{
$folder = $this->getFlexMediaFolder();
if (!$folder) {
// Shared media!
$this->_loadMedia = false;
$folder = $this->getBlueprint()->fields()['avatar']['destination'] ?? 'user://accounts/avatars';
}
return $folder;
}
/**
* @return string|null
*/
protected function getAvatarFile(): ?string
{
$avatars = $this->getElement('avatar');
if (\is_array($avatars) && $avatars) {
$avatar = array_shift($avatars);
return $avatar['path'] ?? null;
}
return null;
}
/**
* Gets the associated media collection (original images).
*
@@ -571,7 +738,12 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
*/
protected function getOriginalMedia()
{
return (new Media($this->getMediaFolder() . '/original', $this->getMediaOrder()))->setTimestamps();
$folder = $this->getMediaFolder();
if ($folder) {
$folder .= '/original';
}
return (new Media($folder ?? '', $this->getMediaOrder()))->setTimestamps();
}
/**
@@ -579,11 +751,46 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
*/
protected function setUpdatedMedia(array $files): void
{
// For shared media folder we need to keep path for backwards compatibility.
$folder = $this->getMediaFolder();
if (!$folder) {
throw new \RuntimeException('No media folder support');
}
$list = [];
$list_original = [];
foreach ($files as $field => $group) {
foreach ($group as $filename => $file) {
if (strpos($field, '/original')) {
if ($file) {
$filename = $file->getClientFilename();
/** @var FormFlashFile $file */
$data = $file->jsonSerialize();
unset($data['tmp_name'], $data['path']);
} else {
$data = null;
}
$settings = $this->getBlueprint()->schema()->getProperty($field);
// Generate random name if required
if ($settings['random_name'] ?? false) {
$extension = pathinfo($filename, PATHINFO_EXTENSION);
$data['name'] = $filename = Utils::generateRandomString(15) . '.' . $extension;
}
if ($this->_loadMedia) {
$filepath = $filename;
} else {
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$filepath = $locator->findResource($folder, false, true) . '/' . $filename;
if ($data) {
$data['path'] = $filepath;
}
}
if ($this->_loadMedia && strpos($field, '/original')) {
// Special handling for original images.
$list_original[$filename] = $file;
continue;
@@ -591,15 +798,10 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
$list[$filename] = $file;
if ($file) {
/** @var FormFlashFile $file */
$data = $file->jsonSerialize();
$path = $file->getClientFilename();
unset($data['tmp_name'], $data['path']);
$this->setNestedProperty("{$field}\n{$path}", $data, "\n");
if ($data) {
$this->setNestedProperty("{$field}\n{$filepath}", $data, "\n");
} else {
$this->unsetNestedProperty("{$field}\n{$filename}", "\n");
$this->unsetNestedProperty("{$field}\n{$filepath}", "\n");
}
}
}
@@ -611,7 +813,7 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
protected function saveUpdatedMedia(): void
{
// Upload/delete original sized images.
/** @var FormFlashFile $file */
/** @var FormFlashFile|null $file */
foreach ($this->_uploads_original ?? [] as $name => $file) {
$name = 'original/' . $name;
if ($file) {
@@ -623,7 +825,7 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
/**
* @var string $filename
* @var UploadedFileInterface $file
* @var UploadedFileInterface|null $file
*/
foreach ($this->getUpdatedMedia() as $filename => $file) {
if ($file) {
@@ -655,13 +857,13 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
continue;
}
/** @var Medium $thumbFile */
/** @var Medium|null $thumbFile */
$thumbFile = $resizedMedia[$filename];
/** @var Medium $imageFile */
/** @var Medium|null $imageFile */
$imageFile = $originalMedia[$filename] ?? $thumbFile;
if ($thumbFile) {
if ($thumbFile && $imageFile) {
$list[$filename] = [
'name' => $filename,
'name' => $info['name'],
'type' => $info['type'],
'size' => $info['size'],
'image_url' => $imageFile->url(),
@@ -680,31 +882,32 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
protected function doSerialize(): array
{
return [
'type' => 'accounts',
'type' => $this->getFlexType(),
'key' => $this->getKey(),
'elements' => $this->jsonSerialize(),
'storage' => $this->getStorage()
'storage' => $this->getMetaData()
];
}
/**
* @param array $serialized
* @return UserGroupCollection|UserGroupIndex
*/
protected function doUnserialize(array $serialized): void
protected function getUserGroups()
{
$grav = Grav::instance();
/** @var UserCollection $accounts */
$accounts = $grav['accounts'];
/** @var Flex $flex */
$flex = $grav['flex'];
$directory = $accounts->getFlexDirectory();
if (!$directory) {
throw new \InvalidArgumentException('Internal error');
/** @var UserGroupCollection|null $groups */
$groups = $flex->getDirectory('user-groups');
if ($groups) {
/** @var UserGroupIndex $index */
$index = $groups->getIndex();
return $index;
}
$this->setFlexDirectory($directory);
$this->setStorage($serialized['storage']);
$this->setKey($serialized['key']);
$this->setElements($serialized['elements']);
return $grav['user_groups'];
}
}

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\Form
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,11 +13,17 @@ use Grav\Common\Iterator;
abstract class AbstractCollection extends Iterator
{
/**
* @return string
*/
public function toJson()
{
return json_encode($this->toArray());
}
/**
* @return array
*/
public function toArray()
{
$items = [];

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,8 +13,12 @@ use Grav\Common\Iterator;
abstract class AbstractPackageCollection extends Iterator
{
/** @var string */
protected $type;
/**
* @return string
*/
public function toJson()
{
$items = [];
@@ -26,6 +30,9 @@ abstract class AbstractPackageCollection extends Iterator
return json_encode($items);
}
/**
* @return array
*/
public function toArray()
{
$items = [];

View File

@@ -3,7 +3,7 @@
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
@@ -13,8 +13,14 @@ use Grav\Common\Iterator;
class CachedCollection extends Iterator
{
protected static $cache;
/** @var array */
protected static $cache = [];
/**
* CachedCollection constructor.
*
* @param array $items
*/
public function __construct($items)
{
parent::__construct();

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