Compare commits

...

504 Commits

Author SHA1 Message Date
Andy Miller
1fb6a39d9d prepare for rc.19 release 2020-12-03 17:12:55 -07:00
Andy Miller
9aeac93c9a composer update 2020-12-03 16:10:54 -07:00
Andy Miller
6188076fe6 prepare for rc.18 release 2020-12-03 14:31:25 -07:00
Andy Miller
0fc7fd3411 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
2020-12-03 14:27:38 -07:00
Andy Miller
8c845c77c1 Merge tag '1.6.29' into develop
Release v1.6.29
2020-12-03 14:22:52 -07:00
Andy Miller
078c8d23d5 Merge branch 'release/1.6.29' 2020-12-03 14:22:52 -07:00
Andy Miller
cb373dae59 prepare for release 2020-12-03 14:22:38 -07:00
Matias Griese
0a42a889ec Sort by numeric in Cron::cronToArray() 2020-12-02 21:51:34 +02:00
Matias Griese
e8f5080f35 Updated Gregwar to our own version 2020-12-02 16:09:10 +02:00
Matias Griese
00f73957dd Fixed doctrine collection sorting to be natural and case insensitive 2020-12-02 10:56:18 +02:00
Matias Griese
3a0480e0d8 Fixed plugin/theme priority ordering to be numeric 2020-12-02 10:45:11 +02:00
Matias Griese
0cee7bcdc8 Fixed Page ordering to be natural and case insensitive 2020-12-02 10:41:10 +02:00
Matias Griese
30401df4b7 Fixed Flex ordering to be natural and case insensitive 2020-12-02 10:37:51 +02:00
Matias Griese
f5d1e98491 Use previous version of Symfony YAML because of a bug 2020-12-01 11:57:08 +02:00
Matias Griese
8f9b2d22c1 Fixed event timeline in clockwork 2020-12-01 10:33:37 +02:00
Matias Griese
2b17767b53 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	system/src/Grav/Common/Backup/Backups.php
2020-12-01 09:35:35 +02:00
Matias Griese
00a7094802 Update getBackupDownloadUrl() method to not pass the path 2020-12-01 09:33:55 +02:00
Andy Miller
6eaf44e397 Merge branch 'develop' into 1.7 2020-11-30 16:14:01 -07:00
Andy Miller
24b52c77fe update changelog 2020-11-30 16:13:51 -07:00
Andy Miller
7a9b906925 update changelog 2020-11-30 16:13:25 -07:00
Andy Miller
53bef264e7 XSS fix for grav-ghsa-cvmr-6428-87w9 2020-11-30 16:11:22 -07:00
Andy Miller
75b74c4ab3 XSS fix for grav-ghsa-cvmr-6428-87w9 2020-11-30 16:10:56 -07:00
Matias Griese
ca3a9aecd6 Do not create the new user/env folder if environment already exists elsewhere 2020-11-30 18:21:13 +02:00
Matias Griese
8f7902af94 Installer improvements 2020-11-30 16:13:24 +02:00
Andy Miller
8df3fa31fb updated with new toolbox 2020-11-27 13:53:33 -07:00
Matias Griese
ff7a1b861e Flex improvements 2020-11-27 14:56:50 +02:00
Matias Griese
d6a82e5c56 Fixed minor installer issues 2020-11-27 14:49:47 +02:00
Matias Griese
f714e6fa14 PHP 8 fixes (part 2) 2020-11-27 14:13:59 +02:00
Matias Griese
6817133819 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	composer.json
	composer.lock
2020-11-27 14:01:56 +02:00
Matias Griese
76670e47a1 PHP 8 fixes 2020-11-27 13:49:10 +02:00
Matias Griese
961b24a019 Fixed exception in CLI GPM and backup commands when php-zip is not enabled [#3075] 2020-11-24 19:52:11 +02:00
Matias Griese
068de42e83 Fixed |safe_email filter to return safe and escaped UTF-8 HTML [#3072] 2020-11-24 19:36:35 +02:00
Matias Griese
75f9c0a892 Fixed bin/grav yamllinter -a and -f not following symlinks [#3080] 2020-11-24 16:42:00 +02:00
Matias Griese
3a1b301b32 FlexObject::refresh() should return boolean status 2020-11-24 14:52:52 +02:00
Matias Griese
9bb1d99ae4 Added FlexObject::refresh() method to make sure object is up to date 2020-11-24 14:48:12 +02:00
Matias Griese
cf052b5bd4 Updated Clockwork to v5.0 [#3072] 2020-11-24 11:47:31 +02:00
Matias Griese
65a18fd270 Update user/config/version.yaml before copying the files to avoid frontend from setting the version schema. 2020-11-23 22:30:13 +02:00
Matias Griese
d4775cc970 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	system/src/Grav/Common/Processors/ConfigurationProcessor.php
2020-11-23 21:54:07 +02:00
Matias Griese
07ee5b42f7 Added basic support for user/config/versions.yaml 2020-11-23 21:53:28 +02:00
Matias Griese
d5048fea10 Composer update 2020-11-23 21:39:00 +02:00
Matias Griese
f3149068e3 Create version.yaml when initializing configuration 2020-11-23 21:36:54 +02:00
Matias Griese
f5d3a5132a Change version.yaml nesting 2020-11-23 21:00:17 +02:00
Matias Griese
70e0ecfef5 Add version information when upgrading Grav 2020-11-23 20:46:41 +02:00
Matias Griese
631e0e0f3f New sites have compatibility features turned off by default, upgrading from older versions will keep the settings on 2020-11-23 19:09:55 +02:00
Matias Griese
b66287cbb9 Fixed CLI self-upgrade from Grav 1.6 [#3079] 2020-11-23 09:50:30 +02:00
Matias Griese
9e6d38bc1a Added support for having all sites / environments under user/env folder [#3072] 2020-11-23 08:50:19 +02:00
Matias Griese
6cbe4aeec4 Fixed system:// stream is causing issues in Admin, making Media tab to disappear and possibly causing other issues [#3072] 2020-11-23 08:42:17 +02:00
Djamil Legato
e219f56ff4 Merge branch 'develop' into 1.7 2020-11-21 14:08:49 -08:00
Djamil Legato
e16b29c566 Better handling of missing repository index (grav-plugin-admin#1916) 2020-11-21 14:05:29 -08:00
Djamil Legato
1b3e3ede4d Better handling of missing repository index (grav-plugin-admin#1916) 2020-11-20 15:40:58 -08:00
Djamil Legato
0a601f10c0 Fixed Purge successful message only working in Scheduler but broken in CLI and Admin (fixes #1935) 2020-11-20 12:16:37 -08:00
1tsi
56ce4ab0f2 Update media.yaml (#3070)
fixed MIME types for .docx, .pptx and .xlsx
2020-11-18 12:10:23 -07:00
Matias Griese
1f6c2f5a0a Add per field configuration options to customize XSS detection 2020-11-18 13:54:42 +02:00
Andy Miller
5f3ddd9389 Merge branch 'develop' into 1.7 2020-11-17 15:24:54 -07:00
Andy Miller
fd0c9823fa better handle cookie_samesite if null 2020-11-17 15:24:08 -07:00
Andy Miller
d23588a217 Merge branch 'develop' into 1.7
# Conflicts:
#	system/blueprints/config/system.yaml
#	system/src/Grav/Common/GPM/Response.php
2020-11-17 12:07:11 -07:00
Andy Miller
ca53b8afca Merge branch '1.7' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2020-11-17 12:01:56 -07:00
Andy Miller
053bc03a54 fixed blueprintschema for config-default-@ 2020-11-17 12:00:55 -07:00
Matias Griese
78dc70bcdc Turn off xss detection on unset fields 2020-11-17 19:10:13 +02:00
Matias Griese
ea00c044d3 Added XSS detection to all forms (use check_xss: false to disable it per field) 2020-11-17 18:54:03 +02:00
randoum
11cd2b086e Allow to set SameSite from system.yaml (#3063)
* Update system.yaml
* Update SessionServiceProvider.php
* Update Session.php
* Update system.yaml
2020-11-17 13:10:12 +02:00
Stephan Strate
ae6f0b5505 Check exact extension in checkFilename utility (#3061)
* Fix uploads_dangerous_extensions checking (#3060)
* Remove redundant prefixing of `.` to extension (#3060)
2020-11-17 13:07:33 +02:00
Vilius Šumskas
247d1a9aa6 Fix failing example custom job. (#3050)
* Since Symfony 4.2 passing chained shell commands to the Process component is not supported anymore and a working directory needs to be set by passing it as a completely separate parameter.

Unless somebody finds a way to use Process() for this and fixes it in the code, rework example custom job.

Related info: https://symfony.com/blog/new-in-symfony-4-2-important-deprecations#deprecated-process-commands-as-strings

* One more place to fix Symfony 4.2 compatibility.
2020-11-17 13:03:30 +02:00
Djamil Legato
6273c2395d Minor tweak to pages list tree ui 2020-11-16 20:24:09 -08:00
Djamil Legato
1396525251 Escape titles in Flex pages list (flex-objects#84) 2020-11-15 12:00:47 -08:00
Andy Miller
de46afff1c vendor library updates 2020-11-13 16:02:52 -07:00
Andy Miller
54fd54d3f0 Set minimum requirements to PHP 7.3.6 2020-11-13 16:01:26 -07:00
randoum
0b41eea2bb Allow to set SameSite from system.yaml (#3063)
* Update system.yaml

* Update SessionServiceProvider.php

* Update Session.php

* Update system.yaml
2020-11-12 13:40:39 -07:00
Matias Griese
63207d370b Fixed updated media missing from media when editing Flex Object after page reload 2020-11-12 21:19:45 +02:00
Matias Griese
e92d88df8b Fixed fatal error in CompiledFile if the cached version is broken 2020-11-12 13:35:36 +02:00
Matias Griese
a9e6d3665f Fixed potential fatal error when creating flex index using cache [#3062] 2020-11-12 13:26:25 +02:00
Matias Griese
2453927ba3 Fixed Utils::isFunctionDisabled() method if there are spaces in disable_functions [#3023] 2020-11-12 13:14:17 +02:00
Matias Griese
94e6cb02f3 Fixed header.admin.children_display_order in Flex Pages to work just like with regular pages 2020-11-12 12:58:26 +02:00
Stephan Strate
9b2b909139 Check exact extension in checkFilename utility (#3061)
* Fix uploads_dangerous_extensions checking (#3060)

* Remove redundant prefixing of `.` to extension (#3060)
2020-11-11 10:30:57 -07:00
Matias Griese
c9a3f349ad Merge remote-tracking branch 'origin/1.7' into 1.7 2020-11-11 14:51:56 +02:00
Matias Griese
439b539dcb Added FormFlashFile::getField() method 2020-11-11 14:51:48 +02:00
Andy Miller
ab7f0a2e95 Address errors in CLI when accidentally initializing twice 2020-11-09 14:25:04 -07:00
Matias Griese
b50dbf3ff0 Fixed sorting by groups in Flex Users 2020-11-09 20:44:37 +02:00
Matias Griese
d3f1f833ab Fixed sorting by groups in Flex Users 2020-11-09 19:47:00 +02:00
Matias Griese
3df099a0e3 Fixed typo in composer.json 2020-11-05 10:45:15 +02:00
Vilius Šumskas
54dccd11ef Fix failing example custom job. (#3050)
* Since Symfony 4.2 passing chained shell commands to the Process component is not supported anymore and a working directory needs to be set by passing it as a completely separate parameter.

Unless somebody finds a way to use Process() for this and fixes it in the code, rework example custom job.

Related info: https://symfony.com/blog/new-in-symfony-4-2-important-deprecations#deprecated-process-commands-as-strings

* One more place to fix Symfony 4.2 compatibility.
2020-11-04 15:40:32 -07:00
Matias Griese
77ec847e20 Tighten requirements (require PHP 8 support for libraries) 2020-11-04 18:31:04 +02:00
Matias Griese
8bf5fcb8ec Improve Flex configuration: gather views together in blueprint 2020-11-04 18:30:15 +02:00
Matias Griese
ff41d76501 Added search option same_as to Flex Objects 2020-11-03 09:11:58 +02:00
Matias Griese
d9772ed5c6 Hide Flex Pages frontend configuration (not ready for production use) 2020-11-02 15:52:27 +02:00
Matias Griese
8dba1b37c7 Update composer 2020-11-02 15:44:45 +02:00
Matias Griese
8b017dc647 Update composer 2020-10-30 22:17:14 +02:00
Matias Griese
c8ee05d671 Set minimum requirements to PHP 7.2.5 2020-10-30 21:52:04 +02:00
Djamil Legato
40216e310d Create SECURITY.md
Added security policy
2020-10-30 12:03:34 -07:00
Grant
6a6f99e9ae Add option for timeout in selfupgrade command (#3013)
* Add option for timeout in selfupgrade command

* Raise the default timeout
2020-10-30 07:48:03 -06:00
Djamil Legato
14ad7cf3ac Set grav_cli as referer when coming from CLI 2020-10-29 15:58:32 -07:00
Djamil Legato
ce327dca42 Set grav_cli as referer when coming from CLI 2020-10-29 15:56:20 -07:00
Djamil Legato
ded7670ac3 Forward a sid to GPM when downloading a premium package via CLI 2020-10-29 15:49:36 -07:00
Djamil Legato
5bf1ec0fd9 Forward a sid to GPM when downloading a premium package via CLI 2020-10-29 15:48:34 -07:00
Matias Griese
306c0505a6 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	CHANGELOG.md
2020-10-29 11:10:33 +02:00
Matias Griese
325cb69a65 Merge remote-tracking branch 'origin/develop' into develop 2020-10-29 11:03:37 +02:00
Matias Griese
c3df9b6484 Added .htaccess rule to block attempts to use Twig in the request URL 2020-10-29 11:03:28 +02:00
Andy Miller
db6bfc00fc Upgraded bin/composer.phar to composer 2 2020-10-26 11:37:24 -06:00
Andy Miller
51b5d7a939 Fix compatibility with Symfony 4.2 and up. (#3048) 2020-10-26 11:34:52 -06:00
Matias Griese
9490f62dee Composer update 2020-10-26 14:58:49 +02:00
Vilius Šumskas
1661dc9ef7 Fix compatibility with Symfony 4.2 and up. (#3048)
This enables running scheduled *shell* commands again because Symfony 4.2 deprecated passing commands as a string to the Process component: https://symfony.com/blog/new-in-symfony-4-2-important-deprecations#deprecated-process-commands-as-strings

One also needs to catch all possible exceptions from the Process component, though. These exceptions are never displayed or sent to any of the logs and I've spent hours trying to debug why my scheduled tasks are failing.
2020-10-23 14:40:35 -06:00
Matias Griese
aa7731dc5d Merge remote-tracking branch 'origin/1.7' into 1.7 2020-10-23 14:45:24 +03:00
Matias Griese
4150564538 Added FlexDirectoryInterface interface 2020-10-23 14:45:16 +03:00
Andy Miller
38043ebade Merge branch 'develop' of github.com:getgrav/grav into develop
# Conflicts:
#	CHANGELOG.md
2020-10-18 15:10:01 -06:00
Andy Miller
9a694a8d3d updated jquery 3.x 2020-10-18 15:08:59 -06:00
Andy Miller
f6997f54eb Merge branch '1.7' of github.com:getgrav/grav into 1.7 2020-10-18 15:07:19 -06:00
Andy Miller
2631c18484 updated jquery 2020-10-18 15:07:13 -06:00
Matias Griese
c0d819b97a Fixed print_r() in twig 2020-10-18 20:17:17 +03:00
Matias Griese
972d32c969 Added deprecation notice when using system.pages_fallback_only 2020-10-16 12:29:37 +03:00
Matias Griese
7f0e51f92f Minor fix to upgrade guide 2020-10-15 13:41:50 +03:00
Matias Griese
6f78c2288f Added missing languages.content_fallback configuration variable to system.yaml 2020-10-15 11:12:30 +03:00
Matias Griese
b331ba42b8 Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	CHANGELOG.md
2020-10-14 11:02:46 +03:00
Matias Griese
fb3efba204 Fixed hardcoded system folder in blueprints, config and language streams 2020-10-14 11:01:45 +03:00
Matias Griese
58e09dcff4 Composer update 2020-10-14 10:38:13 +03:00
Matias Griese
747055f60e Fixed hardcoded system folder in blueprints, config and language streams 2020-10-14 10:37:59 +03:00
Matias Griese
aa07c64440 Missing method 2020-10-14 10:37:45 +03:00
Matias Griese
6b9f1d8414 Fixed bug in collections where filter type: false did not work 2020-10-13 20:54:20 +03:00
Matias Griese
b4bc1d292a Tweak pages collection filters 2020-10-13 20:29:09 +03:00
Matias Griese
c98553d19b Missed few cases (deprecated methods) 2020-10-13 19:18:10 +03:00
Matias Griese
1ace31216f Renamed PageCollectionInterface::nonModular() into PageCollectionInterface::pages() and deprecate the old method
Renamed `PageCollectionInterface::modular()` into `PageCollectionInterface::modules()` and deprecate the old method
2020-10-13 19:01:43 +03:00
Matias Griese
495cec930c Updated upgrade docs 2020-10-12 17:34:16 +03:00
Andy Miller
44ad0ca1ea Menu Visibility Requires Access setting wrong frontmatter login#265 2020-10-11 14:19:15 -06:00
Matias Griese
4ba8e9e99d Fixed media crashing on a bad image 2020-10-08 22:17:12 +03:00
Matias Griese
1eb85b366d Accessing page with unsupported file extension (jpg, pdf, xsl) will use wrong mime type [#3031] 2020-10-08 13:02:47 +03:00
Andy Miller
3821ae441f prepare for rc release 2020-10-07 17:43:25 -06:00
Andy Miller
b01e5f7366 Merge branch 'develop' into 1.7
# Conflicts:
#	system/defines.php
#	system/src/Grav/Common/Helpers/Excerpts.php
2020-10-07 17:41:13 -06:00
Andy Miller
b375a543ec Merge tag '1.6.28' into develop
Release v1.6.28
2020-10-07 17:26:16 -06:00
Andy Miller
2c9d848af5 Merge branch 'release/1.6.28' 2020-10-07 17:26:15 -06:00
Andy Miller
8d65c5c2c0 prepare for release 2020-10-07 17:25:32 -06:00
Matias Griese
0af731b6a2 Fixed bug in FlexMediaTrait::getMediaFieldSettings() 2020-10-07 13:56:19 +03:00
Andy Miller
9d870b2c45 Backported folder::countChildren() from 1.7 2020-10-06 16:06:10 -06:00
Matias Griese
82abf87f75 Improve media rename errors 2020-10-06 13:12:14 +03:00
Matias Griese
81ef023b37 Composer update 2020-10-06 10:23:06 +03:00
Matias Griese
776fffb2dc Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	composer.lock
	system/src/Grav/Common/Grav.php
2020-10-06 10:21:33 +03:00
Andy Miller
cfd5d9e209 improved compatibility 2020-10-05 18:44:59 -06:00
Matias Griese
23716ff729 Do not cache 404 [#3025] 2020-10-05 11:16:17 +03:00
Matias Griese
18921a4f14 Composer update 2020-10-05 10:57:02 +03:00
Andy Miller
569c42724b more docs 2020-10-04 18:28:24 -06:00
Andy Miller
18abf7d644 Caddyfile more friendly for local development 2020-10-04 18:28:18 -06:00
Andy Miller
12efdb9fe4 Merge branch 'develop' into 1.7 2020-10-04 16:36:07 -06:00
Cant_Aim
fdf884036d Update to Caddyfile using Caddy 2 changes. (#2963) 2020-10-04 16:35:37 -06:00
Matias Griese
998018af3e Phpstan fixes 2020-10-02 13:34:51 +03:00
Matias Griese
cae71f3d60 Composer update 2020-10-02 13:24:45 +03:00
Matias Griese
f7e43fab35 Added clearCache() to storages 2020-10-02 13:21:07 +03:00
Matias Griese
4f7a4ac1e2 PhpStan fixes 2020-10-02 13:12:47 +03:00
Matias Griese
5fc7b34ce7 Added reload argument to FlexStorageIngerface::getMetaData() 2020-10-02 12:06:46 +03:00
Andy Miller
cbf73b2290 Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
#	system/src/Grav/Common/GPM/Response.php
#	system/src/Grav/Common/Twig/Node/TwigNodeCache.php
#	system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php
#	system/src/Grav/Common/Twig/TwigExtension.php
#	system/src/Grav/Common/Utils.php
2020-09-30 10:26:51 -06:00
Matias Griese
f344a8166e Changelog update 2020-09-30 14:38:47 +03:00
Matias Griese
9532317928 Fixed fatal error in toggled fields 2020-09-30 14:38:29 +03:00
Matias Griese
4cf85c462d Merge remote-tracking branch 'origin/1.7' into 1.7
# Conflicts:
#	CHANGELOG.md
2020-09-30 14:35:52 +03:00
Matias Griese
6fe2964f37 Flex User: make multiple file/image fields to work 2020-09-30 14:35:29 +03:00
Djamil Legato
2108a902c2 Fixed Referer reference for GPM calls 2020-09-28 17:37:08 -07:00
Andy Miller
1520eec677 Merge branch '1.7' of github.com:getgrav/grav into 1.7 2020-09-28 18:35:06 -06:00
Andy Miller
e3ba3a3ea4 added Uri::getAllHeaders() compatibility 2020-09-28 18:35:01 -06:00
Djamil Legato
1c104f9358 Updated Changelog 2020-09-28 16:38:11 -07:00
Djamil Legato
f8b9536eb8 Fixed Referer reference for GPM calls 2020-09-28 16:37:18 -07:00
Matias Griese
0e34628c6f Flex User: make multiple file/image fields to work 2020-09-25 15:03:15 +03:00
Matias Griese
614bc0b254 Make MediaUploadTrait easier to override 2020-09-25 13:55:46 +03:00
Matias Griese
cc6eafdb09 Fixed Flex User avatar when using folder storage
Fixed bug in `Flex Form` making it impossible to set nested values
2020-09-25 11:08:53 +03:00
Matias Griese
df4cbdcc09 Do not log deprecated messages for now (too much into in clockwork) 2020-09-23 16:18:16 +03:00
Matias Griese
f5e53a9a4c Fixed FlexObject::freeMedia() method causing media to become null 2020-09-23 16:17:25 +03:00
Matias Griese
46f654fcee Fixed unset() in ObjectProperty class 2020-09-23 16:15:39 +03:00
Matias Griese
aa3a3f4a17 Fixed media upload failing with custom folders 2020-09-23 11:52:42 +03:00
Andy Miller
59b3b6cc02 initialize page blueprint first 2020-09-21 15:00:51 -06:00
Matias Griese
6d4b8e8401 Fixed onBlueprintCreated firing multiple times recursively 2020-09-21 23:49:45 +03:00
Matias Griese
9ac7a60835 Make sure that onBlueprintCreated gets called only once 2020-09-21 23:40:08 +03:00
Matias Griese
790dbd381d Added missing onBlueprintCreated event for Flex Pages 2020-09-21 23:20:44 +03:00
Matias Griese
ac6c8a985b Improve MediaUploadTrait 2020-09-21 16:50:22 +03:00
Matias Griese
fea22e0409 Remove typehint from UserObject 2020-09-21 10:40:52 +03:00
Matias Griese
ac7d595a2d Merge branch '1.7' of github.com:getgrav/grav into 1.7
 Conflicts:
	system/src/Grav/Common/Twig/Twig.php
2020-09-21 10:14:04 +03:00
Matias Griese
819e412e09 Improve docblocks in Grav\Common, improve code readability 2020-09-20 22:04:40 +03:00
Jeremy Gonyea
bdfec68340 Custom nginx-ddev-site.conf no longer required (#3011) 2020-09-18 20:43:51 -06:00
Andy Miller
0b7ef6c8fb Merge branch '1.7' of github.com:getgrav/grav into 1.7
# Conflicts:
#	CHANGELOG.md
2020-09-18 12:51:00 -06:00
Andy Miller
9880ce977a Added default templates in Grav itself 2020-09-18 12:49:10 -06:00
Matias Griese
e88f924274 Phpstan fixes 2020-09-18 09:57:56 +03:00
Matias Griese
fe53dc88e5 Improve Media classes 2020-09-18 09:20:27 +03:00
Matias Griese
fdfd6558f2 Composer update 2020-09-18 09:17:28 +03:00
Matias Griese
6d9e3dcad1 Merge branch '1.7' of github.com:getgrav/grav into 1.7 2020-09-15 10:34:51 +03:00
Andy Miller
20c4468edd safety check in Utils::svgImageFunction() 2020-09-11 14:48:50 -06:00
Matias Griese
7f80e650b1 Improve Media classes 2020-09-11 14:13:06 +03:00
Andy Miller
1995837b3f composer udpate 2020-09-10 19:46:30 -06:00
Andy Miller
88c0617279 update changelog 2020-09-10 15:55:33 -06:00
Andy Miller
b8b1bed7ed backported theme_var enhanced logic from 1.7 2020-09-10 15:52:52 -06:00
Andy Miller
03c6e74c4d backported {{ cache }} from 1.7 2020-09-10 15:49:25 -06:00
Andy Miller
71639de5ec Added Utils::fullPath() helper method 2020-09-10 15:43:57 -06:00
Andy Miller
39310cd4af Added svg_image twig function 2020-09-10 15:43:47 -06:00
Matias Griese
582352b2d2 Fixed infinite loop in blueprints with extend@ to a parent stream 2020-09-08 23:15:36 +03:00
Matias Griese
f2c271f66d Fixed Security::sanitizeSVG() creating an empty file if SVG file cannot be parsed 2020-09-08 15:15:25 +03:00
Matias Griese
d99e1f519e Added Filesystem::basename() method 2020-09-07 13:09:07 +03:00
Matias Griese
c53e2843be Minor MediaFileTrait update to support external URLs 2020-09-04 14:24:31 +03:00
Matias Griese
f438ce04fb Merge branch '1.7' of github.com:getgrav/grav into 1.7 2020-09-02 12:41:42 +03:00
Matias Griese
8be5952b92 Upgrade guide update 2020-09-02 10:45:01 +03:00
Andy Miller
cfc8610a16 prepare for rc release 2020-09-01 14:46:52 -06:00
Andy Miller
ad8a5d9870 Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
#	system/defines.php
2020-09-01 14:46:05 -06:00
Andy Miller
75cb4e392d Merge branch 'release/1.6.27' 2020-09-01 14:35:38 -06:00
Andy Miller
ec9dd14cb4 Merge tag '1.6.27' into develop
Release v1.6.27
2020-09-01 14:35:38 -06:00
Andy Miller
6e8c852bfa prepare for release 2020-09-01 14:35:29 -06:00
Andy Miller
f204979ada Merge branch '1.7' of github.com:getgrav/grav into 1.7 2020-08-31 13:58:28 -06:00
Andy Miller
6275327b0a strip <xml> and optionally <style> 2020-08-31 13:58:24 -06:00
Matias Griese
f1126e63b1 Flex: Improve row rename logic 2020-08-31 15:25:02 +03:00
Matias Griese
7157ed08f9 Fixed Flex Pages bug where renaming slug causes bad ordering range after save [#2997] 2020-08-28 21:50:18 +03:00
Matias Griese
d33627f1fe Composer update 2020-08-28 14:26:18 +03:00
Matias Griese
c2611cfeaf Fixed Flex Pages bug where changing a modular page template added duplicate file [admin#1899] 2020-08-28 14:26:07 +03:00
Matias Griese
056614fa6b Fixed Flex Pages bug where onAdminSave passes page as $event['page'] instead of $event['object'] [#2995] 2020-08-28 12:18:02 +03:00
Matias Griese
4c19226959 Include route in Flex Pages edit title 2020-08-28 09:42:41 +03:00
Matias Griese
d41a31f614 Added MediaUploadInterface::renameFile() method 2020-08-28 09:41:50 +03:00
Matias Griese
d83dc07368 Added MediaUploadInterface::renameFile() method 2020-08-28 09:37:44 +03:00
Andy Miller
f30d8748f1 tidy up grav instance reference 2020-08-27 11:02:00 -06:00
Andy Miller
b4710c292f Added themes to cached bluerprints + configs 2020-08-27 11:01:35 -06:00
Andy Miller
c3cd399087 allow null for classes 2020-08-26 14:08:31 -06:00
Andy Miller
f8a2c94903 Added a fullPath() helper method 2020-08-26 14:05:42 -06:00
Andy Miller
747b081809 added svg_image() twig function 2020-08-25 14:47:17 -06:00
Andy Miller
55903dab11 composer updates
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-08-24 11:53:16 -06:00
Andy Miller
a6c094f9aa fix error when no parent exists
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-08-13 11:43:55 -06:00
Andy Miller
b9b978e452 deprecate header_var + enhance theme_var
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-08-12 17:23:27 -06:00
Andy Miller
bad24f8a85 support returning object
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-08-03 20:25:01 -06:00
Andy Miller
57cffbc4c8 support climbing up the page structure looking for a header variable
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-08-03 18:10:13 -06:00
Andy Miller
86d71bf37f Support current page context
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-08-03 18:09:54 -06:00
Matias Griese
c6dd169916 Added MediaUploadInterface::renameFile() method 2020-08-03 16:11:15 +03:00
Matias Griese
cbc85b7a09 Improve medium objects 2020-07-28 17:29:10 +03:00
Matias Griese
016af0f419 Minor improvements for backups 2020-07-28 17:26:55 +03:00
Dominik Scholz-Schulze
1903d44bf8 Fixed enviroment name mapping for localhost (#2924)
Co-authored-by: Dominik Scholz-Schulze <dominik.scholz-schulze@iserv.eu>
2020-07-27 11:11:32 -06:00
Matias Griese
351c270e0e Do not check file size limit when updating object 2020-07-24 11:24:23 +03:00
Matias Griese
ab3d9f89ec Improved media 2020-07-24 11:17:26 +03:00
Andy Miller
97220a27df prepare for rc release
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-22 11:08:23 -06:00
Andy Miller
0310fdf99f Sanity checks
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-21 16:38:02 -06:00
Andy Miller
f1a535b7d4 uppdated changelog
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-21 16:18:24 -06:00
Andy Miller
6879d8b6ce fix for broken exif reading
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-21 14:57:19 -06:00
Matias Griese
046c5f69ac Changelog update 2020-07-21 16:30:37 +03:00
Matias Griese
40687f8cc0 Fixed caching issues with Flex index file 2020-07-21 15:24:54 +03:00
Andy Miller
b8f27ecfd0 sanity check
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-20 23:03:57 -06:00
Andy Miller
16dd2ef9d0 moved fixOrientation() into ImageMediaTrait
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-20 22:47:33 -06:00
Andy Miller
aaff2f486f set default for auto-fix-orientation to true
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-20 19:09:58 -06:00
Andy Miller
d9a62373b8 fix issue with ‘’ set in defaults
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-20 19:09:00 -06:00
Andy Miller
bef4f6a0db Fix orientation when auto_fix_orientation option is enabled
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-20 19:08:45 -06:00
Matias Griese
a66c282ee5 Composer update 2020-07-17 19:41:06 +03:00
Matias Griese
2d50cc2b2d Fixed bug in clearing Flex index (partial fix) 2020-07-17 19:40:57 +03:00
Andy Miller
82f1182503 Prepare for rc release
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-09 10:43:17 -06:00
Andy Miller
360bed8697 Merge branch 'develop' into 1.7 2020-07-09 10:32:02 -06:00
Andy Miller
e4a833b59e cleanup
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-09 10:28:47 -06:00
Matias Griese
f8809d5d62 Fixed missing const CSS_IMPORT_REGEX in BaseAsset 2020-07-09 10:05:33 +03:00
Matias Griese
8adffb8714 Fixed page media only accepting images [#2943] 2020-07-09 09:57:11 +03:00
Andy Miller
9b6649174c fixes complex css imports #2958
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-08 10:39:48 -06:00
Andy Miller
7f03f7c844 Updated changelog
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-07 12:31:01 -06:00
Andy Miller
843596944f Merge branch '1.7' of github.com:getgrav/grav into 1.7 2020-07-07 12:28:14 -06:00
Andy Miller
0f6c5fe49d Asset test compatibility
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-07 12:28:04 -06:00
Ricardo Verdugo
b94c4e775a added optional options to noprocess (#2954)
* added optional options to noprocess

* fix minor bug with skip and better naming

* Added tests

Signed-off-by: Andy Miller <rhuk@mac.com>

* Added some tests

Signed-off-by: Andy Miller <rhuk@mac.com>

Co-authored-by: Ricardo <ricardo@urbansquid.london>
Co-authored-by: Andy Miller <rhuk@mac.com>
2020-07-07 12:27:32 -06:00
torohill
fc97e88928 Left pad scheduler times with zeros. (#2921) 2020-07-07 11:17:15 -06:00
Andy Miller
5b47e6130c update changelog
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-03 21:20:28 -06:00
Andy Miller
86edc18f21 Safer solution for getExcerptsFromHtml
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-03 21:19:39 -06:00
Andy Miller
5ee36e786a UTF-8 safe in Excerpts::getExcerptFromHtml()
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-03 21:18:36 -06:00
Matias Griese
c4e2e5da8d Changelog update 2020-07-02 16:22:45 +03:00
Matias Griese
2c14ec0abd Reverted Language::getDefault() returning false again as plugins use it (updated docblocks) 2020-07-02 16:18:41 +03:00
Matias Griese
5b782fd04c Regression: Default language fix broke Language::getLanguageURLPrefix() and Language::isIncludeDefaultLanguage() methods 2020-07-02 13:58:58 +03:00
Andy Miller
bdd579ad2d prepare for rc release
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-01 14:30:43 -06:00
Andy Miller
d94fa8d2de updated changelog
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-01 14:30:37 -06:00
Andy Miller
c0f1d4e2da Don’t validate, let user’s overriden language work even if no other languages set
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-01 14:04:28 -06:00
Andy Miller
e82f49bdab fall back to ‘en’ by default if no other lang specified
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-07-01 14:03:31 -06:00
Matias Griese
b65d48541d Fixed Language::getDefault() returning false and not null 2020-07-01 20:54:22 +03:00
Matias Griese
e2f68194d7 Docblock fixes in Language 2020-07-01 20:10:43 +03:00
Matias Griese
bd6f8f4ae6 Docblock fixes in Language 2020-07-01 20:09:55 +03:00
Matias Griese
9d4cce93fc Tabs in account blueprint break flex listing 2020-07-01 15:12:09 +03:00
Matias Griese
28ce18f8f0 Improved CvsFormatter to attempt to encode non-scalar variables into JSON before giving up 2020-07-01 14:51:27 +03:00
Matias Griese
c060d1a36e Fixed new User Group allowing bad group name to be saved 2020-07-01 14:30:56 +03:00
Matias Griese
3a05dcf659 Fixed missing onAdminSave and onAdminAfterSave events when using Flex Pages and Flex Users 2020-07-01 14:20:14 +03:00
Matias Griese
291ed38a5a Added page actions for admin 2020-06-30 22:42:19 +03:00
Matias Griese
411d689a48 Fixed broken Flex Page authorization for groups 2020-06-30 21:10:43 +03:00
Matias Griese
df279f879f UserObject: allow incomplete file properties 2020-06-30 14:50:53 +03:00
Matias Griese
9e58bccd49 Documentation updates on Page::topPage() 2020-06-30 10:08:32 +03:00
Matias Griese
092f3b4e24 Fix for FlexPage::topParent() 2020-06-30 01:20:06 +03:00
Matias Griese
005bb77f55 Merge remote-tracking branch 'origin/1.7' into 1.7 2020-06-30 01:09:43 +03:00
Matias Griese
258840d198 Updated UPGRADE-1.7.md 2020-06-30 01:09:34 +03:00
Andy Miller
c483da9efe fix for Page:topParent()
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-29 15:53:53 -06:00
Matias Griese
4b3f202c6f Changelog update 2020-06-29 09:29:57 +03:00
Andy Miller
07571465a7 make sure plugins are properly initialized in CLI
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-28 16:23:03 -06:00
Andy Miller
7d7590b914 Added a new onAfterCacheClear event
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-28 12:54:35 -06:00
Andy Miller
f965961401 udpated bundled composer to 2.0.0-dev
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-27 11:51:49 -06:00
Andy Miller
814ea7f81a composer update
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-27 11:51:37 -06:00
Matias Griese
91a0790695 Fixed retina images not working in Flex [flex-objects 64] 2020-06-26 13:10:24 +03:00
Matias Griese
2b8a6c6a89 Changelog fix 2020-06-26 12:27:33 +03:00
Matias Griese
55648d3452 Changelog update [#1867] 2020-06-26 12:26:09 +03:00
Matias Griese
60316d1b75 Fixed Trying to get property 'username' of non-object error in Flex 2020-06-26 11:54:38 +03:00
Matias Griese
7826608384 Changelog updates 2020-06-25 18:40:30 +03:00
Matias Griese
de151c8a9d Fixed uploading media in non-existing flex object (part 2) 2020-06-25 18:34:31 +03:00
Matias Griese
b0a1effaf9 Fixed uploading media in non-existing object 2020-06-25 15:58:18 +03:00
Matias Griese
fd52d124dd Changelog update 2020-06-23 14:37:46 +03:00
Matias Griese
b772864f2d Merge branch 'feature/1.7-media' of github.com:getgrav/grav into 1.7 2020-06-23 14:35:46 +03:00
Matias Griese
cdb10cfc1f Removed deprecation from a few FlexMediaTrait methods to simplify logic 2020-06-23 12:57:40 +03:00
Matias Griese
da7f0b7dce Fixed MediaUploadTrait::copyUploadedFile() not adding uploaded media to the collection 2020-06-23 12:43:27 +03:00
jackthomasatl
25461f7ca8 Adjusted asset types to enable extension of assets class (#2937)
Co-authored-by: Jack Thomas <Jack.Thomas@elavon.com>
2020-06-22 14:26:03 -06:00
Andy Miller
da7df9f865 needed for cache clear event
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-22 08:57:58 -06:00
Fabien Basmaison
00c5dba210 Use proper ellipsis for summary. (#2939) 2020-06-17 08:28:32 -06:00
Andy Miller
f30219d85a move image loading into it’s own trait to be used in image+static images
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-10 09:58:06 -06:00
Andy Miller
223e75d326 handle non-text links in getExcerptFromHtml
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-10 09:30:20 -06:00
Andy Miller
46d4f4a481 right-trim route for safety
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-09 10:43:21 -06:00
Andy Miller
419ebeafa8 Fix for trailing / in routes
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-08 16:33:55 -06:00
Andy Miller
4be7917d41 prepare for rc release
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-08 15:53:28 -06:00
Andy Miller
191686dffe Merge branch 'develop' into 1.7
Signed-off-by: Andy Miller <rhuk@mac.com>

# Conflicts:
#	system/defines.php
2020-06-08 15:51:08 -06:00
Andy Miller
fa791ed4ab Merge branch 'release/1.6.26' 2020-06-08 15:50:24 -06:00
Andy Miller
e29f8a9657 Merge tag '1.6.26' into develop
Release v1.6.26
2020-06-08 15:50:24 -06:00
Andy Miller
e66f6583b1 prepare for release
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-08 15:49:51 -06:00
Andy Miller
399d90e6cd Merge branch '1.7' into feature/1.7-media 2020-06-08 14:50:27 -06:00
Andy Miller
ec2eaae0d3 Merge branch 'develop' into 1.7
Signed-off-by: Andy Miller <rhuk@mac.com>

# Conflicts:
#	system/src/Grav/Common/GPM/Response.php
2020-06-07 19:47:58 -06:00
Andy Miller
12389b1e0d JSON Route of homepage with no ‘route’ set is valid [form#425]
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-07 19:46:18 -06:00
Florian Körner
ff6e5a20c3 Fix: case-insensitive search of location header (#2932)
looks good!
2020-06-07 15:42:29 -06:00
Andy Miller
7d6526f962 Merge branch '1.7' into feature/1.7-media
Signed-off-by: Andy Miller <rhuk@mac.com>

# Conflicts:
#	CHANGELOG.md
2020-06-07 15:26:10 -06:00
Andy Miller
b8fad8452b JSON route on homepage with no ‘route’ is valid grav-plugin-form#425
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-07 15:23:01 -06:00
Matias Griese
5e9107f376 Merge branch '1.7' of github.com:getgrav/grav into feature/1.7-media
 Conflicts:
	system/src/Grav/Common/Page/Medium/ImageMedium.php
2020-06-05 22:03:53 +03:00
Andy Miller
7faaff304a PSR fix
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-05 12:26:30 -06:00
Matias Griese
94acbf6005 Fixed saving nested file fields in Flex Objects 2020-06-05 10:11:19 +03:00
Andy Miller
a65c21acba Merge branch 'develop' into 1.7
Signed-off-by: Andy Miller <rhuk@mac.com>

# Conflicts:
#	CHANGELOG.md
#	system/blueprints/config/system.yaml
2020-06-04 14:46:54 -06:00
Andy Miller
bfbe4ce1b8 option to control supported attributes in markdown links
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-04 14:42:38 -06:00
Andy Miller
aaa636f357 cleaner handling of cropZoom
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-06-04 14:31:22 -06:00
Matias Griese
d0241ba7ee Changelog update 2020-06-04 22:33:29 +03:00
Matias Griese
b2230225cc Fixed media field saving issues in Flex Pages 2020-06-04 22:01:23 +03:00
Matias Griese
e063718e4f Add media support for Flex UserGroupObject 2020-06-04 16:43:00 +03:00
Matias Griese
03436a670f Add media support for Flex GenericObject 2020-06-04 16:26:40 +03:00
Matias Griese
8be508de5d Fixed typo in class name 2020-06-04 16:16:27 +03:00
Matias Griese
9370a86246 Moved common methods in both Media and GlobalMedia to AbstractMedia 2020-06-04 15:47:28 +03:00
Matias Griese
6c5237cdd8 Added missing methods into GlobalMedia 2020-06-04 15:45:51 +03:00
Matias Griese
2cf5ecd212 Implemented working media fields in FlexMediaTrait 2020-06-04 15:17:52 +03:00
Matias Griese
8e9125772e Add missing functionality to MediaUploadTrait 2020-06-04 12:47:48 +03:00
Matias Griese
62e863dec0 Improve Media handling in Flex 2020-06-03 18:06:37 +03:00
Matias Griese
e00098ea6a Merge branches '1.7' and 'feature/1.7-media' of github.com:getgrav/grav into feature/1.7-media 2020-06-02 13:29:17 +03:00
Matias Griese
f12bd1b51f Added support for case sensitive usernames 2020-06-02 11:57:55 +03:00
Matias Griese
93942a74cf Fixed Flex Object request cache clear when saving object 2020-06-01 13:48:58 +03:00
Matias Griese
401fd04379 Fix minor media bugs 2020-05-29 08:51:27 +03:00
Matias Griese
4fd54f1692 Added MediaUploadTrait 2020-05-28 17:31:49 +03:00
Matias Griese
9565994e5c Move image loading() method into trait 2020-05-28 15:21:03 +03:00
Matias Griese
6876e1a38a Merge branch '1.7' of github.com:getgrav/grav into feature/1.7-media
 Conflicts:
	system/src/Grav/Common/Page/Medium/ImageMedium.php
2020-05-28 15:16:43 +03:00
Matias Griese
7a8e737802 Minor code updates 2020-05-27 13:25:26 +03:00
Andy Miller
9f968611b1 typo
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-26 08:36:40 -06:00
Andy Miller
3bd4f9499a cherry picked #1925 fix from 1.7 to 1.6
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-26 08:35:49 -06:00
Andy Miller
069ef7b12c Fixed system.pages.hide_empty_folders #2925
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-26 08:32:03 -06:00
Andy Miller
9d6207f80e update changelog
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-26 08:17:19 -06:00
Andy Miller
22b493996c renamed markdown_editor to content_editor
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-26 08:16:37 -06:00
Andy Miller
5284717570 Added markdown editor option to user profile blueprint
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-25 14:03:40 -06:00
Matias Griese
96d11e4ffa Fixed new Flex User erroring out on save (thanks @mikebi42) 2020-05-25 22:18:30 +03:00
Matias Griese
a156247dc3 Fixed blueprint value filtering in lists [#2923] 2020-05-22 23:08:02 +03:00
Matias Griese
0e4a88d538 Fixed new Flex Page not having correct form fields for the page type 2020-05-22 21:44:23 +03:00
Andy Miller
a2d1cdfc13 rename hasChildren to countChildren
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-22 10:31:30 -06:00
Andy Miller
dcb0c490ba return count of children rather simple boolean
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-22 10:29:29 -06:00
Andy Miller
7687bdf751 vendor updates
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-20 11:56:16 -06:00
Andy Miller
85ef04abc4 handle images inside link excerpts
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-15 10:44:25 -06:00
Andy Miller
69ae59b933 prepare for rc.11 release
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-14 15:47:01 -06:00
Andy Miller
c5e538421e Merge branch 'develop' into 1.7
Signed-off-by: Andy Miller <rhuk@mac.com>

# Conflicts:
#	CHANGELOG.md
#	system/defines.php
2020-05-14 15:43:22 -06:00
Andy Miller
d7b1689047 Merge tag '1.6.25' into develop
Release 1.6.25
2020-05-14 15:41:45 -06:00
Andy Miller
bd8396ba6e Merge branch 'release/1.6.25' 2020-05-14 15:41:44 -06:00
Andy Miller
859aff590b Prepare for release
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-14 15:41:34 -06:00
Andy Miller
73d8a844d9 updated changelog
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-14 15:24:33 -06:00
Andy Miller
5db395799d Changelog updated
Signed-off-by: Andy Miller <rhuk@mac.com>
2020-05-14 15:17:02 -06:00
Andy Miller
ad1ecf21c3 Merge branch 'develop' into 1.7
Signed-off-by: Andy Miller <rhuk@mac.com>

# Conflicts:
#	system/blueprints/config/system.yaml
#	system/config/system.yaml
#	system/src/Grav/Common/Uri.php
2020-05-14 14:46:14 -06:00
Andy Miller
497ca2a5cd Advanced Customization options for HTTP_X_FORWARDED headers 2020-05-14 14:43:52 -06:00
Andy Miller
4e578ed95e Advanced Customization options for HTTP_X_FORWARDED headers 2020-05-14 14:32:07 -06:00
Andy Miller
8b258c8828 updated changelog 2020-05-14 12:36:50 -06:00
Andy Miller
148117edcb Support for native lazy loading of images #2910 2020-05-14 12:33:46 -06:00
Andy Miller
a57358f311 fix for & errors in URL 2020-05-14 10:47:36 -06:00
Matias Griese
ee0ad1126d Merge moved code 2020-05-12 20:35:04 +03:00
Matias Griese
2db0002ffd Merge branch '1.7' of github.com:getgrav/grav into feature/1.7-media
 Conflicts:
	system/src/Grav/Common/Page/Medium/ImageMedium.php
2020-05-12 20:34:26 +03:00
Matias Griese
a74e07c491 Docblock update 2020-05-08 13:24:10 +03:00
Matias Griese
8a12abf795 Flex: Added PageCollection::all() to mimick Pages class 2020-05-08 12:53:56 +03:00
Matias Griese
62c7cfcd10 Tighten vendor requirements 2020-05-08 10:30:11 +03:00
Matias Griese
9257d6982f Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	composer.json
	composer.lock
2020-05-08 10:21:51 +03:00
Jesse Donat
58da1cd489 Updates donatj/phpuseragentparser to 1.0.0 2020-05-08 10:18:31 +03:00
Andy Miller
69078aaf49 Fix for uppercase image extensions 2020-05-06 11:59:15 -06:00
Matias Griese
33aa0737a4 Improve docblocks 2020-05-06 13:01:47 +03:00
Matias Griese
8b47608cc0 Split Medium classes into traits, define Media interfaces 2020-05-06 12:23:49 +03:00
Matias Griese
d25014779d Fixed some phpstan issues 2020-05-05 19:35:09 +03:00
Matias Griese
5f0380f547 Fixed unit test 2020-05-05 19:04:56 +03:00
Andy Miller
bdbd392c6e Merge branch 'develop' into 1.7 2020-04-30 17:33:57 -06:00
Andy Miller
d53a594971 updated to 1.13 2020-04-30 17:33:41 -06:00
Andy Miller
2604da4b04 Merge branch 'develop' into 1.7 2020-04-30 16:23:47 -06:00
Djamil Legato
77bc8029bb Updated reference to github-release go app 2020-04-30 15:21:48 -07:00
Andy Miller
11e4a4cad8 Updated changelog to ref HttpClient inetgration 2020-04-30 14:48:47 -06:00
Andy Miller
e37f634a38 prepare for rc release 2020-04-30 14:46:14 -06:00
Matias Griese
0a18502c2c Merge remote-tracking branch 'origin/1.7' into 1.7 2020-04-30 22:16:24 +03:00
Matias Griese
b8c81c72e4 Copying Flex Page in admin does nothing 2020-04-30 22:16:16 +03:00
Andy Miller
37c2219b87 Updated changelog 2020-04-30 12:12:58 -06:00
Andy Miller
62f6f3910a Merge branch '1.7' of github.com:getgrav/grav into 1.7 2020-04-30 12:12:03 -06:00
Andy Miller
b450e6998e Added new Excerpts::processLinkHtml() method 2020-04-30 12:11:56 -06:00
Matias Griese
10800a0896 Merge remote-tracking branch 'origin/1.7' into 1.7 2020-04-30 16:30:23 +03:00
Matias Griese
cf61525130 Fixed saving non-numeric-prefix Flex Page changing to numeric-prefix 2020-04-30 16:30:14 +03:00
Andy Miller
24db65cd90 switch to Symfony HTTPClient (#2901) 2020-04-29 15:36:51 -06:00
Matias Griese
daf7e66ec3 Missing use 2020-04-29 22:11:18 +03:00
Matias Griese
18c9a1793e Merge remote-tracking branch 'origin/1.7' into 1.7 2020-04-29 21:41:34 +03:00
Matias Griese
2b0f235e23 Fixed Grav Pages admin with PHP intl extension enabled when using custom page order 2020-04-29 21:41:25 +03:00
Andy Miller
24eca428a6 Force response % to be 0 - 100 only 2020-04-28 17:37:21 -06:00
Andy Miller
677b0b7055 prepare for rc release 2020-04-27 16:10:47 -06:00
Andy Miller
10bd72ad51 Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
#	system/defines.php
2020-04-27 16:09:53 -06:00
Andy Miller
4ddc98b2b6 Merge branch 'release/1.6.24' 2020-04-27 16:08:40 -06:00
Andy Miller
c1f18f5ecf Merge tag '1.6.24' into develop
Release v1.6.24
2020-04-27 16:08:40 -06:00
Andy Miller
d16a88e731 prepare for release 2020-04-27 16:08:28 -06:00
A----
39d0d640e6 Support for the X-Forwarded-Host (#2891) 2020-04-27 10:38:36 -06:00
Matias Griese
bfb55f0e1d Fixed broken Plugin::config() method 2020-04-27 19:21:02 +03:00
Matias Griese
44b07712d5 Move method inside file 2020-04-24 18:21:31 +03:00
Matias Griese
c8ae9e4dcc Fixed creating new Flex User with file storage 2020-04-24 18:19:21 +03:00
Matias Griese
52d1412e4c Fixed creating new Flex User with file storage 2020-04-24 16:18:03 +03:00
Matias Griese
b820130636 Fixed Flex Pages not updating empty content on save [#2890] 2020-04-24 14:30:26 +03:00
Matias Griese
7d4ecd4272 Changelog update 2020-04-24 13:40:25 +03:00
Matias Griese
f907b589e1 Merge remote-tracking branch 'origin/1.7' into 1.7 2020-04-24 13:30:19 +03:00
Matias Griese
a9d80d73d6 Fixed Flex Pages with 00.home page not having ordering set 2020-04-24 13:30:10 +03:00
Andy Miller
fe0ad9c9c2 Added support for webp (must be defined in media.yaml) #1168 2020-04-23 12:35:05 -06:00
Matias Griese
dfa8ce1ad4 Fixed Flex Page parent header.admin.children_display_order setting being ignored in Admin [#2881] 2020-04-23 15:51:58 +03:00
Matias Griese
1ea5dad728 Implemented missing Flex $pageCollection->batch() and $pageCollection->order() methods 2020-04-23 15:51:10 +03:00
Matias Griese
a2b81c9378 Added new plugin/theme blueprint options to upgrade guide 2020-04-23 11:24:33 +03:00
Matias Griese
01b0b1602b Fixed Pages::instances() returning null values when using Flex Pages [#2889] 2020-04-23 10:41:42 +03:00
Andy Miller
7d4aef0f3b Fixed Gregwar Image library using .jpeg for cached images, rather use .jpg 2020-04-22 17:31:31 -06:00
Andy Miller
5485893c8b fix PHP 7.4 Clockwork error 2020-04-20 10:32:55 -06:00
Andy Miller
124543f4a7 minor vendor updates 2020-04-20 10:22:11 -06:00
Matias Griese
e84488e126 Support symlinks when saving File 2020-04-17 10:06:04 +03:00
Matias Griese
ed2cf5174d Improved error response creation 2020-04-08 18:56:22 +03:00
Matias Griese
d0d0a6c224 Composer update 2020-04-07 20:23:25 +03:00
Matias Griese
b04f4f0001 Added Route::getBase() method 2020-04-07 20:23:10 +03:00
Matias Griese
c822920c36 Fixed user avatar creation for new Flex Users when using folder storage 2020-04-04 18:32:36 +03:00
ricardo118
e6354236c1 Using ->menu() over ->title() for Admin Pages (#2871) 2020-03-28 15:11:35 -06:00
Matias Griese
068948e1a2 Fixed response for .json being HTML 2020-03-25 15:08:00 +02:00
Matias Griese
f8bf7ba010 Fixed flex objects with integer keys not working [#2863] 2020-03-23 10:18:51 +02:00
Andy Miller
586105907d disable XDebug for Travis builds 2020-03-19 14:56:03 -06:00
Andy Miller
abd56ffee3 disable XDebug in Travis builds 2020-03-19 14:55:19 -06:00
Andy Miller
5678b4d067 Revert "created aliases for direct \Parsedown and \ParsedownExtra references"
This reverts commit 1ce0176ab6.
2020-03-19 14:32:57 -06:00
Andy Miller
1abb940318 Merge branch 'develop' into 1.7 2020-03-19 14:31:35 -06:00
Andy Miller
15af5bfae7 Merge branch 'release/1.6.23' 2020-03-19 14:27:39 -06:00
Andy Miller
d550a016a9 Merge tag '1.6.23' into develop
Release v1.6.23
2020-03-19 14:27:39 -06:00
Andy Miller
9c8df27bf1 Updated changelog 2020-03-19 14:27:25 -06:00
Andy Miller
1ce0176ab6 created aliases for direct \Parsedown and \ParsedownExtra references 2020-03-19 14:25:08 -06:00
Andy Miller
5bc46c49d2 prepare for rc.8 release 2020-03-19 12:50:09 -06:00
Andy Miller
292d45a7db minor composer updates 2020-03-19 12:47:56 -06:00
Andy Miller
2d375954ab Merge branch '1.7' of github.com:getgrav/grav into 1.7 2020-03-19 12:46:59 -06:00
Andy Miller
7ff6d2a828 Merge branch 'develop' into 1.7
# Conflicts:
#	composer.lock
#	system/defines.php
2020-03-19 12:46:47 -06:00
Andy Miller
ccac8a93bc Merge branch 'release/1.6.23' 2020-03-19 12:46:08 -06:00
Andy Miller
bcbfa0e32a Merge tag '1.6.23' into develop
Release v1.6.23
2020-03-19 12:46:08 -06:00
Andy Miller
453cd62a51 prepare for release 2020-03-19 12:45:58 -06:00
Andy Miller
56e1cbc78e updated composer to pickup toolbox 1.4.7 2020-03-19 12:39:36 -06:00
Matias Griese
58cd77449c Composer update 2020-03-19 20:28:45 +02:00
Andy Miller
db92c7b32d Upgraded jQuery to 3.4.1 - fixes #2859 2020-03-19 11:31:10 -06:00
Matias Griese
6ba54d2b3d Better Content-Encoding handling in Apache when content compression is disabled [#2619] 2020-03-19 11:31:24 +02:00
Andy Miller
987429208c Merge branch 'develop' into 1.7
# Conflicts:
#	CHANGELOG.md
#	composer.json
#	composer.lock
2020-03-18 17:34:58 -06:00
Andy Miller
2eae104c7a Fix for user reported CVE path-based open redirect 2020-03-18 17:32:46 -06:00
Andy Miller
6f2be2a2d2 Updated changelog 2020-03-18 11:50:23 -06:00
Andy Miller
e75960fee3 php 7.4 fixes for ParsedownExtra 2020-03-18 11:44:23 -06:00
Andy Miller
ec71ccdd0d moved parsedown 1.6 and parsedown-extra 0.7 into framework for fixes 2020-03-18 11:39:08 -06:00
Matias Griese
a5d47a36f0 Implemented missing Flex $page->move() method 2020-03-18 14:54:01 +02:00
Matias Griese
866b63530a Fixed some bugs in Flex root page methods 2020-03-18 14:06:04 +02:00
Matias Griese
57feac985c Update installer recommended versions 2020-03-18 11:52:33 +02:00
Andy Miller
c72c980ad2 fixes #2847 2020-03-13 11:15:15 -06:00
Matias Griese
790d29cbfb Fixed some more flex page inconsistencies with the old pages 2020-03-09 20:33:49 +02:00
Matias Griese
b9947f6984 Fixed bad default redirect code in ControllerResponseTrait::createRedirectResponse() 2020-03-09 19:31:44 +02:00
Matias Griese
79b7c3c38f Fixed some bugs in Flex root page methods 2020-03-09 19:29:56 +02:00
Matias Griese
a9df896538 Flex Pages: added has-children to getLevelListing() response 2020-03-09 10:17:11 +02:00
Andy Miller
ad713a1342 update changelog 2020-03-06 12:35:13 -07:00
Andy Miller
b58479cba6 Merge branch '1.7' of github.com:getgrav/grav into 1.7 2020-03-06 12:34:37 -07:00
Andy Miller
107341f33f Fast check to see if a dir has child dirs 2020-03-06 12:34:33 -07:00
Matias Griese
1ed0fcb379 Fixed creating new Flex User when folder storage has been selected 2020-03-06 15:42:33 +02:00
Matias Griese
63892782b4 Save memory when updating large flex indexes 2020-03-06 15:32:22 +02:00
Matias Griese
f5a480c72e Added MediaTrait::freeMedia() method to free media (and memory) 2020-03-06 13:41:03 +02:00
Matias Griese
d80a29b34d Flex storages: added support for custom key length 2020-03-06 10:21:49 +02:00
Andy Miller
5aefc60f2e prepare for rc.7 release 2020-03-05 13:29:11 -07:00
Matias Griese
683c1fe477 Improve dynamic config field 2020-03-05 22:27:30 +02:00
Matias Griese
6b4692f6c7 Minor code refactor 2020-03-05 21:44:50 +02:00
Matias Griese
1351f11551 Added field validation for array of lines 2020-03-05 21:43:53 +02:00
Matias Griese
58280b8d3f Updated UPGRADE-1.7.md 2020-03-05 21:43:14 +02:00
Matias Griese
652ca75f40 Composer update 2020-03-05 21:41:28 +02:00
Matias Griese
4b5f1590aa Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	composer.json
	composer.lock
	system/defines.php
2020-03-05 21:39:59 +02:00
Andy Miller
152c987ed4 Merge branch 'release/1.6.22' 2020-03-05 10:39:00 -07:00
Andy Miller
19311c7ec1 Merge tag '1.6.22' into develop
Release v1.6.22
2020-03-05 10:39:00 -07:00
Andy Miller
eec2d122cc prepare for release 2020-03-05 10:38:50 -07:00
Andy Miller
62f39fe39c Negotiation update to fix #2513 2020-03-04 10:57:43 -07:00
Andy Miller
394fce3825 composer update 2020-03-04 10:54:32 -07:00
Matias Griese
9c6934c0aa Changelog update 2020-03-03 21:03:26 +02:00
Matias Griese
993bc5170b Fixed changing page template in Flex Pages 2020-03-03 21:00:24 +02:00
Matias Griese
d285eda4c2 Fixed reordering flex pages 2020-03-03 20:17:44 +02:00
Matias Griese
0bcbe39521 User accounts/groups: Update title by values entered to the form 2020-03-03 13:17:01 +02:00
Matias Griese
78502b2026 Fixed fatal error on storing flex flash using new object without a key 2020-03-03 13:12:25 +02:00
Matias Griese
df0c806381 Added UserObject::onPrepareRegistration() method to initialize permissions 2020-03-03 11:42:26 +02:00
Matias Griese
d089a1d9c8 Regression: Fixed unchecking toggleable having no effect in Flex forms 2020-03-02 21:02:44 +02:00
Matias Griese
e41072c448 Merge branch 'develop' of github.com:getgrav/grav into 1.7 2020-03-02 10:54:04 +02:00
Matias Griese
36d18d531c Minor fix on CLI command 2020-02-27 14:36:04 +02:00
Matias Griese
fb18412fb3 Updated CLI commands to use the new methods to initialize Grav 2020-02-27 14:14:28 +02:00
Matias Griese
9a8c2b9aa5 Add CLI notes to UPGRADE-1.7.md 2020-02-26 18:37:16 +02:00
Matias Griese
678b39a170 Tiny fix in FlexDirectory::getDirectoryBlueprint() 2020-02-25 14:04:43 +02:00
Matias Griese
d03b0d92f4 * Fixed Blueprint::extend() and Blueprint::embed() not initializing dynamic properties 2020-02-25 13:11:56 +02:00
Matias Griese
11f9ba74e8 Added configuration option system.strict_mode.blueprint_compat to maintain old validation: strict behavior [#1273] 2020-02-24 20:24:25 +02:00
Matias Griese
f4f5bffcd9 Fixed broken ordering sometimes when saving/moving visible Flex Page, ordering being lost when saving modular Flex Page 2020-02-24 14:55:39 +02:00
Matias Griese
d763f9c63e Upgrade doc: Added mention of PageInterface and UserInterface 2020-02-24 09:37:15 +02:00
Matias Griese
44acd3a969 Improved Flex events 2020-02-20 08:58:11 +02:00
Matias Griese
4d7510dc11 Fixed Flex Pages having broken isFirst(), isLast(), prevSibling(), nextSibling() and adjacentSibling() 2020-02-19 12:58:23 +02:00
Matias Griese
f4a3efc3bc Update UPGRADE-1.7.md file 2020-02-19 11:20:33 +02:00
Rotzbua
40bc980084 [travis] add php 7.4 to test 2020-02-19 10:46:49 +02:00
Matias Griese
cf62d1dfa2 Update UPGRADE-1.7.md file 2020-02-18 13:19:54 +02:00
Matias Griese
378e59563d Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	system/src/Grav/Common/Data/BlueprintSchema.php
	tests/unit/Grav/Common/Data/BlueprintTest.php
	tests/unit/data/blueprints/strict.yaml
2020-02-18 13:15:03 +02:00
Matias Griese
1196e06dd6 Reverted validation: strict fix as it breaks sites, see [#1273] 2020-02-18 13:14:09 +02:00
Matias Griese
028bbf08c6 Grav 1.7 edition: Fixed validation: strict not working in blueprints [#1273] 2020-02-18 13:09:14 +02:00
Matias Griese
1d6a474b31 Added Session::regenerateId() method to properly prevent session fixation issues 2020-02-18 12:26:45 +02:00
Matias Griese
79e68eb5df Composer update 2020-02-17 15:17:12 +02:00
Matias Griese
13a1c7cfee Merge remote-tracking branch 'origin/1.7' into 1.7 2020-02-17 14:04:39 +02:00
Matias Griese
a3812141dd Fixed issues reported in phpstan 2020-02-17 14:04:31 +02:00
Andy Miller
5833f1e2da missing lang object 2020-02-14 15:21:32 -07:00
Andy Miller
88311f21cf Merge branch 'develop' into 1.7
# Conflicts:
#	system/src/Grav/Common/Page/Pages.php
2020-02-14 14:31:31 -07:00
Andy Miller
64b33d60f4 updated changelog 2020-02-14 08:36:04 -07:00
Andy Miller
8ccbcf0488 set baseRoute with activelang if available 2020-02-14 08:14:57 -07:00
Matias Griese
4fb485d1dd Merge branch 'develop' of github.com:getgrav/grav into 1.7
 Conflicts:
	CHANGELOG.md
2020-02-14 14:47:56 +02:00
Matias Griese
244c34a536 Page::getCacheKey() method should be public 2020-02-14 13:55:31 +02:00
Matias Griese
52a704e53d Fixed issue with search plugins not being able to switch between page translations 2020-02-14 13:55:06 +02:00
Andy Miller
5852ca4179 Better support for bin/grav server 2020-02-12 18:27:30 -07:00
347 changed files with 14779 additions and 5990 deletions

View File

@@ -27,6 +27,9 @@ RewriteEngine On
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
#
# Block out any script trying to use twig tags in URL.
RewriteCond %{REQUEST_URI} ({{|}}|{%|%}) [OR]
RewriteCond %{QUERY_STRING} ({{|}}|{%25|%25}) [OR]
# Block out any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block out any script that includes a <script> tag in URL.

View File

@@ -3,6 +3,7 @@ php:
- '7.1'
- '7.2'
- '7.3'
- '7.4'
branches:
only:
- develop
@@ -47,8 +48,8 @@ before_install:
fi
- if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "7.1" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
export TRAVIS_TAG=$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4);
eval "$(curl -sL https://raw.githubusercontent.com/travis-ci/gimme/master/gimme | GIMME_GO_VERSION=1.8 bash)";
go get github.com/aktau/github-release;
eval "$(curl -sL https://raw.githubusercontent.com/travis-ci/gimme/master/gimme | GIMME_GO_VERSION=1.13 bash)";
go get github.com/github-release/github-release;
git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null;
if [ ! -z "$TRAVIS_TAG" ]; then
cd ${RT_DEVTOOLS};
@@ -56,7 +57,7 @@ before_install:
fi;
fi
before_script:
- if [ $TRAVIS_PHP_VERSION != 'hhvm' ]; then phpenv config-rm xdebug.ini; fi
- phpenv config-rm xdebug.ini
script:
- if [ $TRAVIS_BRANCH == 'develop' ] || [ $TRAVIS_PULL_REQUEST != 'false' ]; then
vendor/bin/codecept run;

View File

@@ -1,3 +1,246 @@
# v1.7.0-rc.19
## 12/02/2020
1. [](#bugfix)
* Updated composer libraries with latest Toolbox v1.5.6 that contains critical fixes
# v1.7.0-rc.18
## 12/02/2020
1. [](#new)
* Set minimum requirements to **PHP 7.3.6**
* Updated Clockwork to v5.0
* Added `FlexDirectoryInterface` interface
* Renamed `PageCollectionInterface::nonModular()` into `PageCollectionInterface::pages()` and deprecated the old method
* Renamed `PageCollectionInterface::modular()` into `PageCollectionInterface::modules()` and deprecated the old method'
* Upgraded `bin/composer.phar` to `2.0.2` which is all new and much faster
* Added search option `same_as` to Flex Objects
* Added PHP 8 compatible `function_exists()`: `Utils::functionExists()`
* New sites have `compatibility` features turned off by default, upgrading from older versions will keep the settings on
1. [](#improved)
* Updated bundled JQuery to latest version `3.5.1`
* Forward a `sid` to GPM when downloading a premium package via CLI
* Allow `JsonFormatter` options to be passed as a string
* Hide Flex Pages frontend configuration (not ready for production use)
* Improve Flex configuration: gather views together in blueprint
* Added XSS detection to all forms. See [documentation](http://learn.grav.local/17/forms/forms/form-options#xss-checks)
* Better handling of missing repository index [grav-plugin-admin#1916](https://github.com/getgrav/grav-plugin-admin/issues/1916)
* Added support for having all sites / environments under `user/env` folder [#3072](https://github.com/getgrav/grav/issues/3072)
* Added `FlexObject::refresh()` method to make sure object is up to date
1. [](#bugfix)
* *Menu Visibility Requires Access* Security option setting wrong frontmatter [login#265](https://github.com/getgrav/grav-plugin-login/issues/265)
* Accessing page with unsupported file extension (jpg, pdf, xsl) will use wrong mime type [#3031](https://github.com/getgrav/grav/issues/3031)
* Fixed media crashing on a bad image
* Fixed bug in collections where filter `type: false` did not work
* Fixed `print_r()` in twig
* Fixed sorting by groups in `Flex Users`
* Changing `Flex Page` template causes the other language versions of that page to lose their content [admin#1958](https://github.com/getgrav/grav-plugin-admin/issues/1958)
* Fixed plugins getting initialized multiple times (by CLI commands for example)
* Fixed `header.admin.children_display_order` in Flex Pages to work just like with regular pages
* Fixed `Utils::isFunctionDisabled()` method if there are spaces in `disable_functions` [#3023](https://github.com/getgrav/grav/issues/3023)
* Fixed potential fatal error when creating flex index using cache [#3062](https://github.com/getgrav/grav/issues/3062)
* Fixed fatal error in `CompiledFile` if the cached version is broken
* Fixed updated media missing from media when editing Flex Object after page reload
* Fixed issue with `config-default@` breaking on set [#1972](https://github.com/getgrav/grav-plugin-admin/issues/1971)
* Escape titles in Flex pages list [flex-objects#84](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/84)
* Fixed Purge successful message only working in Scheduler but broken in CLI and Admin [#1935](https://github.com/getgrav/grav-plugin-admin/issues/1935)
* Fixed `system://` stream is causing issues in Admin, making Media tab to disappear and possibly causing other issues [#3072](https://github.com/getgrav/grav/issues/3072)
* Fixed CLI self-upgrade from Grav 1.6 [#3079](https://github.com/getgrav/grav/issues/3079)
* Fixed `bin/grav yamllinter -a` and `-f` not following symlinks [#3080](https://github.com/getgrav/grav/issues/3080)
* Fixed `|safe_email` filter to return safe and escaped UTF-8 HTML [#3072](https://github.com/getgrav/grav/issues/3072)
* Fixed exception in CLI GPM and backup commands when `php-zip` is not enabled [#3075](https://github.com/getgrav/grav/issues/3075)
* Fix for XSS advisory [GHSA-cvmr-6428-87w9](https://github.com/getgrav/grav/security/advisories/GHSA-cvmr-6428-87w9)
* Fixed Flex and Page ordering to be natural and case insensitive [flex-objects#87](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/87)
* Fixed plugin/theme priority ordering to be numeric
# v1.7.0-rc.17
## 10/07/2020
1. [](#new)
* Added a `Uri::getAllHeaders()` compatibility function
1. [](#improved)
* Fall back through various templates scenarios if they don't exist in theme to avoid unhelpful error.
* Added default templates for `external.html.twig`, `default.html.twig`, and `modular.html.twig`
* Improve Media classes
* _POTENTIAL BREAKING CHANGE:_ Added reload argument to `FlexStorageInterface::getMetaData()`
1. [](#bugfix)
* Fixed `Security::sanitizeSVG()` creating an empty file if SVG file cannot be parsed
* Fixed infinite loop in blueprints with `extend@` to a parent stream
* Added missing `Stream::create()` method
* Added missing `onBlueprintCreated` event for Flex Pages
* Fixed `onBlueprintCreated` firing multiple times recursively
* Fixed media upload failing with custom folders
* Fixed `unset()` in `ObjectProperty` class
* Fixed `FlexObject::freeMedia()` method causing media to become null
* Fixed bug in `Flex Form` making it impossible to set nested values
* Fixed `Flex User` avatar when using folder storage, also allow multiple images
* Fixed Referer reference during GPM calls.
* Fixed fatal error with toggled lists
# v1.7.0-rc.16
## 09/01/2020
1. [](#new)
* Added a new `svg_image()` twig function to make it easier to 'include' SVG source in Twig
* Added a helper `Utils::fullPath()` to get the full path to a file be it stream, relative, etc.
1. [](#improved)
* Added `themes` to cached blueprints and configuration
1. [](#bugfix)
* Fixed `Flex Pages` issue with `getRoute()` returning path with language prefix for default language if set not to do that
* Fixed `Flex Pages` bug where reordering pages causes page content to disappear if default language uses wrong extension (`.md` vs `.en.md`)
* Fixed `Flex Pages` bug where `onAdminSave` passes page as `$event['page']` instead of `$event['object']` [#2995](https://github.com/getgrav/grav/issues/2995)
* Fixed `Flex Pages` bug where changing a modular page template added duplicate file [admin#1899](https://github.com/getgrav/grav-plugin-admin/issues/1899)
* Fixed `Flex Pages` bug where renaming slug causes bad ordering range after save [#2997](https://github.com/getgrav/grav/issues/2997)
# v1.7.0-rc.15
## 07/22/2020
1. [](#bugfix)
* Fixed Flex index file caching [#2962](https://github.com/getgrav/grav/issues/2962)
* Fixed various issues with Exif data reading and images being incorrectly rotated [#1923](https://github.com/getgrav/grav-plugin-admin/issues/1923)
# v1.7.0-rc.14
## 07/09/2020
1. [](#improved)
* Added ability to `noprocess` specific items only in Link/Image Excerpts, e.g. `http://foo.com/page?id=foo&target=_blank&noprocess=id` [#2954](https://github.com/getgrav/grav/pull/2954)
1. [](#bugfix)
* Regression: Default language fix broke `Language::getLanguageURLPrefix()` and `Language::isIncludeDefaultLanguage()` methods when not using multi-language
* Reverted `Language::getDefault()` and `Language::getLanguage()` to return false again because of plugin compatibility (updated docblocks)
* Fixed UTF-8 issue in `Excerpts::getExcerptsFromHtml`
* Fixed some compatibility issues with recent Changes to `Assets` handling
* Fixed issue with `CSS_IMPORTS_REGEX` breaking with complex URLs [#2958](https://github.com/getgrav/grav/issues/2958)
* Moved duplicated `CSS_IMPORT_REGEX` to local variable in `AssetUtilsTrait::moveImports()`
* Fixed page media only accepting images [#2943](https://github.com/getgrav/grav/issues/2943)
# v1.7.0-rc.13
## 07/01/2020
1. [](#new)
* Added support for uploading and deleting images directly in `Media`
* Added new `onAfterCacheClear` event
1. [](#improved)
* Improved `CvsFormatter` to attempt to encode non-scalar variables into JSON before giving up
* Moved image loading into its own trait to be used by images+static images
* Adjusted asset types to enable extension of assets in class [#2937](https://github.com/getgrav/grav/pull/2937)
* Composer update for vendor library updates
* Updated bundled `composer.phar` to `2.0.0-dev`
1. [](#bugfix)
* Fixed `MediaUploadTrait::copyUploadedFile()` not adding uploaded media to the collection
* Fixed regression in saving media to a new Flex Object [admin#1867](https://github.com/getgrav/grav-plugin-admin/issues/1867)
* Fixed `Trying to get property 'username' of non-object` error in Flex [flex-objects#62](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/62)
* Fixed retina images not working in Flex [flex-objects#64](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/64)
* Fixed plugin initialization in CLI
* Fixed broken logic in `Page::topParent()` when dealing with first-level pages
* Fixed broken `Flex Page` authorization for groups
* Fixed missing `onAdminSave` and `onAdminAfterSave` events when using `Flex Pages` and `Flex Users` [flex-objects#58](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/58)
* Fixed new `User Group` allowing bad group name to be saved [admin#1917](https://github.com/getgrav/grav-plugin-admin/issues/1917)
* Fixed `Language::getDefault()` returning false and not 'en'
* Fixed non-text links in `Excerpts::getExcerptFromHtml`
* Fixed CLI commands not properly intializing Plugins so events can fire
# v1.7.0-rc.12
## 06/08/2020
1. [](#improved)
* Changed `Folder::hasChildren` to `Folder::countChildren`
* Added `Content Editor` option to user account blueprint
1. [](#bugfix)
* Fixed new `Flex Page` not having correct form fields for the page type
* Fixed new `Flex User` erroring out on save (thanks @mikebi42)
* Fixed `Flex Object` request cache clear when saving object
* Fixed blueprint value filtering in lists [#2923](https://github.com/getgrav/grav/issues/2923)
* Fixed blueprint for `system.pages.hide_empty_folders` [#1925](https://github.com/getgrav/grav/issues/2925)
* Fixed file field in `Flex Objects` (use `Grav\Common\Flex\Types\GenericObject` instead of `FlexObject`) [flex-objects#37](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/37)
* Fixed saving nested file fields in `Flex Objects` [flex-objects#34](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/34)
* JSON Route of homepage with no route set is valid [form#425](https://github.com/getgrav/grav-plugin-form/issues/425)
# v1.7.0-rc.11
## 05/14/2020
1. [](#new)
* Added support for native `loading=lazy` attributes on images. Can be set in `system.images.defaults` or per md image with `?loading=lazy` [#2910](https://github.com/getgrav/grav/issues/2910)
1. [](#improved)
* Added `PageCollection::all()` to mimic Pages class
* Added system configuration support for `HTTP_X_Forwarded` headers (host disabled by default)
* Updated `PHPUserAgentParser` to 1.0.0
* Improved docblocks
* Fixed some phpstan issues
* Tighten vendor requirements
1. [](#bugfix)
* Fix for uppercase image extensions
* Fix for `&` errors in HTML when passed to `Excerpts.php`
# v1.7.0-rc.10
## 04/30/2020
1. [](#new)
* Changed `Response::get()` used by **GPM/Admin** to use [Symfony HttpClient v4.4](https://symfony.com/doc/current/components/http_client.html) (`composer install --nodev` required for Git installations)
* Added new `Excerpts::processLinkHtml()` method
1. [](#bugfix)
* Fixed `Flex Pages` admin with PHP `intl` extension enabled when using custom page order
* Fixed saving non-numeric-prefix `Flex Page` changing to numeric-prefix [flex-objects#56](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/56)
* Copying `Flex Page` in admin does nothing [flex-objects#55](https://github.com/trilbymedia/grav-plugin-flex-objects/issues/55)
* Force GPM progress to be between 0-100%
# v1.7.0-rc.9
## 04/27/2020
1. [](#new)
* Support for `webp` image format in Page Media [#1168](https://github.com/getgrav/grav/issues/1168)
* Added `Route::getBase()` method
1. [](#improved)
* Support symlinks when saving `File`
1. [](#bugfix)
* Fixed flex objects with integer keys not working [#2863](https://github.com/getgrav/grav/issues/2863)
* Fixed `Pages::instances()` returning null values when using `Flex Pages` [#2889](https://github.com/getgrav/grav/issues/2889)
* Fixed Flex Page parent `header.admin.children_display_order` setting being ignored in Admin [#2881](https://github.com/getgrav/grav/issues/2881)
* Implemented missing Flex `$pageCollection->batch()` and `$pageCollection->order()` methods
* Fixed user avatar creation for new `Flex Users` when using folder storage
* Fixed `Trying to access array offset on value of type null` PHP 7.4 error in `Plugin.php`
* Fixed Gregwar Image library using `.jpeg` for cached images, rather use `.jpg`
* Fixed `Flex Pages` with `00.home` page not having ordering set
* Fixed `Flex Pages` not updating empty content on save [#2890](https://github.com/getgrav/grav/issues/2890)
* Fixed creating new Flex User with file storage
* Fixed saving new `Flex Object` with custom key
* Fixed broken `Plugin::config()` method
# v1.7.0-rc.8
## 03/19/2020
1. [](#new)
* Added `MediaTrait::freeMedia()` method to free media (and memory)
* Added `Folder::hasChildren()` method to determine if a folder has child folders
1. [](#improved)
* Save memory when updating large flex indexes
* Better `Content-Encoding` handling in Apache when content compression is disabled [#2619](https://github.com/getgrav/grav/issues/2619)
1. [](#bugfix)
* Fixed creating new `Flex User` when folder storage has been selected
* Fixed some bugs in Flex root page methods
* Fixed bad default redirect code in `ControllerResponseTrait::createRedirectResponse()`
* Fixed issue with PHP `HTTP_X_HTTP_METHOD_OVERRIDE` [#2847](https://github.com/getgrav/grav/issues/2847)
* Fixed numeric usernames not working in `Flex Users`
* Implemented missing Flex `$page->move()` method
# v1.7.0-rc.7
## 03/05/2020
1. [](#new)
* Added `Session::regenerateId()` method to properly prevent session fixation issues
* Added configuration option `system.strict_mode.blueprint_compat` to maintain old `validation: strict` behavior [#1273](https://github.com/getgrav/grav/issues/1273)
1. [](#improved)
* Improved Flex events
* Updated CLI commands to use the new methods to initialize Grav
1. [](#bugfix)
* Fixed Flex Pages having broken `isFirst()`, `isLast()`, `prevSibling()`, `nextSibling()` and `adjacentSibling()`
* Fixed broken ordering sometimes when saving/moving visible `Flex Page` [#2837](https://github.com/getgrav/grav/issues/2837)
* Fixed ordering being lost when saving modular `Flex Page`
* Fixed `validation: strict` not working in blueprints (see `system.strict_mode.blueprint_compat` setting) [#1273](https://github.com/getgrav/grav/issues/1273)
* Fixed `Blueprint::extend()` and `Blueprint::embed()` not initializing dynamic properties
* Fixed fatal error on storing flex flash using new object without a key
* Regression: Fixed unchecking toggleable having no effect in Flex forms
* Fixed changing page template in Flex Pages [#2828](https://github.com/getgrav/grav/issues/2828)
# v1.7.0-rc.6
## 02/11/2020
@@ -280,6 +523,106 @@
* Optimization: Initialize debugbar only after the configuration has been loaded
* Optimization: Combine some early Grav processors into a single one
# v1.6.29
## 12/02/2020
1. [](#new)
* Added basic support for `user/config/versions.yaml`
1. [](#improved)
* Updated bundled JQuery to latest version `3.5.1`
* Forward a `sid` to GPM when downloading a premium package via CLI
* Better handling of missing repository index [grav-plugin-admin#1916](https://github.com/getgrav/grav-plugin-admin/issues/1916)
* Set `grav_cli` as referrer when using `Response` from CLI
* Add option for timeout in `self-upgrade` command [#3013](https://github.com/getgrav/grav/pull/3013)
* Allow to set SameSite from system.yaml [#3063](https://github.com/getgrav/grav/pull/3063)
* Update media.yaml with some MS Office mimetypes [#3070](https://github.com/getgrav/grav/pull/3070)
1. [](#bugfix)
* Fixed hardcoded system folder in blueprints, config and language streams
* Added `.htaccess` rule to block attempts to use Twig in the request URL
* Fix compatibility with Symfony 4.2 and up. [#3048](https://github.com/getgrav/grav/pull/3048)
* Fix failing example custom shceduled job. [#3050](https://github.com/getgrav/grav/pull/3050)
* Fix for XSS advisory [GHSA-cvmr-6428-87w9](https://github.com/getgrav/grav/security/advisories/GHSA-cvmr-6428-87w9)
* Fix uploads_dangerous_extensions checking [#3060](https://github.com/getgrav/grav/pull/3060)
* Remove redundant prefixing of `.` to extension [#3060](https://github.com/getgrav/grav/pull/3060)
* Check exact extension in checkFilename utility [#3061](https://github.com/getgrav/grav/pull/3061)
# v1.6.28
## 10/07/2020
1. [](#new)
* Back-ported twig `{% cache %}` tag from Grav 1.7
* Back-ported `Utils::fullPath()` helper function from Grav 1.7
* Back-ported `{{ svg_image() }}` Twig function from Grav 1.7
* Back-ported `Folder::countChildren()` function from Grav 1.7
1. [](#improved)
* Use new `{{ theme_var() }}` enhanced logic from Grav 1.7
* Improved `Excerpts` class with fixes and functionality from Grav 1.7
* Ensure `onBlueprintCreated()` is initialized first
* Do not cache default `404` error page
* Composer update of vendor libraries
* Switched `Caddyfile` to use new Caddy2 syntax + improved usability
1. [](#bugfix)
* Fixed Referer reference during GPM calls.
* Fixed fatal error with toggled lists
# v1.6.27
## 09/01/2020
1. [](#improved)
* Right trim route for safety
* Use the proper ellipsis for summary [#2939](https://github.com/getgrav/grav/pull/2939)
* Left pad schedule times with zeros [#2921](https://github.com/getgrav/grav/pull/2921)
# v1.6.26
## 06/08/2020
1. [](#improved)
* Added new configuration option to control the supported attributes in markdown links [#2882](https://github.com/getgrav/grav/issues/2882)
1. [](#bugfix)
* Fixed blueprint for `system.pages.hide_empty_folders` [#1925](https://github.com/getgrav/grav/issues/2925)
* JSON Route of homepage with no route set is valid
* Fix case-insensitive search of location header [form#425](https://github.com/getgrav/grav-plugin-form/issues/425)
# v1.6.25
## 05/14/2020
1. [](#improved)
* Added system configuration support for `HTTP_X_Forwarded` headers (host disabled by default)
* Updated `PHPUserAgentParser` to 1.0.0
* Bump `Go` to version 1.13 in `travis.yaml`
# v1.6.24
## 04/27/2020
1. [](#improved)
* Added support for `X-Forwarded-Host` [#2891](https://github.com/getgrav/grav/pull/2891)
* Disable XDebug in Travis builds
# v1.6.23
## 03/19/2020
1. [](#new)
* Moved `Parsedown` 1.6 and `ParsedownExtra` 0.7 into `Grav\Framework\Parsedown` to allow fixes
* Added `aliases.php` with references to direct `\Parsedown` and `\ParsedownExtra` references
1. [](#improved)
* Upgraded `jQuery` to latest 3.4.1 version [#2859](https://github.com/getgrav/grav/issues/2859)
1. [](#bugfix)
* Fixed PHP 7.4 issue in ParsedownExtra [#2832](https://github.com/getgrav/grav/issues/2832)
* Fix for [user reported](https://twitter.com/OriginalSicksec) CVE path-based open redirect
* Fix for `stream_set_option` error with PHP 7.4 via Toolbox#28 [#2850](https://github.com/getgrav/grav/issues/2850)
# v1.6.22
## 03/05/2020
1. [](#new)
* Added `Pages::reset()` method
1. [](#improved)
* Updated Negotiation library to address issues [#2513](https://github.com/getgrav/grav/issues/2513)
1. [](#bugfix)
* Fixed issue with search plugins not being able to switch between page translations
* Fixed issues with `Pages::baseRoute()` not picking up active language reliably
* Reverted `validation: strict` fix as it breaks sites, see [#1273](https://github.com/getgrav/grav/issues/1273)
# v1.6.21
## 02/11/2020

View File

@@ -21,7 +21,7 @@ The underlying architecture of Grav is designed to use well-established and _bes
# Requirements
- PHP 7.1.3 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
- PHP 7.3.6 or higher. Check the [required modules list](https://learn.getgrav.org/basics/requirements#php-requirements)
- Check the [Apache](https://learn.getgrav.org/basics/requirements#apache-requirements) or [IIS](https://learn.getgrav.org/basics/requirements#iis-requirements) requirements
# QuickStart

15
SECURITY.md Normal file
View File

@@ -0,0 +1,15 @@
# Security Policy
## Supported Versions
We are focusing our security updates on the following versions
| Version | Supported |
| ------- | ------------------ |
| 1.7.x | :white_check_mark: |
| 1.6.x | :white_check_mark: |
| < 1.6 | :x: |
## Reporting a Vulnerability
Please contact contact@getgrav.org with a detailed explaination of the security issue found and we will work with you to get it resolved as fast as possible.

View File

@@ -1,4 +1,6 @@
# UPGRADE FROM 1.6 TO 1.7
# UPGRADE FROM 1.6 TO 1.7.RC17
Grav 1.7 REQUIRES PHP 7.3.6
## ADMINISTRATORS
@@ -6,15 +8,38 @@
* 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.
## Forms
* **BC BREAK** Fixed `validation: strict`. Please search through all your forms if you were using this feature. If you were, either remove the line or test if the form still works.
* Added configuration option `system.strict_mode.blueprint_compat` to maintain old `validation: strict` behavior
* If you disable compatibiity, form validation will be much more strict (recommended, but may break existing forms)
* DOCUMENT NEW FORM FLASH!
### 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**
### Media
* Support for `webp` image format
* Markdown: Added support for native `loading=lazy` attributes on images. Can be set in `system.images.defaults` or per md image with `?loading=lazy`
* Added ability to `noprocess` specific items only in Link/Image Excerpts, e.g. `http://foo.com/page?id=foo&target=_blank&noprocess=id`
### 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.
### Admin
* If you upgrade from older 1.7 RC, you need to go to Flex Objects plugin settings and turn on `Pages`, `User Accounts` and `User Groups` directories (upgrading 1.6 automatically turns them on)
* Disabling `User Accounts` and `User Groups` directories in Flex Objects plugin should be kept enabled; fine tuned **ACL** may not work without
### Sessions
* Session ID now changes on login to prevent session fixation issues
### CLI
* Added new `bin/grav server` CLI command to easily run Symfony or PHP built-in web servers
@@ -22,7 +47,7 @@
* 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
@@ -31,6 +56,8 @@
* 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
* Added system configuration support for `HTTP_X_Forwarded` headers (host disabled by default)
* Added new configuration option `system.strict_mode.blueprint_compat` to maintain old `validation: strict` behavior
### Debugging
@@ -40,56 +67,186 @@
## DEVELOPERS
### Use composer autoloader
* Please add `composer.json` file to your plugin and run `composer update --no-dev` (and remember to keep it updated):
composer.json
```json
{
"name": "getgrav/grav-plugin-example",
"type": "grav-plugin",
"description": "Example plugin for Grav CMS",
"keywords": ["example", "plugin"],
"homepage": "https://github.com/getgrav/grav-plugin-example",
"license": "MIT",
"authors": [
{
"name": "...",
"email": "...",
"homepage": "...",
"role": "Developer"
}
],
"support": {
"issues": "https://github.com/getgrav/grav-plugin-example/issues",
"docs": "https://github.com/getgrav/grav-plugin-example/blob/master/README.md"
},
"require": {
"php": ">=7.1.3"
},
"autoload": {
"psr-4": {
"Grav\\Plugin\\Example\\": "classes/",
"Grav\\Plugin\\Console\\": "cli/"
},
"classmap": [
"example.php"
]
},
"config": {
"platform": {
"php": "7.1.3"
}
}
}
```
See [Composer schema](https://getcomposer.org/doc/04-schema.md)
* Please use autoloader instead of `require` in the code:
example.php
```php
/**
* @return array
*/
public static function getSubscribedEvents(): array
{
return [
'onPluginsInitialized' => [
// This is only required in Grav 1.6. Grav 1.7 automatically calls $plugin->autolaod() method.
['autoload', 100000],
]
];
}
/**
* Composer autoload.
*
* @return \Composer\Autoload\ClassLoader
*/
public function autoload(): \Composer\Autoload\ClassLoader
{
return require __DIR__ . '/vendor/autoload.php';
}
```
* Plugins & Themes: Call `$plugin->autoload()` and `$theme->autoload()` automatically when object gets initialized
* Make sure your code does not use `require` or `include` for loading classes
### Plugin/Theme Blueprints (`blueprints.yaml`)
* Please add:
```yaml
slug: folder-name
type: plugin|theme
```
* Make sure you update your dependencies. I recommend setting Grav to either 1.6 or 1.7 and update your code/vendor to PHP 7.1
```yaml
dependencies:
- { name: grav, version: '>=1.6.0' }
```
### Sessions
* Added `Session::regenerateId()` method to properly prevent session fixation issues
### ACL
* `user.authorize()` now requires user to be authorized (passed 2FA check), unless the rule contains `login` in its name.
* Added support for more advanced ACL (CRUD)
* **BC BREAK** `user.authorize()` and Flex `object.isAuthorized()` now have two deny states: `false` and `null`.
* **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).
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`
* Added experimental support for `Flex Pages` in the frontend (not recommended to use yet)
* Admin uses `Flex Pages` by default (can be disabled from `Flex-Objects` plugin)
* Added page specific admin permissions support for `Flex Pages`
* Added root page 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()`
* **DEPRECATED** `PageCollectionInterface::nonModular()` in favor of `PageCollectionInterface::pages()`
* **DEPRECATED** `PageCollectionInterface::modular()` in favor of `PageCollectionInterface::modules()`
* **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`.
* **BC BREAK** Always use `\Grav\Common\Page\Interfaces\PageInterface` instead of `\Grav\Common\Page\Page` in method signatures
* Admin now uses `Flex Pages` by default, collection will behave in slightly different way
* **BC BREAK** `$page->topParent()` may return page itself instead of null
### Media
* Added `MediaTrait::freeMedia()` method to free media (and memory)
* Added support for uploading and deleting images directly in `Media` by using PSR-7
* Adjusted asset types to enable extension of assets in class
* **BC BREAK** Media no longer extends `Getters`, accessing `$media->$filename` no longer works, use `$media[$filename]` instead!
### Markdown
* **BC BREAK** Upgraded Parsedown to 1.7 for Parsedown-Extra 0.8. Plugins that extend Parsedown may need a fix to render as HTML
* Added new `Excerpts::processLinkHtml()` method
### Users
* Added experimental support for `Flex Users` in the frontend (not recommended to use yet)
* Admin uses `Flex Users` by default (can be disabled from `Flex-Objects` plugin)
* 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
* **DEPRECATED** `\Grav\Common\User\Group` in favor of `$grav['user_groups']`, which contains Flex UserGroup collection
* **BC BREAK** Always use `\Grav\Common\User\Interfaces\UserInterface` instead of `\Grav\Common\User\User` in method signatures
### Flex
* Do not use `Framework` Flex classes directly, it's better to use or extend classes under `Grav\Common\Flex\Types\Generic` namespace
* Added `$grav['flex']` to access all registered Flex Directories
* Added `FlexRegisterEvent` which triggers when `$grav['flex']` is being accessed the first time
* 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
* Flex Directory: Implemented customizable configuration per flex type
* **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
* **BC BREAK** Added reload argument to `FlexStorageInterface::getMetaData()`
* You can add `edit_list.html.twig` file to a form field in order to customize look in the listing view
### Templating
* Added support for Twig 2.12 (still using Twig 1.42)
* Added support for Twig 2.12 (using Twig 1.43)
* 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
* Added a new `svg_image()` twig function to make it easier to 'include' SVG source in Twig
* Improved `url()` twig function to take third parameter (`true`) to return URL on non-existing file instead of returning false
* Improved `|array` twig filter to work with iterators and objects with `toArray()` method
* Improved `authorize()` twig function to work better with nested rule parameters
* Improved `|yaml_serialize` twig filter: added support for `JsonSerializable` objects and other array-like objects
* Added `themes` to cached blueprints and configuration
* Added default templates for `external.html.twig`, `default.html.twig`, and `modular.html.twig`
### Multi-language
@@ -98,28 +255,101 @@
* 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
### Blueprints
* Added `flatten_array` filter to form field validation
* Added support for `security@: or: [admin.super, admin.pages]` in blueprints (nested AND/OR mode support)
* Blueprint validation: Added `validate: value_type: bool|int|float|string|trim` to `array` to filter all the values inside the array
* If your plugins has blueprints folder, initializing it in the event will be too late. Do this instead:
```php
class MyPlugin extends Plugin
{
/** @var array */
public $features = [
'blueprints' => 0, // Use priority 0
];
}
```
### Events
* Use `Symfony EventDispatcher` directly instead of `rockettheme/toolbox` wrapper.
* Added `$grav->dispatchEvent()` method for PSR-14 events
* Added `PluginsLoadedEvent` which triggers after plugins have been loaded but not yet initialized
* Added `SessionStartEvent` which triggers when session is started
* Added `FlexRegisterEvent` which triggers when `$grav['flex']` is being accessed the first time
* Added `PermissionsRegisterEvent` which triggers when `$grav['permissions']` is being accessed the first time
* Added `onAfterCacheClear` event
* Check `onAdminTwigTemplatePaths` event, it should NOT be:
```php
public function onAdminTwigTemplatePaths($event)
{
// This code breaks all the other plugins in admin, including Flex Objects
$event['paths'] = [__DIR__ . '/admin/themes/grav/templates'];
}
```
but:
```php
public function onAdminTwigTemplatePaths($event)
{
// Add plugin template path for admin.
$paths = $event['paths'];
$paths[] = __DIR__ . '/admin/themes/grav/templates';
$event['paths'] = $paths;
}
```
### Misc
* Added `Utils::isAssoc()` and `Utils::isNegative()` helper methods
* Added `Utils::simpleTemplate()` method for very simple variable templating
* Added `Utils::fullPath()` to get the full path to a file be it stream, relative, etc.
* 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
* Added `Folder::countChildren()` method to determine if a folder has child folders
* Support symlinks when saving `File`
* Added `Route::getBase()` method
* **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.
* Better `Content-Encoding` handling in Apache when content compression is disabled
* Added a `Uri::getAllHeaders()` compatibility function
### CLI
* **BC BREAK** Many plugins initialize Grav in a wrong way, it is not safe to initialize plugins and theme by yourself
* Following calls require Grav 1.6.21 or later, so it is recommended to set Grav dependency to that version
* Inside `serve()` method:
* Call `$this->setLanguage($langCode);` before doing anything else if you want to set the language (or use default)
* Call one of following:
* `$this->initializeGrav();` Already called if you're in `bin/plugin`, otherwise you may need to call this one
* `$this->initializePlugins();` This initializes grav, plugins (up to `onPluginsInitialized`)
* `$this->initializeThemes();` This initializes grav, plugins and theme
* `$this->initializePages();` This initializes grav, plugins, theme and everything needed by pages
* It is a good idea to prefix your CLI command classes with your plugin name, otherwise there may be naming conflicts (we already have some!)
### 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).
## PLUGINS
### Admin
* **BC BREAK** Admin will not initialize frontend pages anymore, this has been done to greatly speed up Admin plugin.
* Added `Content Editor` option to user account blueprint
* **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.
* Admin now uses Flex for editing `Accounts` and `Pages`. If your plugin hooks into either of those, please make sure they still work.
* Admin cache is enabled by default, make sure your plugin clears cache when needed. Please avoid clearing all cache!
### Shortcode Core
* **DEPRECATED** Every shortcode needs to have `init()` method, classes without it will stop working in the future.

Binary file not shown.

View File

@@ -12,60 +12,65 @@
"homepage": "https://getgrav.org",
"license": "MIT",
"require": {
"php": ">=7.1.3",
"php": "^7.3.6 || ^8.0",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-curl": "*",
"ext-zip": "*",
"ext-dom": "*",
"symfony/polyfill-iconv": "^1.9",
"symfony/polyfill-php72": "^1.9",
"symfony/polyfill-php73": "^1.9",
"ext-libxml": "*",
"symfony/polyfill-mbstring": "~1.20",
"symfony/polyfill-iconv": "^1.20",
"symfony/polyfill-php74": "^1.20",
"symfony/polyfill-php80": "^1.20",
"psr/simple-cache": "^1.0",
"psr/http-message": "^1.0",
"psr/http-server-middleware": "^1.0",
"kodus/psr7-server": "*",
"nyholm/psr7": "^1.0",
"twig/twig": "~1.0",
"nyholm/psr7": "^1.3",
"twig/twig": "~1.44",
"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",
"filp/whoops": "~2.2",
"symfony/contracts": "~1.1",
"symfony/yaml": "4.4.16",
"symfony/console": "~4.4",
"symfony/event-dispatcher": "~4.4",
"symfony/var-dumper": "~4.4",
"symfony/process": "~4.4",
"doctrine/cache": "^1.10",
"doctrine/collections": "^1.6",
"guzzlehttp/psr7": "^1.7",
"filp/whoops": "~2.9",
"matthiasmullie/minify": "^1.3",
"monolog/monolog": "~1.0",
"gregwar/image": "2.*",
"donatj/phpuseragentparser": "~0.10",
"pimple/pimple": "~3.2",
"rockettheme/toolbox": "dev-develop",
"maximebf/debugbar": "~1.0",
"league/climate": "^3.4",
"monolog/monolog": "~1.25",
"gregwar/image": "dev-php8",
"gregwar/cache": "dev-php8",
"donatj/phpuseragentparser": "~1.1",
"pimple/pimple": "~3.3",
"rockettheme/toolbox": "~1.5",
"maximebf/debugbar": "~1.16",
"league/climate": "^3.6",
"antoligy/dom-string-iterators": "^1.0",
"miljar/php-exif": "^0.6.4",
"composer/ca-bundle": "^1.0",
"miljar/php-exif": "^0.6",
"composer/ca-bundle": "^1.2",
"dragonmantank/cron-expression": "^1.2",
"phive/twig-extensions-deferred": "^1.0",
"willdurand/negotiation": "^2.3",
"itsgoingd/clockwork": "@beta",
"enshrined/svg-sanitize": "~0.1"
"willdurand/negotiation": "^3.0",
"itsgoingd/clockwork": "^5.0",
"enshrined/svg-sanitize": "~0.13",
"symfony/http-client": "^4.4"
},
"require-dev": {
"codeception/codeception": "^2.4",
"codeception/codeception": "^2.5",
"phpstan/phpstan": "^0.11",
"phpstan/phpstan-deprecation-rules": "^0.11",
"phpunit/php-code-coverage": "~6.0",
"fzaninotto/faker": "^1.8",
"phpunit/php-code-coverage": "~6.1",
"fzaninotto/faker": "^1.9",
"victorjonsson/markdowndocs": "dev-master"
},
"suggest": {
"ext-mbstring": "Recommended for better performance",
"ext-iconv": "Recommended for better performance",
"ext-zend-opcache": "Recommended for better performance",
"ext-intl": "Recommended for multi-language sites",
"ext-memcache": "Needed to support Memcache servers",
@@ -75,20 +80,21 @@
"config": {
"apcu-autoloader": true,
"platform": {
"php": "7.1.3"
"php": "7.3.6"
}
},
"repositories": [{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/trilbymedia/PHP-Markdown-Documentation-Generator"
},
{
"type": "vcs",
"url": "https://github.com/itsgoingd/clockwork"
"url": "https://github.com/getgrav/Cache"
},
{
"type": "vcs",
"url": "https://github.com/rockettheme/toolbox"
"url": "https://github.com/getgrav/Image"
}
],
"autoload": {
@@ -105,12 +111,13 @@
]
},
"scripts": {
"api-17": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.17.md",
"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 3 -c ./tests/phpstan/phpstan.neon system/src --memory-limit=340M",
"phpstan-framework": "vendor/bin/phpstan analyse -l 7 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=128M",
"phpstan-plugins": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=300M",
"phpstan": "vendor/bin/phpstan analyse -l 3 -c ./tests/phpstan/phpstan.neon --memory-limit=400M system/src",
"phpstan-framework": "vendor/bin/phpstan analyse -l 7 -c ./tests/phpstan/phpstan.neon --memory-limit=256M system/src/Grav/Framework",
"phpstan-plugins": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/plugins.neon --memory-limit=400M user/plugins",
"test": "vendor/bin/codecept run unit",
"test-windows": "vendor\\bin\\codecept run unit"
},

2644
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,14 +10,15 @@
namespace Grav;
\define('GRAV_REQUEST_TIME', microtime(true));
\define('GRAV_PHP_MIN', '7.1.3');
\define('GRAV_PHP_MIN', '7.3.6');
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') {
$symfony_server = strpos(getenv('_'), 'symfony') !== false;
$symfony_server = stripos(getenv('_'), 'symfony') !== false || stripos($_SERVER['SERVER_SOFTWARE'], '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>");
}

File diff suppressed because one or more lines are too long

View File

@@ -39,12 +39,13 @@ form:
.command:
type: text
label: PLUGIN_ADMIN.COMMAND
placeholder: 'cd ~;ls -lah;'
placeholder: 'ls'
validate:
required: true
.args:
type: text
label: PLUGIN_ADMIN.EXTRA_ARGUMENTS
placeholder: '-lah'
.at:
type: cron
label: PLUGIN_ADMIN.SCHEDULER_RUNAT

View File

@@ -241,13 +241,15 @@ form:
type: commalist
pages.hide_empty_folders:
type: selectize
size: large
type: toggle
label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS
help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP
classes: fancy
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: commalist
type: bool
pages.url_taxonomy_filters:
type: toggle
@@ -544,6 +546,15 @@ form:
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.markdown.valid_link_attributes:
type: selectize
size: large
label: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES
help: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES_HELP
placeholder: "rel, target, id, class, classes"
classes: fancy
validate:
type: commalist
caching:
type: tab
@@ -730,6 +741,64 @@ form:
size: small
label: PLUGIN_ADMIN.REDIS_PASSWORD
flex_caching:
type: section
title: PLUGIN_ADMIN.FLEX_CACHING
flex.cache.index.enabled:
type: toggle
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_ENABLED
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
flex.cache.index.lifetime:
type: text
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_LIFETIME
default: 60
validate:
type: int
flex.cache.object.enabled:
type: toggle
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_ENABLED
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
flex.cache.object.lifetime:
type: text
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_LIFETIME
default: 600
validate:
type: int
flex.cache.render.enabled:
type: toggle
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_ENABLED
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
flex.cache.render.lifetime:
type: text
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_LIFETIME
default: 600
validate:
type: int
twig:
type: tab
title: PLUGIN_ADMIN.TWIG_TEMPLATING
@@ -777,7 +846,8 @@ form:
type: toggle
label: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES
help: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES_HELP
highlight: 0
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
@@ -1114,13 +1184,24 @@ form:
type: toggle
label: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION
help: PLUGIN_ADMIN.IMAGES_AUTO_FIX_ORIENTATION_HELP
highlight: 0
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
images.defaults.loading:
type: select
size: small
label: PLUGIN_ADMIN.IMAGES_LOADING
help: PLUGIN_ADMIN.IMAGES_LOADING_HELP
highlight: auto
options:
auto: Auto
lazy: Lazy
eager: Eager
images.seofriendly:
type: toggle
label: PLUGIN_ADMIN.IMAGES_SEOFRIENDLY
@@ -1154,7 +1235,6 @@ form:
validate:
type: bool
media.allowed_fallback_types:
type: selectize
size: large
@@ -1264,6 +1344,12 @@ form:
label: PLUGIN_ADMIN.SESSION_PATH
help: PLUGIN_ADMIN.SESSION_PATH_HELP
session.samesite:
type: text
size: small
label: PLUGIN_ADMIN.SESSION_SAMESITE
help: PLUGIN_ADMIN.SESSION_SAMESITE_HELP
session.split:
type: toggle
label: PLUGIN_ADMIN.SESSION_SPLIT
@@ -1432,17 +1518,68 @@ form:
label: PLUGIN_ADMIN.CUSTOM_BASE_URL
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
accounts.type:
type: hidden
http_x_forwarded.protocol:
type: toggle
label: HTTP_X_FORWARDED_PROTO Enabled
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
accounts.storage:
type: hidden
http_x_forwarded.host:
type: toggle
label: HTTP_X_FORWARDED_HOST Enabled
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
http_x_forwarded.port:
type: toggle
label: HTTP_X_FORWARDED_PORT Enabled
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
http_x_forwarded.ip:
type: toggle
label: HTTP_X_FORWARDED IP Enabled
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
strict_mode.blueprint_compat:
type: toggle
label: PLUGIN_ADMIN.STRICT_BLUEPRINT_COMPAT
highlight: 0
default: 0
help: PLUGIN_ADMIN.STRICT_BLUEPRINT_COMPAT_HELP
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
strict_mode.yaml_compat:
type: toggle
label: PLUGIN_ADMIN.STRICT_YAML_COMPAT
highlight: 1
default: 1
highlight: 0
default: 0
help: PLUGIN_ADMIN.STRICT_YAML_COMPAT_HELP
options:
1: PLUGIN_ADMIN.YES
@@ -1453,8 +1590,8 @@ form:
strict_mode.twig_compat:
type: toggle
label: PLUGIN_ADMIN.STRICT_TWIG_COMPAT
highlight: 1
default: 1
highlight: 0
default: 0
help: PLUGIN_ADMIN.STRICT_TWIG_COMPAT_HELP
options:
1: PLUGIN_ADMIN.YES
@@ -1472,18 +1609,21 @@ form:
title: PLUGIN_ADMIN.EXPERIMENTAL
underline: true
flex_pages:
type: section
title: Flex Pages
# flex_pages:
# type: section
# title: Flex Pages
#
# pages.type:
# type: select
# label: PLUGIN_ADMIN.PAGES_TYPE
# highlight: regular
# help: PLUGIN_ADMIN.PAGES_TYPE_HELP
# options:
# regular: PLUGIN_ADMIN.REGULAR
# flex: PLUGIN_ADMIN.FLEX
pages.type:
type: select
label: PLUGIN_ADMIN.PAGES_TYPE
highlight: stable
help: PLUGIN_ADMIN.PAGES_TYPE_HELP
options:
regular: PLUGIN_ADMIN.REGULAR
flex: PLUGIN_ADMIN.FLEX
type: hidden
flex_accounts:
type: section
@@ -1506,61 +1646,3 @@ form:
options:
file: PLUGIN_ADMIN.FILE
folder: PLUGIN_ADMIN.FOLDER
flex_caching:
type: section
title: PLUGIN_ADMIN.FLEX_CACHING
flex.cache.index.enabled:
type: toggle
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_ENABLED
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
flex.cache.index.lifetime:
type: text
label: PLUGIN_ADMIN.FLEX_INDEX_CACHE_LIFETIME
default: 60
validate:
type: int
flex.cache.object.enabled:
type: toggle
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_ENABLED
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
flex.cache.object.lifetime:
type: text
label: PLUGIN_ADMIN.FLEX_OBJECT_CACHE_LIFETIME
default: 600
validate:
type: int
flex.cache.render.enabled:
type: toggle
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_ENABLED
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
flex.cache.render.lifetime:
type: text
label: PLUGIN_ADMIN.FLEX_RENDER_CACHE_LIFETIME
default: 600
validate:
type: int

View File

@@ -2,7 +2,7 @@ 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.
# Deprecated in Grav 1.7.0-rc.4: file was renamed to user-accounts.yaml
extends@:
type: user-accounts
context: blueprints://flex

View File

@@ -0,0 +1,17 @@
form:
compatibility:
type: tab
title: Compatibility
fields:
object.compat.events:
type: toggle
toggleable: true
label: Admin event compatibility
help: Enables onAdminSave and onAdminAfterSave events for plugins
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool

View File

@@ -7,6 +7,10 @@ extends@:
type: default
context: blueprints://pages
#
# HIGHLY SPECIALIZED FLEX TYPE, AVOID USING PAGES AS BASE FOR YOUR OWN TYPE.
#
# Flex configuration
config:
# Administration Configuration (needs Flex Objects plugin)
@@ -99,6 +103,9 @@ config:
label: PLUGIN_ADMIN.ADD
edit:
title:
template: "{% if object.root %}Root <small>( &lt;root&gt; ){% else %}{{ (form.value('header.title') ?? form.value('folder'))|e }} <small>( {{ (object.getRoute().toString(false) ?: '/')|e }} )</small>{% endif %}"
# TODO: not used yet
buttons:
back:
@@ -181,6 +188,13 @@ config:
- title
- name
blueprints:
configure:
fields:
import@:
type: configure/compat
context: blueprints://flex
# Regular form definition
form:
fields:

View File

@@ -7,6 +7,10 @@ extends@:
type: account
context: blueprints://user
#
# HIGHLY SPECIALIZED FLEX TYPE, AVOID USING USER ACCOUNTS AS BASE FOR YOUR OWN TYPE.
#
# Flex configuration
config:
# Administration Configuration (needs Flex Objects plugin)
@@ -52,6 +56,8 @@ config:
username:
link: edit
search: true
field:
label: PLUGIN_ADMIN.USERNAME
email:
search: true
fullname:
@@ -66,7 +72,7 @@ config:
# Edit view
edit:
title:
template: '{{ object.fullname ?? object.username }} &lt;{{ object.email }}&gt;'
template: "{{ form.value('fullname') ?? form.value('username') }} &lt;{{ form.value('email') }}&gt;"
# Configure view
configure:
@@ -108,6 +114,8 @@ config:
folder: 'account://'
pattern: '{FOLDER}/{KEY}{EXT}'
indexed: true
key: username
case_sensitive: false
search:
options:
contains: 1
@@ -115,6 +123,13 @@ config:
- key
- email
blueprints:
configure:
fields:
import@:
type: configure/compat
context: blueprints://flex
# Regular form definition
form:
fields:

View File

@@ -65,7 +65,7 @@ config:
# Edit view
edit:
title:
template: '{{ object.readableName ?? object.groupname }}'
template: "{{ form.value('readableName') ?? form.value('groupname') }}"
# Configure view
configure:
@@ -113,3 +113,10 @@ config:
- key
- groupname
- description
blueprints:
configure:
fields:
import@:
type: configure/compat
context: blueprints://flex

View File

@@ -7,7 +7,7 @@ form:
fields:
header.visibility_requires_access:
header.login.visibility_requires_access:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.PAGE_VISIBILITY_REQUIRES_ACCESS

View File

@@ -67,6 +67,15 @@ form:
default: 'en'
help: PLUGIN_ADMIN.LANGUAGE_HELP
content_editor:
type: select
label: PLUGIN_ADMIN.CONTENT_EDITOR
size: medium
classes: fancy
data-options@: 'Grav\Plugin\Admin\Admin::contentEditor'
default: 'default'
help: PLUGIN_ADMIN.CONTENT_EDITOR_HELP
twofa_check:
type: conditional
condition: config.plugins.admin.twofa_enabled
@@ -99,6 +108,8 @@ form:
sublabel: PLUGIN_ADMIN.2FA_SECRET_HELP
security:
security@: admin.super
title: PLUGIN_ADMIN.ACCESS_LEVELS

View File

@@ -1,48 +1,55 @@
title: Group
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
validation: loose
fields:
groupname:
type: text
size: large
label: PLUGIN_ADMIN.GROUP_NAME
flex-disabled@: exists
flex-readonly@: exists
validate:
required: true
fields:
groupname:
type: text
size: large
label: PLUGIN_ADMIN.GROUP_NAME
flex-disabled@: exists
flex-readonly@: exists
validate:
required: true
rule: slug
readableName:
type: text
size: large
label: PLUGIN_ADMIN.DISPLAY_NAME
readableName:
type: text
size: large
label: PLUGIN_ADMIN.DISPLAY_NAME
description:
type: text
size: large
label: PLUGIN_ADMIN.DESCRIPTION
description:
type: text
size: large
label: PLUGIN_ADMIN.DESCRIPTION
icon:
type: text
size: small
label: PLUGIN_ADMIN.ICON
icon:
type: text
size: small
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
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
access:
type: permissions
check_authorize: false
label: PLUGIN_ADMIN.PERMISSIONS
ignore_empty: true
validate:
type: array
value_type: bool

View File

@@ -1,5 +1,11 @@
title: PLUGIN_ADMIN_PRO.ADD_GROUP
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
@@ -14,3 +20,4 @@ form:
help: PLUGIN_ADMIN_PRO.GROUP_NAME_HELP
validate:
required: true
rule: slug

View File

@@ -24,6 +24,10 @@ types:
type: image
thumb: media/thumb-png.png
mime: image/png
webp:
type: image
thumb: media/thumb-webp.png
mime: image/webp
gif:
type: animated
thumb: media/thumb-gif.png
@@ -103,7 +107,7 @@ types:
docx:
type: file
thumb: media/thumb-docx.png
mime: application/msword
mime: application/vnd.openxmlformats-officedocument.wordprocessingml.document
xls:
type: file
thumb: media/thumb-xls.png
@@ -111,7 +115,7 @@ types:
xlsx:
type: file
thumb: media/thumb-xlsx.png
mime: application/vnd.ms-excel
mime: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
ppt:
type: file
thumb: media/thumb-ppt.png
@@ -119,7 +123,7 @@ types:
pptx:
type: file
thumb: media/thumb-pptx.png
mime: application/vnd.ms-powerpoint
mime: application/vnd.openxmlformats-officedocument.presentationml.presentation
pps:
type: file
thumb: media/thumb-pps.png

View File

@@ -10,18 +10,24 @@ custom_base_url: '' # Set the base_url manually, e.
username_regex: '^[a-z0-9_-]{3,16}$' # Only lowercase chars, digits, dashes, underscores. 3 - 16 chars
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}' # At least one number, one uppercase and lowercase letter, and be at least 8+ chars
intl_enabled: true # Special logic for PHP International Extension (mod_intl)
http_x_forwarded: # Configuration options for the various HTTP_X_FORWARD headers
protocol: true
host: false
port: true
ip: true
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 # 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
override_locale: false # Override the default or system locale with language specific one
content_fallback: {} # Custom language fallbacks. eg: {fr: ['fr', 'en']}
pages_fallback_only: false # DEPRECATED: Use `content_fallback` instead
home:
alias: '/home' # Default path for home, ie /
@@ -56,6 +62,12 @@ pages:
special_chars: # List of special characters to automatically convert to entities
'>': 'gt'
'<': 'lt'
valid_link_attributes: # Valid attributes to pass through via markdown links
- rel
- target
- id
- class
- classes
types: [html,htm,xml,txt,json,rss,atom] # list of valid page types
append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html)
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
@@ -96,7 +108,7 @@ twig:
cache: true # Set to true to enable Twig caching
debug: true # Enable Twig debug
auto_reload: true # Refresh cache on changes
autoescape: false # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
autoescape: true # Autoescape Twig vars (DEPRECATED, always enabled in strict mode)
undefined_functions: true # Allow undefined functions
undefined_filters: true # Allow undefined filters
umask_fix: false # By default Twig creates cached files as 755, fix switches this to 775
@@ -137,8 +149,10 @@ images:
cache_all: false # Cache all image by default
cache_perms: '0755' # MUST BE IN QUOTES!! Default cache folder perms. Usually '0755' or '0775'
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
auto_fix_orientation: false # Automatically fix the image orientation based on the Exif data
auto_fix_orientation: true # Automatically fix the image orientation based on the Exif data
seofriendly: false # SEO-friendly processed image names
defaults:
loading: auto # Let browser pick [auto|lazy|eager]
media:
enable_media_timestamp: false # Enable media timestamps
@@ -154,6 +168,7 @@ session:
uniqueness: path # Should sessions be `path` based or `security.salt` based
secure: false # Set session secure. If true, indicates that communication for this cookie must be over an encrypted transmission. Enable this only on sites that run exclusively on HTTPS
httponly: true # Set session HTTP only. If true, indicates that cookies should be used only over HTTP, and JavaScript modification is not allowed.
samesite: # Set session SameSite. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
split: true # Sessions should be independent between site and plugins (such as admin)
path:
@@ -181,5 +196,6 @@ flex:
lifetime: 600 # Lifetime of cached HTML in seconds (0 = infinite)
strict_mode:
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility
twig_compat: true # Grav 1.5+: Enables deprecated Twig autoescape setting (autoescape: false)
yaml_compat: false # Set to true to enable YAML backwards compatibility
twig_compat: false # Set to true to enable deprecated Twig settings (autoescape: false)
blueprint_compat: false # Set to true to enable backward compatible strict support for blueprints

View File

@@ -8,12 +8,15 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.7.0-rc.6');
define('GRAV_VERSION', '1.7.0-rc.19');
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
define('GRAV_TESTING', true);
define('DS', '/');
if (!defined('DS')) {
define('DS', '/');
}
if (!defined('GRAV_PHP_MIN')) {
define('GRAV_PHP_MIN', '7.1.3');
define('GRAV_PHP_MIN', '7.3.6');
}
// Directories and Paths

View File

@@ -94,9 +94,10 @@ GRAV:
YR_PLURAL: yrs
DEC_PLURAL: decs
FORM:
VALIDATION_FAIL: <b>Validation failed:</b>
INVALID_INPUT: Invalid input in
MISSING_REQUIRED_FIELD: Missing required field:
VALIDATION_FAIL: '<b>Validation failed:</b>'
INVALID_INPUT: 'Invalid input in'
MISSING_REQUIRED_FIELD: 'Missing required field:'
XSS_ISSUES: "Potential XSS issues detected in '%s' field"
MONTHS_OF_THE_YEAR: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
DAYS_OF_THE_WEEK: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
YES: "Yes"

View File

@@ -2,4 +2,5 @@
title: Not Found
routable: false
notfound: true
expires: 0
---

View File

@@ -15,18 +15,28 @@ use Grav\Common\Assets\Traits\TestingAssetsTrait;
use Grav\Common\Config\Config;
use Grav\Framework\Object\PropertyObject;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use function call_user_func_array;
use function count;
use function func_get_args;
use function is_array;
/**
* Class Assets
* @package Grav\Common
*/
class Assets extends PropertyObject
{
use TestingAssetsTrait;
use LegacyAssetsTrait;
const CSS = 'css';
const JS = 'js';
const CSS_COLLECTION = 'assets_css';
const JS_COLLECTION = 'assets_js';
const CSS_TYPE = 'Css';
const JS_TYPE = 'Js';
const INLINE_CSS_TYPE = 'InlineCss';
const INLINE_JS_TYPE = 'InlineJs';
const CSS_TYPE = Assets\Css::Class;
const JS_TYPE = Assets\Js::Class;
const INLINE_CSS_TYPE = Assets\InlineCss::Class;
const INLINE_JS_TYPE = Assets\InlineJs::Class;
/** @const Regex to match CSS and JavaScript files */
const DEFAULT_REGEX = '/.\.(css|js)$/i';
@@ -55,11 +65,19 @@ class Assets extends PropertyObject
/** @var bool */
protected $css_pipeline_before_excludes;
/** @var bool */
protected $inlinecss_pipeline_include_externals;
/** @var bool */
protected $inlinecss_pipeline_before_excludes;
/** @var bool */
protected $js_pipeline;
/** @var bool */
protected $js_pipeline_include_externals;
/** @var bool */
protected $js_pipeline_before_excludes;
/** @var bool */
protected $inlinejs_pipeline_include_externals;
/** @var bool */
protected $inlinejs_pipeline_before_excludes;
/** @var array */
protected $pipeline_options = [];
@@ -76,6 +94,8 @@ class Assets extends PropertyObject
/**
* Initialization called in the Grav lifecycle to initialize the Assets with appropriate configuration
*
* @return void
*/
public function init()
{
@@ -106,7 +126,6 @@ class Assets extends PropertyObject
* assets and/or collections that will be automatically added on startup.
*
* @param array $config Configurable options.
*
* @return $this
*/
public function config(array $config)
@@ -133,24 +152,24 @@ class Assets extends PropertyObject
* It automatically detects the asset type (JavaScript, CSS or collection).
* You may add more than one asset passing an array as argument.
*
* @param array|string $asset
* @param string|string[] $asset
* @return $this
*/
public function add($asset)
{
$args = \func_get_args();
$args = func_get_args();
// More than one asset
if (\is_array($asset)) {
if (is_array($asset)) {
foreach ($asset as $a) {
array_shift($args);
$args = array_merge([$a], $args);
\call_user_func_array([$this, 'add'], $args);
call_user_func_array([$this, 'add'], $args);
}
} elseif (isset($this->collections[$asset])) {
array_shift($args);
$args = array_merge([$this->collections[$asset]], $args);
\call_user_func_array([$this, 'add'], $args);
call_user_func_array([$this, 'add'], $args);
} else {
// Get extension
$extension = pathinfo(parse_url($asset, PHP_URL_PATH), PATHINFO_EXTENSION);
@@ -159,9 +178,9 @@ class Assets extends PropertyObject
if (\strlen($extension) > 0) {
$extension = strtolower($extension);
if ($extension === 'css') {
\call_user_func_array([$this, 'addCss'], $args);
call_user_func_array([$this, 'addCss'], $args);
} elseif ($extension === 'js') {
\call_user_func_array([$this, 'addJs'], $args);
call_user_func_array([$this, 'addJs'], $args);
}
}
}
@@ -169,12 +188,20 @@ class Assets extends PropertyObject
return $this;
}
/**
* @param string $collection
* @param string $type
* @param string|string[] $asset
* @param array $options
* @return $this
*/
protected function addType($collection, $type, $asset, $options)
{
if (\is_array($asset)) {
if (is_array($asset)) {
foreach ($asset as $a) {
$this->addType($collection, $type, $a, $options);
}
return $this;
}
@@ -186,7 +213,7 @@ class Assets extends PropertyObject
// If pipeline disabled, set to position if provided, else after
if (isset($options['pipeline'])) {
if ($options['pipeline'] === false) {
$exclude_type = ($type === $this::JS_TYPE || $type === $this::INLINE_JS_TYPE) ? $this::JS_TYPE : $this::CSS_TYPE;
$exclude_type = ($type === $this::JS_TYPE || $type === $this::INLINE_JS_TYPE) ? $this::JS : $this::CSS;
$excludes = strtolower($exclude_type . '_pipeline_before_excludes');
if ($this->{$excludes}) {
$default = 'after';
@@ -204,11 +231,10 @@ class Assets extends PropertyObject
$options['timestamp'] = $this->timestamp;
// Set order
$options['order'] = \count($this->$collection);
$options['order'] = count($this->$collection);
// Create asset of correct type
$asset_class = "\\Grav\\Common\\Assets\\{$type}";
$asset_object = new $asset_class();
$asset_object = new $type();
// If exists
if ($asset_object->init($asset, $options)) {
@@ -225,7 +251,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($this::CSS_COLLECTION, $this::CSS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::CSS_TYPE));
}
/**
@@ -235,7 +261,7 @@ class Assets extends PropertyObject
*/
public function addInlineCss($asset)
{
return $this->addType(Assets::CSS_COLLECTION, Assets::INLINE_CSS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::INLINE_CSS_TYPE));
return $this->addType($this::CSS_COLLECTION, $this::INLINE_CSS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_CSS_TYPE));
}
/**
@@ -245,7 +271,7 @@ class Assets extends PropertyObject
*/
public function addJs($asset)
{
return $this->addType(Assets::JS_COLLECTION, Assets::JS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::JS_TYPE));
return $this->addType($this::JS_COLLECTION, $this::JS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::JS_TYPE));
}
/**
@@ -255,20 +281,19 @@ class Assets extends PropertyObject
*/
public function addInlineJs($asset)
{
return $this->addType(Assets::JS_COLLECTION, Assets::INLINE_JS_TYPE, $asset, $this->unifyLegacyArguments(\func_get_args(), Assets::INLINE_JS_TYPE));
return $this->addType($this::JS_COLLECTION, $this::INLINE_JS_TYPE, $asset, $this->unifyLegacyArguments(func_get_args(), $this::INLINE_JS_TYPE));
}
/**
* Add/replace collection.
*
* @param string $collectionName
* @param array $assets
* @param string $collectionName
* @param array $assets
* @param bool $overwrite
*
* @return $this
*/
public function registerCollection($collectionName, Array $assets, $overwrite = false)
public function registerCollection($collectionName, array $assets, $overwrite = false)
{
if ($overwrite || !isset($this->collections[$collectionName])) {
$this->collections[$collectionName] = $assets;
@@ -277,6 +302,13 @@ class Assets extends PropertyObject
return $this;
}
/**
* @param array $assets
* @param string $key
* @param string $value
* @param bool $sort
* @return array|false
*/
protected function filterAssets($assets, $key, $value, $sort = false)
{
$results = array_filter($assets, function ($asset) use ($key, $value) {
@@ -284,8 +316,8 @@ class Assets extends PropertyObject
if ($key === 'position' && $value === 'pipeline') {
$type = $asset->getType();
if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline') {
if ($this->{$type . '_pipeline_before_excludes'}) {
if ($asset->getRemote() && $this->{strtolower($type) . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline') {
if ($this->{strtolower($type) . '_pipeline_before_excludes'}) {
$asset->setPosition('after');
} else {
$asset->setPosition('before');
@@ -308,14 +340,25 @@ class Assets extends PropertyObject
return $results;
}
/**
* @param array $assets
* @return array
*/
protected function sortAssets($assets)
{
uasort($assets, static function ($a, $b) {
return $b['priority'] <=> $a['priority'] ?: $a['order'] <=> $b['order'];
});
return $assets;
}
/**
* @param string $type
* @param string $group
* @param array $attributes
* @return string
*/
public function render($type, $group = 'head', $attributes = [])
{
$before_output = '';
@@ -362,7 +405,6 @@ class Assets extends PropertyObject
*
* @param string $group name of the group
* @param array $attributes
*
* @return string
*/
public function css($group = 'head', $attributes = [])
@@ -375,7 +417,6 @@ class Assets extends PropertyObject
*
* @param string $group name of the group
* @param array $attributes
*
* @return string
*/
public function js($group = 'head', $attributes = [])

View File

@@ -14,7 +14,12 @@ use Grav\Common\Grav;
use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Framework\Object\PropertyObject;
use SplFileInfo;
/**
* Class BaseAsset
* @package Grav\Common\Assets
*/
abstract class BaseAsset extends PropertyObject
{
use AssetUtilsTrait;
@@ -22,9 +27,6 @@ abstract class BaseAsset extends PropertyObject
protected const CSS_ASSET = true;
protected const JS_ASSET = false;
/** @const Regex to match CSS import content */
protected const CSS_IMPORT_REGEX = '{@import(.*?);}';
/** @var string|false */
protected $asset;
/** @var string */
@@ -130,7 +132,7 @@ abstract class BaseAsset extends PropertyObject
return false;
}
$file = new \SplFileInfo($path);
$file = new SplFileInfo($path);
$asset = $this->buildLocalLink($file->getPathname());

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
use Grav\Common\Utils;
/**
* Class Css
* @package Grav\Common\Assets
*/
class Css extends BaseAsset
{
/**

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
use Grav\Common\Utils;
/**
* Class InlineCss
* @package Grav\Common\Assets
*/
class InlineCss extends BaseAsset
{
/**

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
use Grav\Common\Utils;
/**
* Class InlineJs
* @package Grav\Common\Assets
*/
class InlineJs extends BaseAsset
{
/**

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\Assets;
use Grav\Common\Utils;
/**
* Class Js
* @package Grav\Common\Assets
*/
class Js extends BaseAsset
{
/**

View File

@@ -16,7 +16,12 @@ use Grav\Common\Uri;
use Grav\Common\Utils;
use Grav\Framework\Object\PropertyObject;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use function array_key_exists;
/**
* Class Pipeline
* @package Grav\Common\Assets
*/
class Pipeline extends PropertyObject
{
use AssetUtilsTrait;
@@ -30,9 +35,6 @@ class Pipeline extends PropertyObject
/** @const Regex to match CSS sourcemap comments */
protected const CSS_SOURCEMAP_REGEX = '{\/\*# (.*?) \*\/}';
/** @const Regex to match CSS import content */
protected const CSS_IMPORT_REGEX = '{@import(.*?);}';
protected const FIRST_FORWARDSLASH_REGEX = '{^\/{1}\w}';
// Following variables come from the configuration:

View File

@@ -9,13 +9,20 @@
namespace Grav\Common\Assets\Traits;
use Closure;
use Grav\Common\Grav;
use Grav\Common\Utils;
use function in_array;
use function is_array;
/**
* Trait AssetUtilsTrait
* @package Grav\Common\Assets\Traits
*/
trait AssetUtilsTrait
{
/**
* @var \Closure|null
* @var Closure|null
*
* Closure used by the pipeline to fetch assets.
*
@@ -86,7 +93,7 @@ trait AssetUtilsTrait
}
// TODO: looks like this is not being used.
$file = $this->fetch_command instanceof \Closure ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
$file = $this->fetch_command instanceof Closure ? @$this->fetch_command->__invoke($link) : @file_get_contents($link);
// No file found, skip it...
if ($file === false) {
@@ -123,9 +130,11 @@ trait AssetUtilsTrait
*/
protected function moveImports($file)
{
$regex = '{@import.*?["\']([^"\']+)["\'].*?;}';
$imports = [];
$file = (string)preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) use (&$imports) {
$file = (string)preg_replace_callback($regex, function ($matches) use (&$imports) {
$imports[] = $matches[0];
return '';
@@ -149,11 +158,11 @@ trait AssetUtilsTrait
if (is_numeric($key)) {
$key = $value;
}
if (\is_array($value)) {
if (is_array($value)) {
$value = implode(' ', $value);
}
if (\in_array($key, $no_key, true)) {
if (in_array($key, $no_key, true)) {
$element = htmlentities($value, ENT_QUOTES, 'UTF-8', false);
} else {
$element = $key . '="' . htmlentities($value, ENT_QUOTES, 'UTF-8', false) . '"';
@@ -168,7 +177,7 @@ trait AssetUtilsTrait
/**
* Render Querystring
*
* @param string $asset
* @param string|null $asset
* @return string
*/
protected function renderQueryString($asset = null)

View File

@@ -10,7 +10,14 @@
namespace Grav\Common\Assets\Traits;
use Grav\Common\Assets;
use function count;
use function is_array;
use function is_int;
/**
* Trait LegacyAssetsTrait
* @package Grav\Common\Assets\Traits
*/
trait LegacyAssetsTrait
{
/**

View File

@@ -9,8 +9,17 @@
namespace Grav\Common\Assets\Traits;
use FilesystemIterator;
use Grav\Common\Grav;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RegexIterator;
use function strlen;
/**
* Trait TestingAssetsTrait
* @package Grav\Common\Assets\Traits
*/
trait TestingAssetsTrait
{
/**
@@ -310,16 +319,16 @@ trait TestingAssetsTrait
*
* @param string $directory
* @param string $pattern (regex)
* @param string $ltrim Will be trimmed from the left of the file path
* @param string|null $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(
$iterator = new RegexIterator(new RecursiveIteratorIterator(new RecursiveDirectoryIterator(
$directory,
\FilesystemIterator::SKIP_DOTS
FilesystemIterator::SKIP_DOTS
)), $pattern);
$offset = \strlen($ltrim);
$offset = strlen($ltrim);
$files = [];
foreach ($iterator as $file) {

View File

@@ -9,6 +9,10 @@
namespace Grav\Common\Backup;
use DateTime;
use Exception;
use FilesystemIterator;
use GlobIterator;
use Grav\Common\Filesystem\Archiver;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Inflector;
@@ -19,8 +23,16 @@ use Grav\Common\Grav;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\File\JsonFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use SplFileInfo;
use stdClass;
use Symfony\Component\EventDispatcher\EventDispatcher;
use function count;
/**
* Class Backups
* @package Grav\Common\Backup
*/
class Backups
{
protected const BACKUP_FILENAME_REGEXZ = "#(.*)--(\d*).zip#";
@@ -33,32 +45,45 @@ class Backups
/** @var array|null */
protected static $backups;
/**
* @return void
*/
public function init()
{
$grav = Grav::instance();
/** @var EventDispatcher $dispatcher */
$dispatcher = Grav::instance()['events'];
$dispatcher = $grav['events'];
$dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
Grav::instance()->fireEvent('onBackupsInitialized', new Event(['backups' => $this]));
$grav->fireEvent('onBackupsInitialized', new Event(['backups' => $this]));
}
/**
* @return void
*/
public function setup()
{
if (null === static::$backup_dir) {
static::$backup_dir = Grav::instance()['locator']->findResource('backup://', true, true);
$grav = Grav::instance();
static::$backup_dir = $grav['locator']->findResource('backup://', true, true);
Folder::create(static::$backup_dir);
}
}
/**
* @param Event $event
* @return void
*/
public function onSchedulerInitialized(Event $event)
{
$grav = Grav::instance();
/** @var Scheduler $scheduler */
$scheduler = $event['scheduler'];
/** @var Inflector $inflector */
$inflector = Grav::instance()['inflector'];
$inflector = $grav['inflector'];
foreach (static::getBackupProfiles() as $id => $profile) {
$at = $profile['schedule_at'];
@@ -80,7 +105,7 @@ class Backups
public function getBackupDownloadUrl($backup, $base_url)
{
$param_sep = $param_sep = Grav::instance()['config']->get('system.param_sep', ':');
$download = urlencode(base64_encode($backup));
$download = urlencode(base64_encode(basename($backup)));
$url = rtrim(Grav::instance()['uri']->rootUrl(true), '/') . '/' . trim(
$base_url,
'/'
@@ -132,19 +157,21 @@ class Backups
{
if ($force || null === static::$backups) {
static::$backups = [];
$backups_itr = new \GlobIterator(static::$backup_dir . '/*.zip', \FilesystemIterator::KEY_AS_FILENAME);
$inflector = Grav::instance()['inflector'];
$grav = Grav::instance();
$backups_itr = new GlobIterator(static::$backup_dir . '/*.zip', FilesystemIterator::KEY_AS_FILENAME);
$inflector = $grav['inflector'];
$long_date_format = DATE_RFC2822;
/**
* @var string $name
* @var \SplFileInfo $file
* @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]);
$date = DateTime::createFromFormat(static::BACKUP_DATE_FORMAT, $matches[2]);
$timestamp = $date->getTimestamp();
$backup = new \stdClass();
$backup = new stdClass();
$backup->title = $inflector->titleize($matches[1]);
$backup->time = $date;
$backup->date = $date->format($long_date_format);
@@ -170,17 +197,19 @@ class Backups
*/
public static function backup($id = 0, callable $status = null)
{
$grav = Grav::instance();
$profiles = static::getBackupProfiles();
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$locator = $grav['locator'];
if (isset($profiles[$id])) {
$backup = (object) $profiles[$id];
} else {
throw new \RuntimeException('No backups defined...');
throw new RuntimeException('No backups defined...');
}
$name = Grav::instance()['inflector']->underscorize($backup->name);
$name = $grav['inflector']->underscorize($backup->name);
$date = date(static::BACKUP_DATE_FORMAT, time());
$filename = trim($name, '_') . '--' . $date . '.zip';
$destination = static::$backup_dir . DS . $filename;
@@ -194,7 +223,7 @@ class Backups
}
if (!file_exists($backup_root)) {
throw new \RuntimeException("Backup location: {$backup_root} does not exist...");
throw new RuntimeException("Backup location: {$backup_root} does not exist...");
}
$options = [
@@ -202,7 +231,6 @@ class Backups
'exclude_paths' => static::convertExclude($backup->exclude_paths ?? ''),
];
/** @var Archiver $archiver */
$archiver = Archiver::create('zip');
$archiver->setArchive($destination)->setOptions($options)->compress($backup_root, $status)->addEmptyFolders($options['exclude_paths'], $status);
@@ -221,16 +249,16 @@ class Backups
}
// Log the backup
Grav::instance()['log']->notice('Backup Created: ' . $destination);
$grav['log']->notice('Backup Created: ' . $destination);
// Fire Finished event
Grav::instance()->fireEvent('onBackupFinished', new Event(['backup' => $destination]));
$grav->fireEvent('onBackupFinished', new Event(['backup' => $destination]));
// Purge anything required
static::purge();
// Log
$log = JsonFile::instance(Grav::instance()['locator']->findResource("log://backup.log", true, true));
$log = JsonFile::instance($locator->findResource("log://backup.log", true, true));
$log->content([
'time' => time(),
'location' => $destination
@@ -241,7 +269,8 @@ class Backups
}
/**
* @throws \Exception
* @return void
* @throws Exception
*/
public static function purge()
{
@@ -261,7 +290,7 @@ class Backups
case 'time':
$last = end($backups);
$now = new \DateTime();
$now = new DateTime();
$interval = $now->diff($last->time);
if ($interval->days > $purge_config['max_backups_time']) {
unlink($last->path);
@@ -288,6 +317,7 @@ class Backups
protected static function convertExclude($exclude)
{
$lines = preg_split("/[\s,]+/", $exclude);
return array_map('trim', $lines, array_fill(0, \count($lines), '/'));
return array_map('trim', $lines, array_fill(0, count($lines), '/'));
}
}

View File

@@ -9,6 +9,8 @@
namespace Grav\Common;
use function donatj\UserAgent\parse_user_agent;
/**
* Internally uses the PhpUserAgent package https://github.com/donatj/PhpUserAgent
*/

View File

@@ -9,13 +9,21 @@
namespace Grav\Common;
use DirectoryIterator;
use \Doctrine\Common\Cache as DoctrineCache;
use Exception;
use Grav\Common\Config\Config;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Scheduler\Scheduler;
use LogicException;
use Psr\SimpleCache\CacheInterface;
use RocketTheme\Toolbox\Event\Event;
use Symfony\Component\EventDispatcher\EventDispatcher;
use function dirname;
use function extension_loaded;
use function function_exists;
use function in_array;
use function is_array;
/**
* The GravCache object is used throughout Grav to store and retrieve cached data.
@@ -113,7 +121,6 @@ class Cache extends Getters
* Initialization that sets a base key and the driver based on configuration settings
*
* @param Grav $grav
*
* @return void
*/
public function init(Grav $grav)
@@ -172,7 +179,7 @@ class Cache extends Getters
$current = basename($this->cache_dir);
$count = 0;
foreach (new \DirectoryIterator($cache_dir) as $file) {
foreach (new DirectoryIterator($cache_dir) as $file) {
$dir = $file->getBasename();
if ($dir === $current || $file->isDot() || $file->isFile()) {
continue;
@@ -189,6 +196,7 @@ class Cache extends Getters
* Public accessor to set the enabled state of the cache
*
* @param bool|int $enabled
* @return void
*/
public function setEnabled($enabled)
{
@@ -265,7 +273,7 @@ class Cache extends Getters
$driver = new DoctrineCache\MemcacheCache();
$driver->setMemcache($memcache);
} else {
throw new \LogicException('Memcache PHP extension has not been installed');
throw new LogicException('Memcache PHP extension has not been installed');
}
break;
@@ -279,7 +287,7 @@ class Cache extends Getters
$driver = new DoctrineCache\MemcachedCache();
$driver->setMemcached($memcached);
} else {
throw new \LogicException('Memcached PHP extension has not been installed');
throw new LogicException('Memcached PHP extension has not been installed');
}
break;
@@ -306,7 +314,7 @@ class Cache extends Getters
$driver = new DoctrineCache\RedisCache();
$driver->setRedis($redis);
} else {
throw new \LogicException('Redis PHP extension has not been installed');
throw new LogicException('Redis PHP extension has not been installed');
}
break;
@@ -322,7 +330,6 @@ class Cache extends Getters
* Gets a cached entry if it exists based on an id. If it does not exist, it returns false
*
* @param string $id the id of the cached entry
*
* @return mixed|bool returns the cached entry, can be any type, or false if doesn't exist
*/
public function fetch($id)
@@ -339,7 +346,7 @@ class Cache extends Getters
*
* @param string $id the id of the cached entry
* @param array|object|int $data the data for the cached entry to store
* @param int $lifetime the lifetime to store the entry in seconds
* @param int|null $lifetime the lifetime to store the entry in seconds
*/
public function save($id, $data, $lifetime = null)
{
@@ -397,6 +404,8 @@ class Cache extends Getters
/**
* Getter method to get the cache key
*
* @return string
*/
public function getKey()
{
@@ -405,6 +414,9 @@ class Cache extends Getters
/**
* Setter method to set key (Advanced)
*
* @param string $key
* @return void
*/
public function setKey($key)
{
@@ -416,7 +428,6 @@ class Cache extends Getters
* Helper method to clear all Grav caches
*
* @param string $remove standard|all|assets-only|images-only|cache-only
*
* @return array
*/
public static function clearCache($remove = 'standard')
@@ -491,7 +502,7 @@ class Cache extends Getters
if ($anything) {
$output[] = '<red>Cleared: </red>' . $path . '/*';
}
} catch (\Exception $e) {
} catch (Exception $e) {
// stream not found or another error while deleting files.
$output[] = '<red>ERROR: </red>' . $e->getMessage();
}
@@ -514,9 +525,14 @@ class Cache extends Getters
@opcache_reset();
}
Grav::instance()->fireEvent('onAfterCacheClear', new Event(['remove' => $remove, 'output' => &$output]));
return $output;
}
/**
* @return void
*/
public static function invalidateCache()
{
$user_config = USER_DIR . 'config/system.yaml';
@@ -538,6 +554,7 @@ class Cache extends Getters
* Set the cache lifetime programmatically
*
* @param int $future timestamp
* @return void
*/
public function setLifetime($future)
{
@@ -555,7 +572,7 @@ class Cache extends Getters
/**
* Retrieve the cache lifetime (in seconds)
*
* @return mixed
* @return int
*/
public function getLifetime()
{
@@ -569,7 +586,7 @@ class Cache extends Getters
/**
* Returns the current driver name
*
* @return mixed
* @return string
*/
public function getDriverName()
{
@@ -579,7 +596,7 @@ class Cache extends Getters
/**
* Returns the current driver setting
*
* @return mixed
* @return string
*/
public function getDriverSetting()
{
@@ -603,20 +620,30 @@ class Cache extends Getters
/**
* Static function to call as a scheduled Job to purge old Doctrine files
*
* @param bool $echo
*
* @return string|void
*/
public static function purgeJob()
public static function purgeJob($echo = false)
{
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
$deleted_folders = $cache->purgeOldCache();
$msg = 'Purged ' . $deleted_folders . ' old cache folders...';
echo 'Purged ' . $deleted_folders . ' old cache folders...';
if ($echo) {
echo $msg;
} else {
return $msg;
}
}
/**
* Static function to call as a scheduled Job to clear Grav cache
*
* @param string $type
* @return void
*/
public static function clearJob($type)
{
@@ -626,6 +653,10 @@ class Cache extends Getters
echo strip_tags(implode("\n", $result));
}
/**
* @param Event $event
* @return void
*/
public function onSchedulerInitialized(Event $event)
{
/** @var Scheduler $scheduler */
@@ -637,7 +668,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', [true], $name);
$job->at($at);
$job->output($logs);
$job->backlink('/config/system#caching');

View File

@@ -9,6 +9,12 @@
namespace Grav\Common;
use function function_exists;
/**
* Class Composer
* @package Grav\Common
*/
class Composer
{
/** @const Default composer location */
@@ -21,7 +27,7 @@ class Composer
*/
public static function getComposerLocation()
{
if (!\function_exists('shell_exec') || stripos(PHP_OS, 'win') === 0) {
if (!function_exists('shell_exec') || stripos(PHP_OS, 'win') === 0) {
return self::DEFAULT_PATH;
}

View File

@@ -9,8 +9,17 @@
namespace Grav\Common\Config;
use BadMethodCallException;
use Exception;
use RocketTheme\Toolbox\File\PhpFile;
use RuntimeException;
use function get_class;
use function is_array;
/**
* Class CompiledBase
* @package Grav\Common\Config
*/
abstract class CompiledBase
{
/** @var int Version number for the compiled file. */
@@ -41,12 +50,12 @@ abstract class CompiledBase
* @param string $cacheFolder Cache folder to be used.
* @param array $files List of files as returned from ConfigFileFinder class.
* @param string $path Base path for the file list.
* @throws \BadMethodCallException
* @throws BadMethodCallException
*/
public function __construct($cacheFolder, array $files, $path)
{
if (!$cacheFolder) {
throw new \BadMethodCallException('Cache folder not defined.');
throw new BadMethodCallException('Cache folder not defined.');
}
$this->path = $path ? rtrim($path, '\\/') . '/' : '';
@@ -57,7 +66,7 @@ abstract class CompiledBase
/**
* Get filename for the compiled PHP file.
*
* @param string $name
* @param string|null $name
* @return $this
*/
public function name($name = null)
@@ -71,6 +80,8 @@ abstract class CompiledBase
/**
* Function gets called when cached configuration is saved.
*
* @return void
*/
public function modified()
{
@@ -133,11 +144,14 @@ abstract class CompiledBase
* Create configuration object.
*
* @param array $data
* @return void
*/
abstract protected function createObject(array $data = []);
/**
* Finalize configuration object.
*
* @return void
*/
abstract protected function finalizeObject();
@@ -185,9 +199,9 @@ 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)
|| $cache['@class'] !== get_class($this)
) {
return false;
}
@@ -209,7 +223,7 @@ abstract class CompiledBase
* Save compiled file.
*
* @param string $filename
* @throws \RuntimeException
* @throws RuntimeException
* @internal
*/
protected function saveCompiledFile($filename)
@@ -219,7 +233,7 @@ abstract class CompiledBase
// Attempt to lock the file for writing.
try {
$file->lock(false);
} catch (\Exception $e) {
} catch (Exception $e) {
// Another process has locked the file; we will check this in a bit.
}
@@ -229,7 +243,7 @@ abstract class CompiledBase
}
$cache = [
'@class' => \get_class($this),
'@class' => get_class($this),
'timestamp' => time(),
'checksum' => $this->checksum(),
'files' => $this->files,

View File

@@ -70,6 +70,8 @@ class CompiledBlueprints extends CompiledBase
/**
* Finalize configuration object.
*
* @return void
*/
protected function finalizeObject()
{
@@ -80,6 +82,7 @@ class CompiledBlueprints extends CompiledBase
*
* @param string $name Name of the position.
* @param array $files Files to be loaded.
* @return void
*/
protected function loadFile($name, $files)
{

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
/**
* Class CompiledConfig
* @package Grav\Common\Config
*/
class CompiledConfig extends CompiledBase
{
/** @var callable Blueprints loader. */
@@ -60,6 +64,7 @@ class CompiledConfig extends CompiledBase
* Create configuration object.
*
* @param array $data
* @return void
*/
protected function createObject(array $data = [])
{
@@ -73,6 +78,8 @@ class CompiledConfig extends CompiledBase
/**
* Finalize configuration object.
*
* @return void
*/
protected function finalizeObject()
{
@@ -82,6 +89,8 @@ class CompiledConfig extends CompiledBase
/**
* Function gets called when cached configuration is saved.
*
* @return void
*/
public function modified()
{
@@ -93,6 +102,7 @@ class CompiledConfig extends CompiledBase
*
* @param string $name Name of the position.
* @param string $filename File to be loaded.
* @return void
*/
protected function loadFile($name, $filename)
{

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
/**
* Class CompiledLanguages
* @package Grav\Common\Config
*/
class CompiledLanguages extends CompiledBase
{
/**
@@ -30,6 +34,7 @@ class CompiledLanguages extends CompiledBase
* Create configuration object.
*
* @param array $data
* @return void
*/
protected function createObject(array $data = [])
{
@@ -38,6 +43,8 @@ class CompiledLanguages extends CompiledBase
/**
* Finalize configuration object.
*
* @return void
*/
protected function finalizeObject()
{
@@ -48,6 +55,8 @@ class CompiledLanguages extends CompiledBase
/**
* Function gets called when cached configuration is saved.
*
* @return void
*/
public function modified()
{
@@ -59,6 +68,7 @@ class CompiledLanguages extends CompiledBase
*
* @param string $name Name of the position.
* @param string $filename File to be loaded.
* @return void
*/
protected function loadFile($name, $filename)
{

View File

@@ -15,6 +15,10 @@ use Grav\Common\Data\Data;
use Grav\Common\Service\ConfigServiceProvider;
use Grav\Common\Utils;
/**
* Class Config
* @package Grav\Common\Config
*/
class Config extends Data
{
/** @var string */
@@ -55,7 +59,7 @@ class Config extends Data
}
/**
* @param null $modified
* @param bool|null $modified
* @return bool
*/
public function modified($modified = null)
@@ -68,7 +72,7 @@ class Config extends Data
}
/**
* @param null $timestamp
* @param int|null $timestamp
* @return int
*/
public function timestamp($timestamp = null)
@@ -105,6 +109,9 @@ class Config extends Data
return $this;
}
/**
* @return void
*/
public function debug()
{
/** @var Debugger $debugger */
@@ -116,6 +123,9 @@ class Config extends Data
}
}
/**
* @return void
*/
public function init()
{
$setup = Grav::instance()['setup']->toArray();

View File

@@ -9,8 +9,14 @@
namespace Grav\Common\Config;
use DirectoryIterator;
use Grav\Common\Filesystem\Folder;
use RecursiveDirectoryIterator;
/**
* Class ConfigFileFinder
* @package Grav\Common\Config
*/
class ConfigFileFinder
{
/** @var string */
@@ -41,6 +47,7 @@ class ConfigFileFinder
foreach ($paths as $folder) {
$list += $this->detectRecursive($folder, $pattern, $levels);
}
return $list;
}
@@ -62,6 +69,7 @@ class ConfigFileFinder
$list += $files[trim($path, '/')];
}
return $list;
}
@@ -79,6 +87,7 @@ class ConfigFileFinder
foreach ($paths as $folder) {
$list = array_merge_recursive($list, $this->detectAll($folder, $pattern, $levels));
}
return $list;
}
@@ -97,6 +106,7 @@ class ConfigFileFinder
foreach ($folders as $folder) {
$list += $this->detectInFolder($folder, $filename);
}
return $list;
}
@@ -104,7 +114,7 @@ class ConfigFileFinder
* Find filename from a list of folders.
*
* @param array $folders
* @param string $filename
* @param string|null $filename
* @return array
*/
public function locateInFolders(array $folders, $filename = null)
@@ -114,6 +124,7 @@ class ConfigFileFinder
$path = trim(Folder::getRelativePath($folder), '/');
$list[$path] = $this->detectInFolder($folder, $filename);
}
return $list;
}
@@ -167,7 +178,7 @@ class ConfigFileFinder
'filters' => [
'pre-key' => $this->base,
'key' => $pattern,
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
'value' => function (RecursiveDirectoryIterator $file) use ($path) {
return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
}
],
@@ -188,7 +199,7 @@ class ConfigFileFinder
* Detects all directories with the lookup file and returns them with last modification time.
*
* @param string $folder Location to look up from.
* @param string $lookup Filename to be located (defaults to directory name).
* @param string|null $lookup Filename to be located (defaults to directory name).
* @return array
* @internal
*/
@@ -201,9 +212,7 @@ class ConfigFileFinder
$list = [];
if (is_dir($folder)) {
$iterator = new \DirectoryIterator($folder);
/** @var \DirectoryIterator $directory */
$iterator = new DirectoryIterator($folder);
foreach ($iterator as $directory) {
if (!$directory->isDir() || $directory->isDot()) {
continue;
@@ -245,7 +254,7 @@ class ConfigFileFinder
'filters' => [
'pre-key' => $this->base,
'key' => $pattern,
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
'value' => function (RecursiveDirectoryIterator $file) use ($path) {
return ["{$path}/{$file->getSubPathname()}" => $file->getMTime()];
}
],

View File

@@ -12,6 +12,10 @@ namespace Grav\Common\Config;
use Grav\Common\Data\Data;
use Grav\Common\Utils;
/**
* Class Languages
* @package Grav\Common\Config
*/
class Languages extends Data
{
/** @var string|null */
@@ -62,6 +66,9 @@ class Languages extends Data
return $this->timestamp;
}
/**
* @return void
*/
public function reformat()
{
if (isset($this->items['plugins'])) {
@@ -72,6 +79,7 @@ class Languages extends Data
/**
* @param array $data
* @return void
*/
public function mergeRecursive(array $data)
{

View File

@@ -9,13 +9,22 @@
namespace Grav\Common\Config;
use BadMethodCallException;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Data\Data;
use Grav\Common\Utils;
use InvalidArgumentException;
use Pimple\Container;
use Psr\Http\Message\ServerRequestInterface;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use function defined;
use function is_array;
/**
* Class Setup
* @package Grav\Common\Config
*/
class Setup extends Data
{
/**
@@ -60,13 +69,13 @@ class Setup extends Data
'blueprints' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['environment://blueprints', 'user://blueprints', 'system/blueprints'],
'' => ['environment://blueprints', 'user://blueprints', 'system://blueprints'],
]
],
'config' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['environment://config', 'user://config', 'system/config'],
'' => ['environment://config', 'user://config', 'system://config'],
]
],
'plugins' => [
@@ -90,7 +99,7 @@ class Setup extends Data
'languages' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['environment://languages', 'user://languages', 'system/languages'],
'' => ['environment://languages', 'user://languages', 'system://languages'],
]
],
'cache' => [
@@ -156,7 +165,7 @@ class Setup extends Data
{
// If no environment is set, make sure we get one (CLI or hostname).
if (!static::$environment) {
if (\defined('GRAV_CLI')) {
if (defined('GRAV_CLI')) {
static::$environment = 'cli';
} else {
/** @var ServerRequestInterface $request */
@@ -168,12 +177,12 @@ class Setup extends Data
}
// Resolve server aliases to the proper environment.
$environment = $this->environments[static::$environment] ?? static::$environment;
$environment = static::$environments[static::$environment] ?? static::$environment;
// Pre-load setup.php which contains our initial configuration.
// Configuration may contain dynamic parts, which is why we need to always load it.
// If "GRAV_SETUP_PATH" has been defined, use it, otherwise use defaults.
$file = \defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
$file = defined('GRAV_SETUP_PATH') ? GRAV_SETUP_PATH : GRAV_ROOT . '/setup.php';
$setup = is_file($file) ? (array) include $file : [];
// Add default streams defined in beginning of the class.
@@ -187,13 +196,16 @@ class Setup extends Data
// Set up environment.
$this->def('environment', $environment);
$this->def('streams.schemes.environment.prefixes', ['' => ["user://{$this->get('environment')}"]]);
$this->def(
'streams.schemes.environment.prefixes',
['' => ["user://env/{$this->get('environment')}", "user://{$this->get('environment')}"]]
);
}
/**
* @return $this
* @throws \RuntimeException
* @throws \InvalidArgumentException
* @throws RuntimeException
* @throws InvalidArgumentException
*/
public function init()
{
@@ -221,7 +233,7 @@ class Setup extends Data
} while (--$guard);
if (!$guard) {
throw new \RuntimeException('Setup: Configuration reload loop detected!');
throw new RuntimeException('Setup: Configuration reload loop detected!');
}
// Make sure we have valid setup.
@@ -234,7 +246,8 @@ class Setup extends Data
* Initialize resource locator by using the configuration.
*
* @param UniformResourceLocator $locator
* @throws \BadMethodCallException
* @return void
* @throws BadMethodCallException
*/
public function initializeLocator(UniformResourceLocator $locator)
{
@@ -280,24 +293,42 @@ class Setup extends Data
/**
* @param UniformResourceLocator $locator
* @throws \InvalidArgumentException
* @throws \BadMethodCallException
* @throws \RuntimeException
* @throws InvalidArgumentException
* @throws BadMethodCallException
* @throws RuntimeException
*/
protected function check(UniformResourceLocator $locator)
{
$streams = $this->items['streams']['schemes'] ?? null;
if (!\is_array($streams)) {
throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
if (!is_array($streams)) {
throw new InvalidArgumentException('Configuration is missing streams.schemes!');
}
$diff = array_keys(array_diff_key($this->streams, $streams));
if ($diff) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
);
}
try {
// If environment is found, remove all missing override locations (B/C compatibility).
if ($locator->findResource('environment://', true)) {
$prefixes = $this->get('streams.schemes.environment.prefixes.');
$update = false;
foreach ($prefixes as $i => $prefix) {
if ($locator->findResource($prefix, true)) {
break;
}
unset($prefixes[$i]);
$update = true;
}
if ($update) {
$this->set('streams.schemes.environment.prefixes', ['' => array_values($prefixes)]);
$this->initializeLocator($locator);
}
}
if (!$locator->findResource('environment://config', true)) {
// If environment does not have its own directory, remove it from the lookup.
$this->set('streams.schemes.environment.prefixes', ['config' => []]);
@@ -315,8 +346,8 @@ class Setup extends Data
$security_file->save();
$security_file->free();
}
} catch (\RuntimeException $e) {
throw new \RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
} catch (RuntimeException $e) {
throw new RuntimeException(sprintf('Grav failed to initialize: %s', $e->getMessage()), 500, $e);
}
}
}

View File

@@ -14,7 +14,20 @@ use Grav\Common\Grav;
use Grav\Common\User\Interfaces\UserInterface;
use RocketTheme\Toolbox\Blueprints\BlueprintForm;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use function call_user_func_array;
use function count;
use function function_exists;
use function in_array;
use function is_array;
use function is_int;
use function is_object;
use function is_string;
/**
* Class Blueprint
* @package Grav\Common\Data
*/
class Blueprint extends BlueprintForm
{
/** @var string */
@@ -35,6 +48,9 @@ class Blueprint extends BlueprintForm
/** @var array */
protected $handlers = [];
/**
* Clone blueprint.
*/
public function __clone()
{
if ($this->blueprintSchema) {
@@ -84,9 +100,9 @@ class Blueprint extends BlueprintForm
$current = $this->getDefaults();
foreach ($path as $field) {
if (\is_object($current) && isset($current->{$field})) {
if (is_object($current) && isset($current->{$field})) {
$current = $current->{$field};
} elseif (\is_array($current) && isset($current[$field])) {
} elseif (is_array($current) && isset($current[$field])) {
$current = $current[$field];
} else {
return null;
@@ -127,7 +143,7 @@ class Blueprint extends BlueprintForm
$current = &$this->items;
foreach ($path as $field) {
if (\is_object($current)) {
if (is_object($current)) {
// Handle objects.
if (!isset($current->{$field})) {
$current->{$field} = [];
@@ -136,7 +152,7 @@ class Blueprint extends BlueprintForm
$current = &$current->{$field};
} else {
// Handle arrays and scalars.
if (!\is_array($current)) {
if (!is_array($current)) {
$current = [$field => []];
} elseif (!isset($current[$field])) {
$current[$field] = [];
@@ -164,12 +180,44 @@ class Blueprint extends BlueprintForm
return $this;
}
/**
* Extend blueprint with another blueprint.
*
* @param BlueprintForm|array $extends
* @param bool $append
* @return $this
*/
public function extend($extends, $append = false)
{
parent::extend($extends, $append);
$this->deepInit($this->items);
return $this;
}
/**
* @param string $name
* @param mixed $value
* @param string $separator
* @param bool $append
* @return $this
*/
public function embed($name, $value, $separator = '/', $append = false)
{
parent::embed($name, $value, $separator, $append);
$this->deepInit($this->items);
return $this;
}
/**
* Merge two arrays by using blueprints.
*
* @param array $data1
* @param array $data2
* @param string $name Optional
* @param string|null $name Optional
* @param string $separator Optional
* @return array
*/
@@ -212,13 +260,15 @@ class Blueprint extends BlueprintForm
* Validate data against blueprints.
*
* @param array $data
* @throws \RuntimeException
* @param array $options
* @return void
* @throws RuntimeException
*/
public function validate(array $data)
public function validate(array $data, array $options = [])
{
$this->initInternals();
$this->blueprintSchema->validate($data);
$this->blueprintSchema->validate($data, $options);
}
/**
@@ -266,6 +316,7 @@ class Blueprint extends BlueprintForm
/**
* @param string $name
* @param callable $callable
* @return void
*/
public function addDynamicHandler(string $name, callable $callable): void
{
@@ -274,6 +325,8 @@ class Blueprint extends BlueprintForm
/**
* Initialize validator.
*
* @return void
*/
protected function initInternals()
{
@@ -307,7 +360,7 @@ class Blueprint extends BlueprintForm
/**
* @param string|array $path
* @param string $context
* @param string|null $context
* @return array
*/
protected function getFiles($path, $context = null)
@@ -315,9 +368,13 @@ class Blueprint extends BlueprintForm
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if (\is_string($path) && !$locator->isStream($path)) {
if (is_string($path) && !$locator->isStream($path)) {
// Find path overrides.
$paths = (array) ($this->overrides[$path] ?? null);
if (null === $context) {
$paths = (array) ($this->overrides[$path] ?? null);
} else {
$paths = [];
}
// Add path pointing to default context.
if ($context === null) {
@@ -341,7 +398,7 @@ class Blueprint extends BlueprintForm
$files = [];
foreach ($paths as $lookup) {
if (\is_string($lookup) && strpos($lookup, '://')) {
if (is_string($lookup) && strpos($lookup, '://')) {
$files = array_merge($files, $locator->findResources($lookup));
} else {
$files[] = $lookup;
@@ -355,12 +412,13 @@ class Blueprint extends BlueprintForm
* @param array $field
* @param string $property
* @param array $call
* @return void
*/
protected function dynamicData(array &$field, $property, array &$call)
{
$params = $call['params'];
if (\is_array($params)) {
if (is_array($params)) {
$function = array_shift($params);
} else {
$function = $params;
@@ -371,18 +429,18 @@ class Blueprint extends BlueprintForm
$data = null;
if (!$f) {
if (\function_exists($o)) {
$data = \call_user_func_array($o, $params);
if (function_exists($o)) {
$data = call_user_func_array($o, $params);
}
} else {
if (method_exists($o, $f)) {
$data = \call_user_func_array([$o, $f], $params);
$data = call_user_func_array([$o, $f], $params);
}
}
// If function returns a value,
if (null !== $data) {
if (\is_array($data) && isset($field[$property]) && \is_array($field[$property])) {
if (is_array($data) && isset($field[$property]) && is_array($field[$property])) {
// Combine field and @data-field together.
$field[$property] += $data;
} else {
@@ -396,15 +454,33 @@ class Blueprint extends BlueprintForm
* @param array $field
* @param string $property
* @param array $call
* @return void
*/
protected function dynamicConfig(array &$field, $property, array &$call)
{
$value = $call['params'];
$params = $call['params'];
if (is_array($params)) {
$value = array_shift($params);
$params = array_shift($params);
} else {
$value = $params;
$params = [];
}
$default = $field[$property] ?? null;
$config = Grav::instance()['config']->get($value, $default);
if (!empty($field['value_only'])) {
$config = array_combine($config, $config);
}
if (null !== $config) {
$field[$property] = $config;
if (!empty($params['append']) && is_array($config) && isset($field[$property]) && is_array($field[$property])) {
// Combine field and @config-field together.
$field[$property] += $config;
} else {
// Or create/replace field with @config-field.
$field[$property] = $config;
}
}
}
@@ -412,6 +488,7 @@ class Blueprint extends BlueprintForm
* @param array $field
* @param string $property
* @param array $call
* @return void
*/
protected function dynamicSecurity(array &$field, $property, array &$call)
{
@@ -433,6 +510,12 @@ class Blueprint extends BlueprintForm
}
}
/**
* @param UserInterface $user
* @param array $actions
* @param string $op
* @return bool
*/
protected function resolveActions(UserInterface $user, array $actions, string $op = 'and')
{
$c = $i = count($actions);
@@ -455,6 +538,7 @@ class Blueprint extends BlueprintForm
* @param array $field
* @param string $property
* @param array $call
* @return void
*/
protected function dynamicScope(array &$field, $property, array &$call)
{
@@ -463,7 +547,7 @@ class Blueprint extends BlueprintForm
}
$scopes = (array)$call['params'];
$matches = \in_array($this->scope, $scopes, true);
$matches = in_array($this->scope, $scopes, true);
if ($this->scope && $property !== 'ignore') {
$matches = !$matches;
}
@@ -481,7 +565,7 @@ class Blueprint extends BlueprintForm
*/
protected function addPropertyRecursive(array &$field, $property, $value)
{
if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
if (is_array($value) && isset($field[$property]) && is_array($field[$property])) {
$field[$property] = array_merge_recursive($field[$property], $value);
} else {
$field[$property] = $value;

View File

@@ -9,15 +9,26 @@
namespace Grav\Common\Data;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
use RocketTheme\Toolbox\Blueprints\BlueprintSchema as BlueprintSchemaBase;
use RuntimeException;
use function is_array;
use function is_string;
/**
* Class BlueprintSchema
* @package Grav\Common\Data
*/
class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{
use Export;
/** @var array */
protected $filter = ['validation' => true, 'xss_check' => true];
/** @var array */
protected $ignoreFormKeys = [
'title' => true,
@@ -49,13 +60,15 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
* Validate data against blueprints.
*
* @param array $data
* @throws \RuntimeException
* @param array $options
* @throws RuntimeException
*/
public function validate(array $data)
public function validate(array $data, array $options = [])
{
try {
$messages = $this->validateArray($data, $this->nested, $this->items['']['form'] ?? []);
} catch (\RuntimeException $e) {
$validation = $this->items['']['form']['validation'] ?? 'loose';
$messages = $this->validateArray($data, $this->nested, $validation === 'strict', $options['xss_check'] ?? true);
} catch (RuntimeException $e) {
throw (new ValidationException($e->getMessage(), $e->getCode(), $e))->setMessages();
}
@@ -131,17 +144,19 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
/**
* @param array $data
* @param array $rules
* @param array $parent
* @param bool $strict
* @param bool $xss
* @return array
* @throws \RuntimeException
* @throws RuntimeException
*/
protected function validateArray(array $data, array $rules, array $parent)
protected function validateArray(array $data, array $rules, bool $strict, bool $xss = true)
{
$messages = $this->checkRequired($data, $rules);
foreach ($data as $key => $child) {
$val = $rules[$key] ?? $rules['*'] ?? null;
$rule = \is_string($val) ? $this->items[$val] : null;
$rule = is_string($val) ? $this->items[$val] : null;
$checkXss = $xss;
if ($rule) {
// Item has been defined in blueprints.
@@ -151,12 +166,25 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
}
$messages += Validation::validate($child, $rule);
} elseif (\is_array($child) && \is_array($val)) {
} elseif (is_array($child) && is_array($val)) {
// Array has been defined in blueprints.
$messages += $this->validateArray($child, $val, $rule ?? []);
} elseif (isset($parent['validation']) && $parent['validation'] === 'strict') {
// Undefined/extra item.
throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));
$messages += $this->validateArray($child, $val, $strict);
$checkXss = false;
} elseif ($strict) {
// Undefined/extra item in strict mode.
/** @var Config $config */
$config = Grav::instance()['config'];
if (!$config->get('system.strict_mode.blueprint_strict_compat', true)) {
throw new RuntimeException(sprintf('%s is not defined in blueprints', $key));
}
user_error(sprintf('Having extra key %s in your data is deprecated with blueprint having \'validation: strict\'', $key), E_USER_DEPRECATED);
}
if ($checkXss) {
$messages += Validation::checkSafety($child, $rule ?: ['name' => $key]);
}
}
@@ -175,28 +203,9 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{
$results = [];
if ($missingValuesAsNull) {
// 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 (!$rule || !empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
continue;
}
$results[$key] = null;
}
}
}
foreach ($data as $key => $field) {
if (null === $field && !$missingValuesAsNull) {
continue;
}
$val = $rules[$key] ?? $rules['*'] ?? null;
$rule = \is_string($val) ? $this->items[$val] : $this->items[$parent . $key] ?? null;
$rule = is_string($val) ? $this->items[$val] : $this->items[$parent . $key] ?? null;
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
// Skip any data in the ignored field.
@@ -204,11 +213,24 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
continue;
}
if ($rule && $rule['type'] !== '_parent') {
if (null === $field) {
if ($missingValuesAsNull) {
$results[$key] = null;
} else {
unset($results[$key]);
}
continue;
}
$isParent = isset($val['*']);
$type = $rule['type'] ?? null;
if (!$isParent && $type && $type !== '_parent') {
$field = Validation::filter($field, $rule);
} elseif (\is_array($field) && \is_array($val)) {
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$field = $this->filterArray($field, $val, $parent . $key . '.', $missingValuesAsNull, $keepEmptyValues);
$k = $isParent ? '*' : $key;
$field = $this->filterArray($field, $val, $parent . $k . '.', $missingValuesAsNull, $keepEmptyValues);
if (null === $field) {
// Nested parent has no values.
@@ -220,7 +242,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
continue;
}
if ($keepEmptyValues || (null !== $field && (!\is_array($field) || !empty($field)))) {
if ($keepEmptyValues || (null !== $field && (!is_array($field) || !empty($field)))) {
$results[$key] = $field;
}
}
@@ -228,6 +250,11 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
return $results ?: null;
}
/**
* @param array $nested
* @param string $parent
* @return void
*/
protected function buildIgnoreNested(array $nested, $parent = '')
{
$ignore = true;
@@ -263,8 +290,19 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
continue;
}
if (is_array($value)) {
// Special toggle handling for all the nested data.
$toggle = $toggles[$key] ?? [];
if (!is_array($toggle)) {
if (!$toggle) {
$data[$key] = null;
continue;
}
$toggle = [];
}
// Recursively fetch the items.
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggles[$key] ?? [], $value);
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggle, $value);
} else {
$field = $this->get($value);
// Do not add the field if:
@@ -299,7 +337,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
$messages = [];
foreach ($fields as $name => $field) {
if (!\is_string($field)) {
if (!is_string($field)) {
continue;
}
@@ -340,6 +378,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
* @param array $field
* @param string $property
* @param array $call
* @return void
*/
protected function dynamicConfig(array &$field, $property, array &$call)
{

View File

@@ -9,9 +9,17 @@
namespace Grav\Common\Data;
use DirectoryIterator;
use Grav\Common\Grav;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use function is_array;
use function is_object;
/**
* Class Blueprints
* @package Grav\Common\Data
*/
class Blueprints
{
/** @var array|string */
@@ -34,7 +42,7 @@ class Blueprints
*
* @param string $type Blueprint type.
* @return Blueprint
* @throws \RuntimeException
* @throws RuntimeException
*/
public function get($type)
{
@@ -65,10 +73,9 @@ class Blueprints
if ($locator->isStream($this->search)) {
$iterator = $locator->getIterator($this->search);
} else {
$iterator = new \DirectoryIterator($this->search);
$iterator = new DirectoryIterator($this->search);
}
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
if (!$file->isFile() || '.' . $file->getExtension() !== YAML_EXT) {
continue;
@@ -92,7 +99,7 @@ class Blueprints
{
$blueprint = new Blueprint($name);
if (\is_array($this->search) || \is_object($this->search)) {
if (is_array($this->search) || is_object($this->search)) {
// Page types.
$blueprint->setOverrides($this->search);
$blueprint->setContext('blueprints://pages');
@@ -102,7 +109,7 @@ class Blueprints
try {
$blueprint->load()->init();
} catch (\RuntimeException $e) {
} catch (RuntimeException $e) {
$log = Grav::instance()['log'];
$log->error(sprintf('Blueprint %s cannot be loaded: %s', $name, $e->getMessage()));

View File

@@ -9,12 +9,22 @@
namespace Grav\Common\Data;
use Exception;
use RocketTheme\Toolbox\ArrayTraits\Countable;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
use RocketTheme\Toolbox\File\FileInterface;
use RuntimeException;
use function func_get_args;
use function is_array;
use function is_callable;
use function is_object;
/**
* Class Data
* @package Grav\Common\Data
*/
class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable, ExportInterface
{
use NestedArrayAccessWithGetters, Countable, Export;
@@ -23,7 +33,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
protected $gettersVariable = 'items';
/** @var array */
protected $items;
/** @var Blueprint|null */
/** @var Blueprint */
protected $blueprints;
/** @var FileInterface|null */
protected $storage;
@@ -40,7 +50,9 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
public function __construct(array $items = [], $blueprints = null)
{
$this->items = $items;
$this->blueprints = $blueprints;
if (null !== $blueprints) {
$this->blueprints = $blueprints;
}
}
/**
@@ -87,20 +99,20 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return $this
* @throws \RuntimeException
* @throws RuntimeException
*/
public function join($name, $value, $separator = '.')
{
$old = $this->get($name, null, $separator);
if ($old !== null) {
if (!\is_array($old)) {
throw new \RuntimeException('Value ' . $old);
if (!is_array($old)) {
throw new RuntimeException('Value ' . $old);
}
if (\is_object($value)) {
if (is_object($value)) {
$value = (array) $value;
} elseif (!\is_array($value)) {
throw new \RuntimeException('Value ' . $value);
} elseif (!is_array($value)) {
throw new RuntimeException('Value ' . $value);
}
$value = $this->blueprints()->mergeData($old, $value, $name, $separator);
@@ -133,7 +145,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
*/
public function joinDefaults($name, $value, $separator = '.')
{
if (\is_object($value)) {
if (is_object($value)) {
$value = (array) $value;
}
@@ -154,14 +166,14 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
* @param array|object $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return array
* @throws \RuntimeException
* @throws RuntimeException
*/
public function getJoined($name, $value, $separator = '.')
{
if (\is_object($value)) {
if (is_object($value)) {
$value = (array) $value;
} elseif (!\is_array($value)) {
throw new \RuntimeException('Value ' . $value);
} elseif (!is_array($value)) {
throw new RuntimeException('Value ' . $value);
}
$old = $this->get($name, null, $separator);
@@ -171,8 +183,8 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
return $value;
}
if (!\is_array($old)) {
throw new \RuntimeException('Value ' . $old);
if (!is_array($old)) {
throw new RuntimeException('Value ' . $old);
}
// Return joined data.
@@ -210,7 +222,7 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
* Validate by blueprints.
*
* @return $this
* @throws \Exception
* @throws Exception
*/
public function validate()
{
@@ -251,18 +263,19 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
public function blueprints()
{
if (!$this->blueprints) {
$this->blueprints = new Blueprint;
} elseif (\is_callable($this->blueprints)) {
$this->blueprints = new Blueprint();
} elseif (is_callable($this->blueprints)) {
// Lazy load blueprints.
$blueprints = $this->blueprints;
$this->blueprints = $blueprints();
}
return $this->blueprints;
}
/**
* Save data if storage has been defined.
* @throws \RuntimeException
* @throws RuntimeException
*/
public function save()
{
@@ -315,6 +328,9 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
return $this->storage;
}
/**
* @return array
*/
public function jsonSerialize()
{
return $this->items;

View File

@@ -9,8 +9,13 @@
namespace Grav\Common\Data;
use Exception;
use RocketTheme\Toolbox\File\FileInterface;
/**
* Interface DataInterface
* @package Grav\Common\Data
*/
interface DataInterface
{
/**
@@ -41,7 +46,7 @@ interface DataInterface
/**
* Validate by blueprints.
*
* @throws \Exception
* @throws Exception
*/
public function validate();

View File

@@ -9,10 +9,29 @@
namespace Grav\Common\Data;
use ArrayAccess;
use Countable;
use DateTime;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Security;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\Utils;
use Grav\Common\Yaml;
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
use Traversable;
use function count;
use function is_array;
use function is_bool;
use function is_float;
use function is_int;
use function is_string;
/**
* Class Validation
* @package Grav\Common\Data
*/
class Validation
{
/**
@@ -78,6 +97,91 @@ class Validation
return $messages;
}
/**
* @param mixed $value
* @param array $field
*/
public static function checkSafety($value, array $field)
{
$messages = [];
$type = $field['validate']['type'] ?? $field['type'] ?? 'text';
$options = $field['xss_check'] ?? [];
if ($options === false || $type === 'unset') {
return $messages;
}
if (!is_array($options)) {
$options = [];
}
$name = ucfirst($field['label'] ?? $field['name'] ?? 'UNKNOWN');
/** @var UserInterface $user */
$user = Grav::instance()['user'] ?? null;
/** @var Config $config */
$config = Grav::instance()['config'];
$xss_whitelist = $config->get('security.xss_whitelist', 'admin.super');
// Get language class.
/** @var Language $language */
$language = Grav::instance()['language'];
if (!static::authorize($xss_whitelist, $user)) {
$defaults = Security::getXssDefaults();
$options += $defaults;
$options['enabled_rules'] += $defaults['enabled_rules'];
if (!empty($options['safe_protocols'])) {
$options['invalid_protocols'] = array_diff($options['invalid_protocols'], $options['safe_protocols']);
}
if (!empty($options['safe_tags'])) {
$options['dangerous_tags'] = array_diff($options['dangerous_tags'], $options['safe_tags']);
}
if (is_string($value)) {
$violation = Security::detectXss($value, $options);
if ($violation) {
$messages[$name][] = $language->translate(['GRAV.FORM.XSS_ISSUES', $language->translate($name)], null, true);
}
} elseif (is_array($value)) {
$violations = Security::detectXssFromArray($value, "{$name}.", $options);
if ($violations) {
$messages[$name][] = $language->translate(['GRAV.FORM.XSS_ISSUES', $language->translate($name)], null, true);
}
}
}
return $messages;
}
/**
* Checks user authorisation to the action.
*
* @param string|string[] $action
* @param UserInterface|null $user
* @return bool
*/
public static function authorize($action, UserInterface $user = null)
{
if (!$user) {
return false;
}
$action = (array)$action;
foreach ($action as $a) {
// Ignore 'admin.super' if it's not the only value to be checked.
if ($a === 'admin.super' && count($action) > 1 && $user instanceof FlexObjectInterface) {
continue;
}
if ($user->authorize($a)) {
return true;
}
}
return false;
}
/**
* Filter value against a blueprint field definition.
*
@@ -123,7 +227,7 @@ class Validation
*/
public static function typeText($value, array $params, array $field)
{
if (!\is_string($value) && !is_numeric($value)) {
if (!is_string($value) && !is_numeric($value)) {
return false;
}
@@ -161,7 +265,7 @@ class Validation
*/
protected static function filterText($value, array $params, array $field)
{
if (!\is_string($value) && !is_numeric($value)) {
if (!is_string($value) && !is_numeric($value)) {
return '';
}
@@ -194,7 +298,7 @@ class Validation
*/
protected static function filterCommaList($value, array $params, array $field)
{
return \is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
return is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
}
/**
@@ -205,7 +309,18 @@ class Validation
*/
public static function typeCommaList($value, array $params, array $field)
{
return \is_array($value) ? true : self::typeText($value, $params, $field);
return is_array($value) ? true : self::typeText($value, $params, $field);
}
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return array|array[]|false|string[]
*/
protected static function filterLines($value, array $params, array $field)
{
return is_array($value) ? $value : preg_split('/\s*[\r\n]+\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
}
/**
@@ -338,7 +453,7 @@ class Validation
*/
public static function typeToggle($value, array $params, array $field)
{
if (\is_bool($value)) {
if (is_bool($value)) {
$value = (int)$value;
}
@@ -430,7 +545,7 @@ class Validation
{
$format = Grav::instance()['config']->get('system.pages.dateformat.default');
if ($format) {
$converted = new \DateTime($value);
$converted = new DateTime($value);
return $converted->format($format);
}
return $value;
@@ -483,7 +598,7 @@ class Validation
*/
public static function typeEmail($value, array $params, array $field)
{
$values = !\is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
$values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
foreach ($values as $val) {
if (!(self::typeText($val, $params, $field) && filter_var($val, FILTER_VALIDATE_EMAIL))) {
@@ -518,17 +633,17 @@ class Validation
*/
public static function typeDatetime($value, array $params, array $field)
{
if ($value instanceof \DateTime) {
if ($value instanceof DateTime) {
return true;
}
if (!\is_string($value)) {
if (!is_string($value)) {
return false;
}
if (!isset($params['format'])) {
return false !== strtotime($value);
}
$dateFromFormat = \DateTime::createFromFormat($params['format'], $value);
$dateFromFormat = DateTime::createFromFormat($params['format'], $value);
return $dateFromFormat && $value === date($params['format'], $dateFromFormat->getTimestamp());
}
@@ -624,21 +739,21 @@ class Validation
*/
public static function typeArray($value, array $params, array $field)
{
if (!\is_array($value)) {
if (!is_array($value)) {
return false;
}
if (isset($field['multiple'])) {
if (isset($params['min']) && \count($value) < $params['min']) {
if (isset($params['min']) && count($value) < $params['min']) {
return false;
}
if (isset($params['max']) && \count($value) > $params['max']) {
if (isset($params['max']) && count($value) > $params['max']) {
return false;
}
$min = $params['min'] ?? 0;
if (isset($params['step']) && (\count($value) - $min) % $params['step'] === 0) {
if (isset($params['step']) && (count($value) - $min) % $params['step'] === 0) {
return false;
}
}
@@ -686,7 +801,7 @@ class Validation
$options = isset($field['options']) ? array_keys($field['options']) : [];
$multi = $field['multiple'] ?? false;
if (\count($values) === 1 && isset($values[0]) && $values[0] === '') {
if (count($values) === 1 && isset($values[0]) && $values[0] === '') {
return null;
}
@@ -700,7 +815,7 @@ class Validation
if ($multi) {
foreach ($values as $key => $val) {
if (\is_array($val)) {
if (is_array($val)) {
$val = implode(',', $val);
$values[$key] = array_map('trim', explode(',', $val));
} else {
@@ -742,7 +857,7 @@ class Validation
unset($values[$key]);
}
}
if (\is_array($val)) {
if (is_array($val)) {
$val = static::arrayFilterRecurse($val, $params);
if ($params['ignore_empty'] && empty($val)) {
unset($values[$key]);
@@ -792,7 +907,7 @@ class Validation
*/
public static function typeList($value, array $params, array $field)
{
if (!\is_array($value)) {
if (!is_array($value)) {
return false;
}
@@ -827,7 +942,7 @@ class Validation
*/
public static function filterYaml($value, $params)
{
if (!\is_string($value)) {
if (!is_string($value)) {
return $value;
}
@@ -937,7 +1052,7 @@ class Validation
*/
public static function typeBool($value, $params)
{
return \is_bool($value) || $value == 1 || $value == 0;
return is_bool($value) || $value == 1 || $value == 0;
}
/**
@@ -947,7 +1062,7 @@ class Validation
*/
public static function validateBool($value, $params)
{
return \is_bool($value) || $value == 1 || $value == 0;
return is_bool($value) || $value == 1 || $value == 0;
}
/**
@@ -977,7 +1092,7 @@ class Validation
*/
public static function validateFloat($value, $params)
{
return \is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
return is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
}
/**
@@ -1027,7 +1142,7 @@ class Validation
*/
public static function validateArray($value, $params)
{
return \is_array($value) || ($value instanceof \ArrayAccess && $value instanceof \Traversable && $value instanceof \Countable);
return is_array($value) || ($value instanceof ArrayAccess && $value instanceof Traversable && $value instanceof Countable);
}
/**

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\Data;
use Grav\Common\Grav;
/**
* Class ValidationException
* @package Grav\Common\Data
*/
class ValidationException extends \RuntimeException
{
/** @var array */

View File

@@ -25,6 +25,7 @@ use DebugBar\DataCollector\PhpInfoCollector;
use DebugBar\DataCollector\RequestDataCollector;
use DebugBar\DataCollector\TimeDataCollector;
use DebugBar\DebugBar;
use DebugBar\DebugBarException;
use DebugBar\JavascriptRenderer;
use Grav\Common\Config\Config;
use Grav\Common\Processors\ProcessorInterface;
@@ -34,55 +35,59 @@ use Monolog\Logger;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RocketTheme\Toolbox\Event\Event;
use ReflectionObject;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Throwable;
use Twig\Environment;
use Twig\Template;
use Twig\TemplateWrapper;
use function array_slice;
use function count;
use function define;
use function defined;
use function extension_loaded;
use function get_class;
use function gettype;
use function is_array;
use function is_bool;
use function is_object;
use function is_scalar;
use function is_string;
/**
* Class Debugger
* @package Grav\Common
*/
class Debugger
{
/** @var static */
protected static $instance;
/** @var Grav|null */
protected $grav;
/** @var Config|null */
protected $config;
/** @var JavascriptRenderer|null */
protected $renderer;
/** @var DebugBar|null */
protected $debugbar;
/** @var Clockwork|null */
protected $clockwork;
/** @var bool */
protected $enabled = false;
/** @var bool */
protected $initialized = false;
/** @var array */
protected $timers = [];
/** @var array */
protected $deprecations = [];
/** @var callable|null */
protected $errorHandler;
/** @var float */
protected $requestTime;
/** @var float */
protected $currentTime;
/** @var int */
protected $profiling = 0;
/** @var bool */
protected $censored = false;
@@ -95,8 +100,8 @@ class Debugger
$this->currentTime = microtime(true);
if (!\defined('GRAV_REQUEST_TIME')) {
\define('GRAV_REQUEST_TIME', $this->currentTime);
if (!defined('GRAV_REQUEST_TIME')) {
define('GRAV_REQUEST_TIME', $this->currentTime);
}
$this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME;
@@ -105,6 +110,9 @@ class Debugger
$this->setErrorHandler();
}
/**
* @return Clockwork|null
*/
public function getClockwork(): ?Clockwork
{
return $this->enabled ? $this->clockwork : null;
@@ -114,7 +122,7 @@ class Debugger
* Initialize the debugger
*
* @return $this
* @throws \DebugBar\DebugBarException
* @throws DebugBarException
*/
public function init()
{
@@ -155,16 +163,17 @@ class Debugger
$clockwork->addDataSource(new MonologDataSource($log));
}
$clockwork->addDataSource(new TwigClockworkDataSource());
$timeLine = $clockwork->getTimeline();
$timeline = $clockwork->timeline();
if ($this->requestTime !== GRAV_REQUEST_TIME) {
$timeLine->addEvent('server', 'Server', $this->requestTime, GRAV_REQUEST_TIME);
$event = $timeline->event('Server');
$event->finalize($this->requestTime, GRAV_REQUEST_TIME);
}
if ($this->currentTime !== GRAV_REQUEST_TIME) {
$timeLine->addEvent('loading', 'Loading', GRAV_REQUEST_TIME, $this->currentTime);
$event = $timeline->event('Loading');
$event->finalize(GRAV_REQUEST_TIME, $this->currentTime);
}
$timeLine->addEvent('setup', 'Site Setup', $this->currentTime, microtime(true));
$event = $timeline->event('Site Setup');
$event->finalize($this->currentTime, microtime(true));
}
if ($this->censored) {
@@ -223,12 +232,14 @@ class Debugger
$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);
}
@@ -244,7 +255,7 @@ class Debugger
$this->finalize();
$clockwork->getTimeline()->finalize($request->getAttribute('request_time'));
$clockwork->timeline()->finalize($request->getAttribute('request_time'));
if ($this->censored) {
$censored = 'CENSORED';
@@ -325,28 +336,30 @@ class Debugger
return new Response(200, $headers, json_encode($data));
}
protected function addMeasures()
/**
* @return void
*/
protected function addMeasures(): void
{
if (!$this->enabled) {
return;
}
$nowTime = microtime(true);
$clkTimeLine = $this->clockwork ? $this->clockwork->getTimeline() : null;
$clkTimeLine = $this->clockwork ? $this->clockwork->timeline() : 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);
}
$event = $clkTimeLine->event($description);
$event->finalize($startTime, $endTime);
} elseif ($debTimeLine) {
if ($endTime - $startTime < 0.001) {
continue;
}
if ($debTimeLine) {
$debTimeLine->addMeasure($description ?? $name, $startTime, $endTime);
}
}
@@ -356,8 +369,7 @@ class Debugger
/**
* Set/get the enabled state of the debugger
*
* @param bool $state If null, the method returns the enabled value. If set, the method sets the enabled state
*
* @param bool|null $state If null, the method returns the enabled value. If set, the method sets the enabled state
* @return bool
*/
public function enabled($state = null)
@@ -401,7 +413,7 @@ class Debugger
$this->renderer = $this->debugbar->getJavascriptRenderer();
$this->renderer->setIncludeVendors(false);
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
[$css_files, $js_files] = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
foreach ((array)$css_files as $css) {
$assets->addCss($css);
@@ -418,6 +430,10 @@ class Debugger
return $this;
}
/**
* @param int $limit
* @return array
*/
public function getCaller($limit = 2)
{
$trace = debug_backtrace(false, $limit);
@@ -429,9 +445,8 @@ class Debugger
* Adds a data collector
*
* @param DataCollectorInterface $collector
*
* @return $this
* @throws \DebugBar\DebugBarException
* @throws DebugBarException
*/
public function addCollector($collector)
{
@@ -446,9 +461,8 @@ class Debugger
* Returns a data collector
*
* @param string $name
*
* @return DataCollectorInterface|null
* @throws \DebugBar\DebugBarException
* @throws DebugBarException
*/
public function getCollector($name)
{
@@ -520,7 +534,7 @@ class Debugger
* Hierarchical Profiler support.
*
* @param callable $callable
* @param string $message
* @param string|null $message
* @return mixed
*/
public function profile(callable $callable, string $message = null)
@@ -532,8 +546,20 @@ class Debugger
return $response;
}
public function addTwigProfiler(Environment $twig): void
{
$clockwork = $this->getClockwork();
if ($clockwork) {
$source = new TwigClockworkDataSource($twig);
$source->listenToEvents();
$clockwork->addDataSource($source);
}
}
/**
* Start profiling code.
*
* @return void
*/
public function startProfiling(): void
{
@@ -548,7 +574,7 @@ class Debugger
/**
* Stop profiling code. Returns profiling array or null if profiling couldn't be done.
*
* @param string $message
* @param string|null $message
* @return array|null
*/
public function stopProfiling(string $message = null): ?array
@@ -577,6 +603,10 @@ class Debugger
return $timings;
}
/**
* @param array $timings
* @return array
*/
protected function buildProfilerTimings(array $timings): array
{
// Filter method calls which take almost no time.
@@ -615,6 +645,10 @@ class Debugger
return $table;
}
/**
* @param string|null $call
* @return mixed|string|null
*/
protected function parseProfilerCall(?string $call)
{
if (null === $call) {
@@ -650,7 +684,6 @@ class Debugger
*
* @param string $name
* @param string|null $description
*
* @return $this
*/
public function startTimer($name, $description = null)
@@ -664,7 +697,6 @@ class Debugger
* Stop the named timer
*
* @param string $name
*
* @return $this
*/
public function stopTimer($name)
@@ -683,7 +715,6 @@ class Debugger
* @param mixed $message
* @param string $label
* @param mixed|bool $isString
*
* @return $this
*/
public function addMessage($message, $label = 'info', $isString = true)
@@ -732,24 +763,26 @@ class Debugger
* @param string $name
* @param object $event
* @param EventDispatcherInterface $dispatcher
* @param float|null $time
* @return $this
*/
public function addEvent(string $name, $event, EventDispatcherInterface $dispatcher)
public function addEvent(string $name, $event, EventDispatcherInterface $dispatcher, float $time = null)
{
if ($this->enabled) {
if ($this->clockwork) {
$data = null;
if ($event && method_exists($event, '__debugInfo')) {
$data = $event;
}
if ($this->enabled && $this->clockwork) {
$time = $time ?? microtime(true);
$duration = (microtime(true) - $time) * 1000;
$listeners = [];
foreach ($dispatcher->getListeners($name) as $listener) {
$listeners[] = $this->resolveCallable($listener);
}
$this->clockwork->addEvent($name, $data, microtime(true), ['listeners' => $listeners]);
$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, $time, ['listeners' => $listeners, 'duration' => $duration]);
}
return $this;
@@ -758,10 +791,10 @@ class Debugger
/**
* Dump exception into the Messages tab of the Debug Bar
*
* @param \Throwable $e
* @param Throwable $e
* @return Debugger
*/
public function addException(\Throwable $e)
public function addException(Throwable $e)
{
if ($this->initialized && $this->enabled) {
if ($this->debugbar) {
@@ -780,6 +813,9 @@ class Debugger
return $this;
}
/**
* @return void
*/
public function setErrorHandler()
{
$this->errorHandler = set_error_handler(
@@ -834,7 +870,7 @@ class Debugger
if ($arg instanceof \SplFileInfo) {
$arg = $arg->getPathname();
}
if (\is_string($arg) && preg_match('/.+\.(yaml|md)$/i', $arg)) {
if (is_string($arg) && preg_match('/.+\.(yaml|md)$/i', $arg)) {
$errfile = $arg;
$errline = 0;
@@ -852,18 +888,18 @@ class Debugger
if (isset($current['args'])) {
$args = [];
foreach ($current['args'] as $arg) {
if (\is_string($arg)) {
if (is_string($arg)) {
$arg = "'" . $arg . "'";
if (mb_strlen($arg) > 100) {
$arg = 'string';
}
} elseif (\is_bool($arg)) {
} elseif (is_bool($arg)) {
$arg = $arg ? 'true' : 'false';
} elseif (\is_scalar($arg)) {
} elseif (is_scalar($arg)) {
$arg = $arg;
} elseif (\is_object($arg)) {
} elseif (is_object($arg)) {
$arg = get_class($arg) . ' $object';
} elseif (\is_array($arg)) {
} elseif (is_array($arg)) {
$arg = '$array';
} else {
$arg = '$object';
@@ -879,7 +915,7 @@ class Debugger
$reflection = null;
if ($object instanceof TemplateWrapper) {
$reflection = new \ReflectionObject($object);
$reflection = new ReflectionObject($object);
$property = $reflection->getProperty('template');
$property->setAccessible(true);
$object = $property->getValue($object);
@@ -989,6 +1025,9 @@ class Debugger
return true;
}
/**
* @return array
*/
protected function getDeprecations(): array
{
if (!$this->deprecations) {
@@ -1004,6 +1043,10 @@ class Debugger
return $list;
}
/**
* @return void
* @throws DebugBarException
*/
protected function addDeprecations()
{
if (!$this->deprecations) {
@@ -1022,6 +1065,10 @@ class Debugger
}
}
/**
* @param array $deprecated
* @return array
*/
protected function getDepracatedMessage($deprecated)
{
$scope = $deprecated['scope'];
@@ -1059,6 +1106,10 @@ class Debugger
];
}
/**
* @param array $trace
* @return string
*/
protected function getFunction($trace)
{
if (!isset($trace['function'])) {
@@ -1068,6 +1119,10 @@ class Debugger
return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
}
/**
* @param callable $callable
* @return string
*/
protected function resolveCallable(callable $callable)
{
if (is_array($callable)) {

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\Errors;
use Whoops\Handler\Handler;
/**
* Class BareHandler
* @package Grav\Common\Errors
*/
class BareHandler extends Handler
{
/**

View File

@@ -9,11 +9,20 @@
namespace Grav\Common\Errors;
use Exception;
use Grav\Common\Grav;
use Whoops;
use function is_int;
/**
* Class Errors
* @package Grav\Common\Errors
*/
class Errors
{
/**
* @return void
*/
public function resetHandlers()
{
$grav = Grav::instance();
@@ -59,7 +68,7 @@ class Errors
$whoops->prependHandler(function ($exception, $inspector, $run) use ($logger) {
try {
$logger->addCritical($exception->getMessage() . ' - Trace: ' . $exception->getTraceAsString());
} catch (\Exception $e) {
} catch (Exception $e) {
echo $e;
}
});

View File

@@ -9,10 +9,15 @@
namespace Grav\Common\Errors;
use RuntimeException;
use Whoops\Handler\Handler;
use Whoops\Util\Misc;
use Whoops\Util\TemplateHelper;
/**
* Class SimplePageHandler
* @package Grav\Common\Errors
*/
class SimplePageHandler extends Handler
{
/** @var array */
@@ -62,7 +67,7 @@ class SimplePageHandler extends Handler
/**
* @param string $resource
* @return string
* @throws \RuntimeException
* @throws RuntimeException
*/
protected function getResource($resource)
{
@@ -85,7 +90,7 @@ class SimplePageHandler extends Handler
}
// If we got this far, nothing was found.
throw new \RuntimeException(
throw new RuntimeException(
"Could not find resource '{$resource}' in any resource paths (searched: " . implode(', ', $this->searchPaths). ')'
);
}

View File

@@ -9,6 +9,10 @@
namespace Grav\Common\Errors;
/**
* Class SystemFacade
* @package Grav\Common\Errors
*/
class SystemFacade extends \Whoops\Util\SystemFacade
{
/** @var callable */
@@ -26,6 +30,8 @@ class SystemFacade extends \Whoops\Util\SystemFacade
/**
* Special case to deal with Fatal errors and the like.
*
* @return void
*/
public function handleShutdown()
{

View File

@@ -9,15 +9,24 @@
namespace Grav\Common\File;
use Exception;
use RocketTheme\Toolbox\File\PhpFile;
use RuntimeException;
use Throwable;
use function function_exists;
use function get_class;
/**
* Trait CompiledFile
* @package Grav\Common\File
*/
trait CompiledFile
{
/**
* Get/set parsed file contents.
*
* @param mixed $var
* @return string|array
* @return array
*/
public function content($var = null)
{
@@ -28,9 +37,12 @@ trait CompiledFile
$file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
$modified = $this->modified();
if (!$modified) {
return $this->decode($this->raw());
try {
return $this->decode($this->raw());
} catch (Throwable $e) {
// If the compiled file is broken, we can safely ignore the error and continue.
}
}
$class = get_class($this);
@@ -46,7 +58,7 @@ trait CompiledFile
// Attempt to lock the file for writing.
try {
$file->lock(false);
} catch (\Exception $e) {
} catch (Exception $e) {
// Another process has locked the file; we will check this in a bit.
}
@@ -75,8 +87,8 @@ 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);
} catch (Exception $e) {
throw new RuntimeException(sprintf('Failed to read %s: %s', basename($this->filename), $e->getMessage()), 500, $e);
}
return parent::content($var);

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\File;
use RocketTheme\Toolbox\File\JsonFile;
/**
* Class CompiledJsonFile
* @package Grav\Common\File
*/
class CompiledJsonFile extends JsonFile
{
use CompiledFile;

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\File;
use RocketTheme\Toolbox\File\MarkdownFile;
/**
* Class CompiledMarkdownFile
* @package Grav\Common\File
*/
class CompiledMarkdownFile extends MarkdownFile
{
use CompiledFile;

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\File;
use RocketTheme\Toolbox\File\YamlFile;
/**
* Class CompiledYamlFile
* @package Grav\Common\File
*/
class CompiledYamlFile extends YamlFile
{
use CompiledFile;

View File

@@ -9,8 +9,16 @@
namespace Grav\Common\Filesystem;
use FilesystemIterator;
use Grav\Common\Utils;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use function function_exists;
/**
* Class Archiver
* @package Grav\Common\Filesystem
*/
abstract class Archiver
{
/** @var array */
@@ -42,6 +50,7 @@ abstract class Archiver
public function setArchive($archive_file)
{
$this->archive_file = $archive_file;
return $this;
}
@@ -52,11 +61,12 @@ abstract class Archiver
public function setOptions($options)
{
// Set infinite PHP execution time if possible.
if (function_exists('set_time_limit') && !Utils::isFunctionDisabled('set_time_limit')) {
if (Utils::functionExists('set_time_limit')) {
set_time_limit(0);
}
$this->options = $options + $this->options;
return $this;
}
@@ -83,15 +93,15 @@ abstract class Archiver
/**
* @param string $rootPath
* @return \RecursiveIteratorIterator
* @return RecursiveIteratorIterator
*/
protected function getArchiveFiles($rootPath)
{
$exclude_paths = $this->options['exclude_paths'];
$exclude_files = $this->options['exclude_files'];
$dirItr = new \RecursiveDirectoryIterator($rootPath, \RecursiveDirectoryIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::UNIX_PATHS);
$dirItr = new RecursiveDirectoryIterator($rootPath, RecursiveDirectoryIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS | FilesystemIterator::UNIX_PATHS);
$filterItr = new RecursiveDirectoryFilterIterator($dirItr, $rootPath, $exclude_paths, $exclude_files);
$files = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
$files = new RecursiveIteratorIterator($filterItr, RecursiveIteratorIterator::SELF_FIRST);
return $files;
}

View File

@@ -9,8 +9,18 @@
namespace Grav\Common\Filesystem;
use DirectoryIterator;
use Exception;
use FilesystemIterator;
use Grav\Common\Grav;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RegexIterator;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use function count;
use function dirname;
use function is_callable;
abstract class Folder
{
@@ -30,16 +40,15 @@ abstract class Folder
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
$flags = RecursiveDirectoryIterator::SKIP_DOTS;
if ($locator->isStream($path)) {
$directory = $locator->getRecursiveIterator($path, $flags);
} else {
$directory = new \RecursiveDirectoryIterator($path, $flags);
$directory = new RecursiveDirectoryIterator($path, $flags);
}
$filter = new RecursiveFolderFilterIterator($directory);
$iterator = new \RecursiveIteratorIterator($filter, \RecursiveIteratorIterator::SELF_FIRST);
$iterator = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::SELF_FIRST);
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $dir) {
$dir_modified = $dir->getMTime();
if ($dir_modified > $last_modified) {
@@ -67,23 +76,23 @@ abstract class Folder
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
$flags = RecursiveDirectoryIterator::SKIP_DOTS;
if ($locator->isStream($path)) {
$directory = $locator->getRecursiveIterator($path, $flags);
} else {
$directory = new \RecursiveDirectoryIterator($path, $flags);
$directory = new RecursiveDirectoryIterator($path, $flags);
}
$recursive = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
$iterator = new \RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
$recursive = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
$iterator = new RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
/** @var \RecursiveDirectoryIterator $file */
/** @var RecursiveDirectoryIterator $file */
foreach ($iterator as $filepath => $file) {
try {
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
}
} catch (\Exception $e) {
} catch (Exception $e) {
Grav::instance()['log']->error('Could not process file: ' . $e->getMessage());
}
}
@@ -102,17 +111,17 @@ abstract class Folder
$files = [];
if (file_exists($path)) {
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
$flags = RecursiveDirectoryIterator::SKIP_DOTS;
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if ($locator->isStream($path)) {
$directory = $locator->getRecursiveIterator($path, $flags);
} else {
$directory = new \RecursiveDirectoryIterator($path, $flags);
$directory = new RecursiveDirectoryIterator($path, $flags);
}
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file) {
$files[] = $file->getPathname() . '?'. $file->getMTime();
@@ -201,12 +210,12 @@ abstract class Folder
* @param string $path
* @param array $params
* @return array
* @throws \RuntimeException
* @throws RuntimeException
*/
public static function all($path, array $params = [])
{
if (!$path) {
throw new \RuntimeException("Path doesn't exist.");
throw new RuntimeException("Path doesn't exist.");
}
if (!file_exists($path)) {
return [];
@@ -225,26 +234,26 @@ abstract class Folder
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if ($recursive) {
$flags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS
+ \FilesystemIterator::CURRENT_AS_SELF + \FilesystemIterator::FOLLOW_SYMLINKS;
$flags = RecursiveDirectoryIterator::SKIP_DOTS + FilesystemIterator::UNIX_PATHS
+ FilesystemIterator::CURRENT_AS_SELF + FilesystemIterator::FOLLOW_SYMLINKS;
if ($locator->isStream($path)) {
$directory = $locator->getRecursiveIterator($path, $flags);
} else {
$directory = new \RecursiveDirectoryIterator($path, $flags);
$directory = new RecursiveDirectoryIterator($path, $flags);
}
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
$iterator->setMaxDepth(max($levels, -1));
} else {
if ($locator->isStream($path)) {
$iterator = $locator->getIterator($path);
} else {
$iterator = new \FilesystemIterator($path);
$iterator = new FilesystemIterator($path);
}
}
$results = [];
/** @var \RecursiveDirectoryIterator $file */
/** @var RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
// Ignore hidden files.
if (strpos($file->getFilename(), '.') === 0 && $file->isFile()) {
@@ -291,8 +300,9 @@ abstract class Folder
*
* @param string $source
* @param string $target
* @param string $ignore Ignore files matching pattern (regular expression).
* @throws \RuntimeException
* @param string|null $ignore Ignore files matching pattern (regular expression).
* @return void
* @throws RuntimeException
*/
public static function copy($source, $target, $ignore = null)
{
@@ -300,7 +310,7 @@ abstract class Folder
$target = rtrim($target, '\\/');
if (!is_dir($source)) {
throw new \RuntimeException('Cannot copy non-existing folder.');
throw new RuntimeException('Cannot copy non-existing folder.');
}
// Make sure that path to the target exists before copying.
@@ -330,7 +340,7 @@ abstract class Folder
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message'] ?? 'Unknown error');
throw new RuntimeException($error['message'] ?? 'Unknown error');
}
// Make sure that the change will be detected when caching.
@@ -342,13 +352,14 @@ abstract class Folder
*
* @param string $source
* @param string $target
* @throws \RuntimeException
* @return void
* @throws RuntimeException
*/
public static function move($source, $target)
{
if (!file_exists($source) || !is_dir($source)) {
// Rename fails if source folder does not exist.
throw new \RuntimeException('Cannot move non-existing folder.');
throw new RuntimeException('Cannot move non-existing folder.');
}
// Don't do anything if the source is the same as the new target
@@ -358,7 +369,7 @@ abstract class Folder
if (file_exists($target)) {
// Rename fails if target folder exists.
throw new \RuntimeException('Cannot move files to existing folder/file.');
throw new RuntimeException('Cannot move files to existing folder/file.');
}
// Make sure that path to the target exists before moving.
@@ -390,7 +401,7 @@ abstract class Folder
* @param string $target
* @param bool $include_target
* @return bool
* @throws \RuntimeException
* @throws RuntimeException
*/
public static function delete($target, $include_target = true)
{
@@ -402,7 +413,7 @@ abstract class Folder
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
throw new RuntimeException($error['message']);
}
// Make sure that the change will be detected when caching.
@@ -417,7 +428,8 @@ abstract class Folder
/**
* @param string $folder
* @throws \RuntimeException
* @return void
* @throws RuntimeException
*/
public static function mkdir($folder)
{
@@ -426,7 +438,8 @@ abstract class Folder
/**
* @param string $folder
* @throws \RuntimeException
* @return void
* @throws RuntimeException
*/
public static function create($folder)
{
@@ -441,7 +454,7 @@ abstract class Folder
// Take yet another look, make sure that the folder doesn't exist.
clearstatcache(true, $folder);
if (!@is_dir($folder)) {
throw new \RuntimeException(sprintf('Unable to create directory: %s', $folder));
throw new RuntimeException(sprintf('Unable to create directory: %s', $folder));
}
}
}
@@ -451,9 +464,8 @@ abstract class Folder
*
* @param string $src
* @param string $dest
*
* @return bool
* @throws \RuntimeException
* @throws RuntimeException
*/
public static function rcopy($src, $dest)
{
@@ -470,8 +482,7 @@ abstract class Folder
}
// Open the source directory to read in files
$i = new \DirectoryIterator($src);
/** @var \DirectoryIterator $f */
$i = new DirectoryIterator($src);
foreach ($i as $f) {
if ($f->isFile()) {
copy($f->getRealPath(), "{$dest}/" . $f->getFilename());
@@ -484,6 +495,22 @@ abstract class Folder
return true;
}
/**
* Does a directory contain children
*
* @param string $directory
* @return int|false
*/
public static function countChildren($directory)
{
if (!is_dir($directory)) {
return false;
}
$directories = glob($directory . '/*', GLOB_ONLYDIR);
return count($directories);
}
/**
* @param string $folder
* @param bool $include_target

View File

@@ -9,7 +9,16 @@
namespace Grav\Common\Filesystem;
class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
use RecursiveFilterIterator;
use RecursiveIterator;
use SplFileInfo;
use function in_array;
/**
* Class RecursiveDirectoryFilterIterator
* @package Grav\Common\Filesystem
*/
class RecursiveDirectoryFilterIterator extends RecursiveFilterIterator
{
/** @var string */
protected static $root;
@@ -21,12 +30,12 @@ class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
/**
* Create a RecursiveFilterIterator from a RecursiveIterator
*
* @param \RecursiveIterator $iterator
* @param RecursiveIterator $iterator
* @param string $root
* @param array $ignore_folders
* @param array $ignore_files
*/
public function __construct(\RecursiveIterator $iterator, $root, $ignore_folders, $ignore_files)
public function __construct(RecursiveIterator $iterator, $root, $ignore_folders, $ignore_files)
{
parent::__construct($iterator);
@@ -42,7 +51,7 @@ class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
*/
public function accept()
{
/** @var \SplFileInfo $file */
/** @var SplFileInfo $file */
$file = $this->current();
$filename = $file->getFilename();
$relative_filename = str_replace($this::$root . '/', '', $file->getPathname());
@@ -61,7 +70,7 @@ class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
}
/**
* @return RecursiveDirectoryFilterIterator|\RecursiveFilterIterator
* @return RecursiveDirectoryFilterIterator|RecursiveFilterIterator
*/
public function getChildren()
{

View File

@@ -10,7 +10,14 @@
namespace Grav\Common\Filesystem;
use Grav\Common\Grav;
use RecursiveIterator;
use SplFileInfo;
use function in_array;
/**
* Class RecursiveFolderFilterIterator
* @package Grav\Common\Filesystem
*/
class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
{
/** @var array */
@@ -19,10 +26,10 @@ class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
/**
* Create a RecursiveFilterIterator from a RecursiveIterator
*
* @param \RecursiveIterator $iterator
* @param RecursiveIterator $iterator
* @param array $ignore_folders
*/
public function __construct(\RecursiveIterator $iterator, $ignore_folders = [])
public function __construct(RecursiveIterator $iterator, $ignore_folders = [])
{
parent::__construct($iterator);
@@ -40,7 +47,7 @@ class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
*/
public function accept()
{
/** @var \SplFileInfo $current */
/** @var SplFileInfo $current */
$current = $this->current();
return $current->isDir() && !in_array($current->getFilename(), $this::$ignore_folders, true);

View File

@@ -9,6 +9,16 @@
namespace Grav\Common\Filesystem;
use InvalidArgumentException;
use RuntimeException;
use ZipArchive;
use function extension_loaded;
use function strlen;
/**
* Class ZipArchiver
* @package Grav\Common\Filesystem
*/
class ZipArchiver extends Archiver
{
/**
@@ -18,21 +28,22 @@ class ZipArchiver extends Archiver
*/
public function extract($destination, callable $status = null)
{
$zip = new \ZipArchive();
$zip = new ZipArchive();
$archive = $zip->open($this->archive_file);
if ($archive === true) {
Folder::create($destination);
if (!$zip->extractTo($destination)) {
throw new \RuntimeException('ZipArchiver: ZIP failed to extract ' . $this->archive_file . ' to ' . $destination);
throw new RuntimeException('ZipArchiver: ZIP failed to extract ' . $this->archive_file . ' to ' . $destination);
}
$zip->close();
return $this;
}
throw new \RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file);
throw new RuntimeException('ZipArchiver: Failed to open ' . $this->archive_file);
}
/**
@@ -43,16 +54,16 @@ class ZipArchiver extends Archiver
public function compress($source, callable $status = null)
{
if (!extension_loaded('zip')) {
throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
throw new InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
}
if (!file_exists($source)) {
throw new \InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...');
throw new InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...');
}
$zip = new \ZipArchive();
if (!$zip->open($this->archive_file, \ZipArchive::CREATE)) {
throw new \InvalidArgumentException('ZipArchiver:' . $this->archive_file . ' cannot be created...');
$zip = new ZipArchive();
if (!$zip->open($this->archive_file, ZipArchive::CREATE)) {
throw new InvalidArgumentException('ZipArchiver:' . $this->archive_file . ' cannot be created...');
}
// Get real path for our folder
@@ -98,12 +109,12 @@ class ZipArchiver extends Archiver
public function addEmptyFolders($folders, callable $status = null)
{
if (!extension_loaded('zip')) {
throw new \InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
throw new InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
}
$zip = new \ZipArchive();
$zip = new ZipArchive();
if (!$zip->open($this->archive_file)) {
throw new \InvalidArgumentException('ZipArchiver: ' . $this->archive_file . ' cannot be opened...');
throw new InvalidArgumentException('ZipArchiver: ' . $this->archive_file . ' cannot be opened...');
}
$status && $status([

View File

@@ -29,14 +29,22 @@ trait FlexCollectionTrait
public function triggerEvent(string $name, $event = null)
{
if (null === $event) {
$event = new Event(['collection' => $this]);
$event = new Event([
'type' => 'flex',
'directory' => $this->getFlexDirectory(),
'collection' => $this
]);
}
if (strpos($name, 'onFlexCollection') !== 0 && strpos($name, 'on') === 0) {
$name = 'onFlexCollection' . substr($name, 2);
}
$container = $this->getContainer();
$container->fireEvent($name, $event);
if ($event instanceof Event) {
$container->fireEvent($name, $event);
} else {
$container->dispatchEvent($event);
}
return $this;
}

View File

@@ -28,15 +28,34 @@ trait FlexObjectTrait
*/
public function triggerEvent(string $name, $event = null)
{
$events = [
'onRender' => 'onFlexObjectRender',
'onBeforeSave' => 'onFlexObjectBeforeSave',
'onAfterSave' => 'onFlexObjectAfterSave',
'onBeforeDelete' => 'onFlexObjectBeforeDelete',
'onAfterDelete' => 'onFlexObjectAfterDelete'
];
if (null === $event) {
$event = new Event(['object' => $this]);
$event = new Event([
'type' => 'flex',
'directory' => $this->getFlexDirectory(),
'object' => $this
]);
}
if (strpos($name, 'onFlexObject') !== 0 && strpos($name, 'on') === 0) {
if (isset($events['name'])) {
$name = $events['name'];
} elseif (strpos($name, 'onFlexObject') !== 0 && strpos($name, 'on') === 0) {
$name = 'onFlexObject' . substr($name, 2);
}
$container = $this->getContainer();
$container->fireEvent($name, $event);
if ($event instanceof Event) {
$container->fireEvent($name, $event);
} else {
$container->dispatchEvent($event);
}
return $this;
}

View File

@@ -14,6 +14,7 @@ namespace Grav\Common\Flex\Types\Generic;
use Grav\Common\Flex\Traits\FlexGravTrait;
use Grav\Common\Flex\Traits\FlexObjectTrait;
use Grav\Framework\Flex\FlexObject;
use Grav\Framework\Flex\Traits\FlexMediaTrait;
/**
* Class GenericObject
@@ -23,4 +24,5 @@ class GenericObject extends FlexObject
{
use FlexGravTrait;
use FlexObjectTrait;
use FlexMediaTrait;
}

View File

@@ -13,12 +13,22 @@ namespace Grav\Common\Flex\Types\Pages;
use Grav\Common\Flex\Traits\FlexCollectionTrait;
use Grav\Common\Flex\Traits\FlexGravTrait;
use Grav\Common\Grav;
use Grav\Common\Page\Header;
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;
use Collator;
use InvalidArgumentException;
use RuntimeException;
use function array_search;
use function count;
use function extension_loaded;
use function in_array;
use function is_array;
use function is_string;
/**
* Class GravPageCollection
@@ -67,6 +77,8 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
'dateRange' => true,
'visible' => true,
'nonVisible' => true,
'pages' => true,
'modules' => true,
'modular' => true,
'nonModular' => true,
'published' => true,
@@ -76,6 +88,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
'ofType' => true,
'ofOneOfTheseTypes' => true,
'ofOneOfTheseAccessLevels' => true,
'withOrdered' => true,
'withModules' => true,
'withPages' => true,
'withTranslation' => true,
@@ -91,7 +104,6 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
*/
public function getRoot()
{
/** @var PageIndex $index */
$index = $this->getIndex();
return $index->getRoot();
@@ -139,7 +151,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
public function addPage(PageInterface $page)
{
if (!$page instanceof FlexObjectInterface) {
throw new \InvalidArgumentException('$page is not a flex page.');
throw new InvalidArgumentException('$page is not a flex page.');
}
// FIXME: support other keys.
@@ -157,7 +169,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
*/
public function merge(PageCollectionInterface $collection)
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
}
/**
@@ -168,7 +180,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
*/
public function intersect(PageCollectionInterface $collection)
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
}
/**
@@ -214,7 +226,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
*/
public function append($items)
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
throw new RuntimeException(__METHOD__ . '(): Not Implemented');
}
/**
@@ -225,7 +237,14 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
*/
public function batch($size): array
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
$chunks = $this->chunk($size);
$list = [];
foreach ($chunks as $chunk) {
$list[] = $this->createFrom($chunk);
}
return $list;
}
/**
@@ -233,13 +252,164 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
*
* @param string $by
* @param string $dir
* @param array $manual
* @param string $sort_flags
* @param array|null $manual
* @param string|null $sort_flags
* @return static
*/
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
{
throw new \RuntimeException(__METHOD__ . '(): Not Implemented');
if (!$this->count()) {
return $this;
}
if ($by === 'random') {
return $this->shuffle();
}
$keys = $this->buildSort($by, $dir, $manual, $sort_flags);
return $this->createFrom(array_replace(array_flip($keys), $this->toArray()) ?? []);
}
/**
* @param string $order_by
* @param string $order_dir
* @param array|null $manual
* @param int|null $sort_flags
* @return array
*/
protected function buildSort($order_by = 'default', $order_dir = 'asc', $manual = null, $sort_flags = null): array
{
// do this header query work only once
$header_query = null;
$header_default = null;
if (strpos($order_by, 'header.') === 0) {
$query = explode('|', str_replace('header.', '', $order_by), 2);
$header_query = array_shift($query) ?? '';
$header_default = array_shift($query);
}
$list = [];
foreach ($this as $key => $child) {
switch ($order_by) {
case 'title':
$list[$key] = $child->title();
break;
case 'date':
$list[$key] = $child->date();
$sort_flags = SORT_REGULAR;
break;
case 'modified':
$list[$key] = $child->modified();
$sort_flags = SORT_REGULAR;
break;
case 'publish_date':
$list[$key] = $child->publishDate();
$sort_flags = SORT_REGULAR;
break;
case 'unpublish_date':
$list[$key] = $child->unpublishDate();
$sort_flags = SORT_REGULAR;
break;
case 'slug':
$list[$key] = $child->slug();
break;
case 'basename':
$list[$key] = basename($key);
break;
case 'folder':
$list[$key] = $child->folder();
break;
case 'manual':
case 'default':
default:
if (is_string($header_query)) {
/** @var Header $child_header */
$child_header = $child->header();
$header_value = $child_header->get($header_query);
if (is_array($header_value)) {
$list[$key] = implode(',', $header_value);
} elseif ($header_value) {
$list[$key] = $header_value;
} else {
$list[$key] = $header_default ?: $key;
}
$sort_flags = $sort_flags ?: SORT_REGULAR;
break;
}
$list[$key] = $key;
$sort_flags = $sort_flags ?: SORT_REGULAR;
}
}
if (null === $sort_flags) {
$sort_flags = SORT_NATURAL | SORT_FLAG_CASE;
}
// else just sort the list according to specified key
if (extension_loaded('intl') && Grav::instance()['config']->get('system.intl_enabled')) {
$locale = setlocale(LC_COLLATE, '0'); //`setlocale` with a '0' param returns the current locale set
$col = Collator::create($locale);
if ($col) {
if (($sort_flags & SORT_NATURAL) === SORT_NATURAL) {
$list = preg_replace_callback('~([0-9]+)\.~', static function ($number) {
return sprintf('%032d.', $number[0]);
}, $list);
if (!is_array($list)) {
throw new RuntimeException('Internal Error');
}
$list_vals = array_values($list);
if (is_numeric(array_shift($list_vals))) {
$sort_flags = Collator::SORT_REGULAR;
} else {
$sort_flags = Collator::SORT_STRING;
}
}
$col->asort($list, $sort_flags);
} else {
asort($list, $sort_flags);
}
} else {
asort($list, $sort_flags);
}
// Move manually ordered items into the beginning of the list. Order of the unlisted items does not change.
if (is_array($manual) && !empty($manual)) {
$i = count($manual);
$new_list = [];
foreach ($list as $key => $dummy) {
$child = $this[$key];
$order = array_search($child->slug, $manual, true);
if ($order === false) {
$order = $i++;
}
$new_list[$key] = (int)$order;
}
$list = $new_list;
// Apply manual ordering to the list.
asort($list, SORT_NUMERIC);
}
if ($order_dir !== 'asc') {
$list = array_reverse($list);
}
return array_keys($list);
}
/**
* Mimicks Pages class.
*
* @return $this
* @deprecated 1.7 Not needed anymore in Flex Pages (does nothing).
*/
public function all()
{
return $this;
}
/**
@@ -310,11 +480,32 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
}
/**
* Creates new collection with only modular pages
* Creates new collection with only pages
*
* @return static The collection with only modular pages
* @return static The collection with only pages
*/
public function modular()
public function pages()
{
$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 modules
*
* @return static The collection with only modules
*/
public function modules()
{
$entries = [];
/**
@@ -331,24 +522,23 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
}
/**
* Creates new collection with only non-modular pages
* Alias of modules()
*
* @return static The collection with only non-modular pages
* @return static
*/
public function modular()
{
return $this->modules();
}
/**
* Alias of pages()
*
* @return static
*/
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);
return $this->pages();
}
/**
@@ -447,7 +637,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
{
$entries = [];
foreach ($this as $key => $object) {
if ($object && \in_array($object->template(), $types, true)) {
if ($object && in_array($object->template(), $types, true)) {
$entries[$key] = $object;
}
}
@@ -466,19 +656,19 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
$entries = [];
foreach ($this as $key => $object) {
if ($object && isset($object->header()->access)) {
if (\is_array($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)) {
if (is_array($accessLevel)) {
foreach ($accessLevel as $innerIndex => $innerAccessLevel) {
if (\in_array($innerAccessLevel, $accessLevels)) {
if (in_array($innerAccessLevel, $accessLevels)) {
$valid = true;
}
}
} else {
if (\in_array($index, $accessLevels)) {
if (in_array($index, $accessLevels)) {
$valid = true;
}
}
@@ -488,7 +678,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
}
} else {
//Single value for access
if (\in_array($object->header()->access, $accessLevels)) {
if (in_array($object->header()->access, $accessLevels)) {
$entries[$key] = $object;
}
}
@@ -498,6 +688,17 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
return $this->createFrom($entries);
}
/**
* @param bool $bool
* @return FlexCollectionInterface|FlexPageCollection
*/
public function withOrdered(bool $bool = true)
{
$list = array_keys(array_filter($this->call('isOrdered', [$bool])));
return $this->select($list);
}
/**
* @param bool $bool
* @return FlexCollectionInterface|FlexPageCollection
@@ -509,7 +710,6 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
return $this->select($list);
}
/**
* @param bool $bool
* @return FlexCollectionInterface|FlexPageCollection

View File

@@ -11,38 +11,34 @@ declare(strict_types=1);
namespace Grav\Common\Flex\Types\Pages;
use Exception;
use Grav\Common\Debugger;
use Grav\Common\File\CompiledJsonFile;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Flex\Traits\FlexGravTrait;
use Grav\Common\Flex\Traits\FlexIndexTrait;
use Grav\Common\Grav;
use Grav\Common\Page\Header;
use Grav\Common\Page\Interfaces\PageCollectionInterface;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Utils;
use Grav\Framework\Flex\FlexDirectory;
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
use Grav\Framework\Flex\Interfaces\FlexStorageInterface;
use Grav\Framework\Flex\Pages\FlexPageIndex;
use RuntimeException;
use function is_array;
use function is_string;
/**
* 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
class PageIndex extends FlexPageIndex implements PageCollectionInterface
{
use FlexGravTrait;
use FlexIndexTrait;
@@ -80,15 +76,19 @@ class PageIndex extends FlexPageIndex
// Load saved index.
$index = static::loadIndex($storage);
$timestamp = $index['timestamp'] ?? 0;
if ($timestamp && $timestamp > time() - 2) {
return $index['index'];
}
$version = $index['version'] ?? 0;
$force = static::VERSION !== $version;
// TODO: Following check flex index to be out of sync after some saves, disabled until better solution is found.
//$timestamp = $index['timestamp'] ?? 0;
//if (!$force && $timestamp && $timestamp > time() - 1) {
// return $index['index'];
//}
// Load up to date index.
$entries = parent::loadEntriesFromStorage($storage);
return static::updateIndexFile($storage, $index['index'], $entries, ['include_missing' => true]);
return static::updateIndexFile($storage, $index['index'], $entries, ['include_missing' => true, 'force_update' => $force]);
}
/**
@@ -135,7 +135,7 @@ class PageIndex extends FlexPageIndex
$debugger = Grav::instance()['debugger'];
$message = sprintf('Flex Pages: root page is broken in storage: %s', $row['__ERROR']);
$debugger->addException(new \RuntimeException($message));
$debugger->addException(new RuntimeException($message));
$debugger->addMessage($message, 'error');
$row = ['__META' => $root];
@@ -274,12 +274,20 @@ class PageIndex extends FlexPageIndex
*/
public function getLevelListing(array $options): array
{
// Undocumented B/C
$order = $options['order'] ?? 'asc';
if ($order === SORT_ASC) {
$options['order'] = 'asc';
} elseif ($order === SORT_DESC) {
$options['order'] = 'desc';
}
$options += [
'field' => null,
'route' => null,
'leaf_route' => null,
'sortby' => null,
'order' => SORT_ASC,
'order' => 'asc',
'lang' => null,
'filters' => [],
];
@@ -368,7 +376,8 @@ class PageIndex extends FlexPageIndex
'type' => 'root',
'modified' => $page->modified(),
'size' => 0,
'symlink' => false
'symlink' => false,
'has-children' => false
];
} else {
$response[] = [
@@ -402,21 +411,38 @@ class PageIndex extends FlexPageIndex
$children = $page->children()->getIndex();
$selectedChildren = $children->filterBy($filters, true);
/** @var Header $header */
$header = $page->header();
if (!$field && $header->get('admin.children_display_order') === 'collection' && ($orderby = $header->get('content.order.by'))) {
// Use custom sorting by page header.
$sortby = $orderby;
$order = $header->get('content.order.dir', $order);
$custom = $header->get('content.order.custom');
}
if ($sortby) {
// Sort children.
$selectedChildren = $selectedChildren->order($sortby, $order, $custom ?? null);
}
/** @var PageObject $child */
foreach ($selectedChildren as $child) {
$selected = $child->path() === $extra;
$includeChildren = \is_array($leaf) && !empty($leaf) && $selected;
$includeChildren = is_array($leaf) && !empty($leaf) && $selected;
if ($field) {
$child_count = count($child->children());
$payload = [
'name' => $child->title(),
'name' => $child->menu(),
'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
'size' => $child_count,
'symlink' => false,
'has-children' => $child_count > 0
];
} else {
// TODO: all these features are independent from each other, we cannot just have one icon/color to catch all.
@@ -452,7 +478,7 @@ class PageIndex extends FlexPageIndex
'visible' => $child->visible(),
'routable' => $child->routable(),
'tags' => $tags,
'actions' => null,
'actions' => $this->getListingActions($child),
];
$extras = array_filter($extras, static function ($v) {
return $v !== null;
@@ -463,7 +489,7 @@ class PageIndex extends FlexPageIndex
$payload = [
'item-key' => basename($child->rawRoute() ?? $child->getKey()),
'icon' => $icon,
'title' => $child->title(),
'title' => htmlspecialchars($child->menu()),
'route' => [
'display' => $child->getRoute()->toString(false) ?: '/',
'raw' => $child->rawRoute(),
@@ -490,11 +516,6 @@ class PageIndex extends FlexPageIndex
$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) {
@@ -508,9 +529,31 @@ class PageIndex extends FlexPageIndex
return [$status, $msg ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED', $response, $path];
}
/**
* @param PageObject $object
* @return array
*/
protected function getListingActions(PageObject $object): array
{
$actions = [];
if ($object->isAuthorized('read')) {
$actions[] = 'preview';
$actions[] = 'edit';
}
if ($object->isAuthorized('update')) {
$actions[] = 'copy';
$actions[] = 'move';
}
if ($object->isAuthorized('delete')) {
$actions[] = 'delete';
}
return $actions;
}
/**
* @param FlexStorageInterface $storage
* @return CompiledJsonFile|\Grav\Common\File\CompiledYamlFile|null
* @return CompiledJsonFile|CompiledYamlFile|null
*/
protected static function getIndexFile(FlexStorageInterface $storage)
{
@@ -542,4 +585,408 @@ class PageIndex extends FlexPageIndex
return date($dateFormat, $timestamp) ?: null;
}
/**
* Add a single page to a collection
*
* @param PageInterface $page
*
* @return PageCollection
*/
public function addPage(PageInterface $page)
{
return $this->getCollection()->addPage($page);
}
/**
*
* Create a copy of this collection
*
* @return static
*/
public function copy()
{
return clone $this;
}
/**
*
* Merge another collection with the current collection
*
* @param PageCollectionInterface $collection
* @return $this
*/
public function merge(PageCollectionInterface $collection)
{
return $this->getCollection()->merge($collection);
}
/**
* Intersect another collection with the current collection
*
* @param PageCollectionInterface $collection
* @return $this
*/
public function intersect(PageCollectionInterface $collection)
{
return $this->getCollection()->intersect($collection);
}
/**
* Split collection into array of smaller collections.
*
* @param int $size
* @return PageCollectionInterface[]
*/
public function batch($size)
{
return $this->getCollection()->batch($size);
}
/**
* Remove item from the list.
*
* @param PageInterface|string|null $key
*
* @return $this
* @throws \InvalidArgumentException
*/
public function remove($key = null)
{
return $this->getCollection()->remove($key);
}
/**
* Reorder collection.
*
* @param string $by
* @param string $dir
* @param array $manual
* @param string $sort_flags
*
* @return PageCollectionInterface
*/
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null)
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('order', [$by, $dir, $manual, $sort_flags]);
return $collection;
}
/**
* Check to see if this item is the first in the collection.
*
* @param string $path
*
* @return bool True if item is first.
*/
public function isFirst($path): bool
{
/** @var bool $result */
$result = $this->__call('isFirst', [$path]);
return $result;
}
/**
* Check to see if this item is the last in the collection.
*
* @param string $path
*
* @return bool True if item is last.
*/
public function isLast($path): bool
{
/** @var bool $result */
$result = $this->__call('isLast', [$path]);
return $result;
}
/**
* Gets the previous sibling based on current position.
*
* @param string $path
*
* @return PageInterface|null The previous item.
*/
public function prevSibling($path)
{
/** @var PageInterface|null $result */
$result = $this->__call('prevSibling', [$path]);
return $result;
}
/**
* Gets the next sibling based on current position.
*
* @param string $path
*
* @return PageInterface|null The next item.
*/
public function nextSibling($path)
{
/** @var PageInterface|null $result */
$result = $this->__call('nextSibling', [$path]);
return $result;
}
/**
* Returns the adjacent sibling based on a direction.
*
* @param string $path
* @param int $direction either -1 or +1
*
* @return PageInterface|PageCollectionInterface|false The sibling item.
*/
public function adjacentSibling($path, $direction = 1)
{
/** @var PageInterface|PageCollectionInterface|false $result */
$result = $this->__call('adjacentSibling', [$path, $direction]);
return $result;
}
/**
* Returns the item in the current position.
*
* @param string $path the path the item
*
* @return int|null The index of the current page, null if not found.
*/
public function currentPosition($path): ?int
{
/** @var int|null $result */
$result = $this->__call('currentPosition', [$path]);
return $result;
}
/**
* Returns the items between a set of date ranges of either the page date field (default) or
* an arbitrary datetime page field where end date is optional
* Dates can be passed in as text that strtotime() can process
* http://php.net/manual/en/function.strtotime.php
*
* @param string $startDate
* @param bool $endDate
* @param string|null $field
*
* @return PageCollectionInterface
* @throws \Exception
*/
public function dateRange($startDate, $endDate = false, $field = null)
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('dateRange', [$startDate, $endDate, $field]);
return $collection;
}
/**
* Mimicks Pages class.
*
* @return $this
* @deprecated 1.7 Not needed anymore in Flex Pages (does nothing).
*/
public function all()
{
return $this;
}
/**
* Creates new collection with only visible pages
*
* @return PageCollectionInterface The collection with only visible pages
*/
public function visible()
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('visible', []);
return $collection;
}
/**
* Creates new collection with only non-visible pages
*
* @return PageCollectionInterface The collection with only non-visible pages
*/
public function nonVisible()
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('nonVisible', []);
return $collection;
}
/**
* Creates new collection with only non-modular pages
*
* @return PageCollectionInterface The collection with only non-modular pages
*/
public function pages()
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('pages', []);
return $collection;
}
/**
* Creates new collection with only modular pages
*
* @return PageCollectionInterface The collection with only modular pages
*/
public function modules()
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('modules', []);
return $collection;
}
/**
* Creates new collection with only modular pages
*
* @return PageCollectionInterface The collection with only modular pages
*/
public function modular()
{
return $this->modules();
}
/**
* Creates new collection with only non-modular pages
*
* @return PageCollectionInterface The collection with only non-modular pages
*/
public function nonModular()
{
return $this->pages();
}
/**
* Creates new collection with only published pages
*
* @return PageCollectionInterface The collection with only published pages
*/
public function published()
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('published', []);
return $collection;
}
/**
* Creates new collection with only non-published pages
*
* @return PageCollectionInterface The collection with only non-published pages
*/
public function nonPublished()
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('nonPublished', []);
return $collection;
}
/**
* Creates new collection with only routable pages
*
* @return PageCollectionInterface The collection with only routable pages
*/
public function routable()
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('routable', []);
return $collection;
}
/**
* Creates new collection with only non-routable pages
*
* @return PageCollectionInterface The collection with only non-routable pages
*/
public function nonRoutable()
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('nonRoutable', []);
return $collection;
}
/**
* Creates new collection with only pages of the specified type
*
* @param string $type
*
* @return PageCollectionInterface The collection
*/
public function ofType($type)
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('ofType', []);
return $collection;
}
/**
* Creates new collection with only pages of one of the specified types
*
* @param string[] $types
*
* @return PageCollectionInterface The collection
*/
public function ofOneOfTheseTypes($types)
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('ofOneOfTheseTypes', []);
return $collection;
}
/**
* Creates new collection with only pages of one of the specified access levels
*
* @param array $accessLevels
*
* @return PageCollectionInterface The collection
*/
public function ofOneOfTheseAccessLevels($accessLevels)
{
/** @var PageCollectionInterface $collection */
$collection = $this->__call('ofOneOfTheseAccessLevels', []);
return $collection;
}
/**
* Converts collection into an array.
*
* @return array
*/
public function toArray()
{
return $this->getCollection()->toArray();
}
/**
* Get the extended version of this Collection with each page keyed by route
*
* @return array
* @throws Exception
*/
public function toExtendedArray()
{
return $this->getCollection()->toExtendedArray();
}
}

View File

@@ -20,21 +20,33 @@ use Grav\Common\Flex\Types\Pages\Traits\PageLegacyTrait;
use Grav\Common\Flex\Types\Pages\Traits\PageRoutableTrait;
use Grav\Common\Flex\Types\Pages\Traits\PageTranslateTrait;
use Grav\Common\Language\Language;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Pages;
use Grav\Common\Utils;
use Grav\Framework\Filesystem\Filesystem;
use Grav\Framework\Flex\FlexObject;
use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
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;
use RuntimeException;
use stdClass;
use function array_key_exists;
use function assert;
use function count;
use function func_get_args;
use function in_array;
use function is_array;
/**
* Class GravPageObject
* @package Grav\Plugin\FlexObjects\Types\GravPages
*
* @property string $name
* @property string $slug
* @property string $route
* @property string $folder
* @property int|false $order
@@ -71,6 +83,9 @@ class PageObject extends FlexPageObject
] + parent::getCachedMethods();
}
/**
* @return void
*/
public function initialize(): void
{
if (!$this->_initialized) {
@@ -86,7 +101,17 @@ class PageObject extends FlexPageObject
public function getRoute($query = []): Route
{
$route = RouteFactory::createFromString($this->route());
if (\is_array($query)) {
if ($lang = $route->getLanguage()) {
$grav = Grav::instance();
if (!$grav['config']->get('system.languages.include_default_lang')) {
/** @var Language $language */
$language = $grav['language'];
if ($lang === $language->getDefault()) {
$route = $route->withLanguage('');
}
}
}
if (is_array($query)) {
foreach ($query as $key => $value) {
$route = $route->withQueryParam($key, $value);
}
@@ -102,7 +127,7 @@ class PageObject extends FlexPageObject
*/
public function getFormValue(string $name, $default = null, string $separator = null)
{
$test = new \stdClass();
$test = new stdClass();
$value = $this->pageContentValue($name, $test);
if ($value !== $test) {
@@ -147,77 +172,209 @@ class PageObject extends FlexPageObject
}
/**
* @param array|bool $reorder
* @return FlexObject|\Grav\Framework\Flex\Interfaces\FlexObjectInterface
* @param array $variables
* @return array
*/
public function save($reorder = true)
protected function onBeforeSave(array $variables)
{
// Reorder siblings.
if ($reorder === true && !$this->root()) {
$reorder = $this->_reorder ?: false;
$reorder = $variables[0] ?? true;
$meta = $this->getMetaData();
if (($meta['copy'] ?? false) === true) {
$this->folder = $this->getKey();
}
// Figure out storage path to the new route.
$parentKey = $this->getProperty('parent_key');
if ($parentKey !== '') {
$parentRoute = $this->getProperty('route');
// Root page cannot be moved.
if ($this->root()) {
throw new RuntimeException(sprintf('Root page cannot be moved to %s', $parentRoute));
}
// 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 = $parentKey !== false ? $this->getFlexDirectory()->getObject($parentKey, 'storage_key') : null;
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));
}
// TODO: make sure that the page doesn't exist yet if moved/copied.
}
if ($reorder === true && !$this->root()) {
$reorder = $this->_reorder;
}
// Force automatic reorder if item is supposed to be added to the last.
if (!is_array($reorder) && (int)$this->order() >= 999999) {
$reorder = [];
}
// Reorder siblings.
$siblings = is_array($reorder) ? $this->reorderSiblings($reorder) : [];
/** @var static $instance */
$instance = parent::save();
$data = $this->prepareStorage();
unset($data['header']);
foreach ($siblings as $sibling) {
$data = $sibling->prepareStorage();
unset($data['header']);
}
return ['reorder' => $reorder, 'siblings' => $siblings];
}
/**
* @param array $variables
* @return array
*/
protected function onSave(array $variables): array
{
/** @var PageCollection $siblings */
$siblings = $variables['siblings'];
foreach ($siblings as $sibling) {
$sibling->save(false);
}
return $variables;
}
/**
* @param array $variables
*/
protected function onAfterSave(array $variables): void
{
$this->getFlexDirectory()->reloadIndex();
}
/**
* @param array|bool $reorder
* @return FlexObject|FlexObjectInterface
*/
public function save($reorder = true)
{
$variables = $this->onBeforeSave(func_get_args());
// Backwards compatibility with older plugins.
$fireEvents = $reorder && $this->isAdminSite() && $this->getFlexDirectory()->getConfig('object.compat.events', true);
$grav = $this->getContainer();
if ($fireEvents) {
$self = $this;
$grav->fireEvent('onAdminSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => &$self]));
if ($self !== $this) {
throw new RuntimeException('Switching Flex Page object during onAdminSave event is not supported! Please update plugin.');
}
}
/** @var static $instance */
$instance = parent::save();
$variables = $this->onSave($variables);
$this->onAfterSave($variables);
// Backwards compatibility with older plugins.
if ($fireEvents) {
$grav->fireEvent('onAdminAfterSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => $this]));
}
return $instance;
}
/**
* Prepare move page to new location. Moves also everything that's under the current page.
*
* You need to call $this->save() in order to perform the move.
*
* @param PageInterface $parent New parent page.
*
* @return $this
*/
public function move(PageInterface $parent)
{
if (!$parent instanceof FlexObjectInterface) {
throw new RuntimeException('Failed: Parent is not Flex Object');
}
$this->_reorder = [];
$this->setProperty('parent_key', $parent->getStorageKey());
return $this;
}
/**
* @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();
if (!$parent) {
throw new RuntimeException('Cannot reorder a page which has no parent');
}
/** @var PageCollection|null $siblings */
$siblings = $parent ? $parent->children() : null;
$siblings = $parent->children();
/** @var PageCollection|null $siblings */
$siblings = $siblings ? $siblings->withVisible()->getCollection() : null;
if ($siblings) {
$ordering = array_flip($ordering);
if ($storageKey !== null) {
$siblings = $siblings->getCollection()->withOrdered()->orderBy(['order' => 'ASC']);
$storageKey = $this->getMasterKey();
$filesystem = Filesystem::getInstance(false);
$oldParentKey = ltrim($filesystem->dirname("/{$storageKey}"), '/');
$newParentKey = $this->getProperty('parent_key');
$isMoved = $oldParentKey !== $newParentKey;
$order = !$isMoved ? $this->order() : false;
if ($storageKey !== null) {
if ($order !== false) {
// Add current page back to the list if it's ordered.
$siblings->set($storageKey, $this);
} else {
// Remove old copy of the current page from the siblings list.
$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);
}
// Add missing siblings into the end of the list, keeping the previous ordering between them.
foreach ($siblings as $sibling) {
$basename = preg_replace('|^\d+\.|', '', $sibling->getProperty('folder'));
if (!in_array($basename, $ordering, true)) {
$ordering[] = $basename;
}
/** @var PageCollection $siblings */
$siblings = $siblings->orderBy(['order' => 'ASC']);
$siblings->removeElement($this);
} else {
/** @var PageCollection $siblings */
$siblings = $this->getFlexDirectory()->createCollection([]);
}
// Reorder.
$ordering = array_flip(array_values($ordering));
$count = count($ordering);
foreach ($siblings as $sibling) {
$basename = preg_replace('|^\d+\.|', '', $sibling->getProperty('folder'));
$newOrder = $ordering[$basename] ?? null;
$newOrder = null !== $newOrder ? $newOrder + 1 : (int)$sibling->order() + $count;
$sibling->order($newOrder);
}
$siblings = $siblings->orderBy(['order' => 'ASC']);
$siblings->removeElement($this);
// If menu item was moved, just make it to be the last in order.
if ($isMoved && $order !== false) {
$parentKey = $this->getProperty('parent_key');
$newParent = $this->getFlexDirectory()->getObject($parentKey, 'storage_key');
$newSiblings = $newParent->children()->getCollection()->withOrdered();
$order = 0;
foreach ($newSiblings as $sibling) {
$order = max($order, (int)$sibling->order());
}
$this->order($order + 1);
}
return $siblings;
@@ -259,12 +416,19 @@ class PageObject extends FlexPageObject
$template = $this->getProperty('template') . ($name ? '.' . $name : '');
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
} catch (\RuntimeException $e) {
} catch (RuntimeException $e) {
$template = 'default' . ($name ? '.' . $name : '');
$blueprint = $this->getFlexDirectory()->getBlueprint($template, 'blueprints://pages');
}
$isNew = $blueprint->get('initialized', false) === false;
if ($isNew === true && $name === '') {
// Support onBlueprintCreated event just like in Pages::blueprints($template)
$blueprint->set('initialized', true);
Grav::instance()->fireEvent('onBlueprintCreated', new Event(['blueprint' => $blueprint, 'type' => $template]));
}
return $blueprint;
}
@@ -372,64 +536,38 @@ class PageObject extends FlexPageObject
*/
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.
// Change parent page 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, '/');
$parentKey = trim($elements['route'] ?? '', '/');
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();
$parentKey = $parent ? $parent->getStorageKey() : $parentKey;
}
$elements['parent_key'] = $parentKey;
}
// Deal with ordering=bool and order=page1,page2,page3.
if ($this->root()) {
// Root page doesn't have ordering.
unset($elements['ordering'], $elements['order']);
} elseif (array_key_exists('ordering', $elements) && array_key_exists('order', $elements)) {
// Store ordering.
$this->_reorder = !empty($elements['order']) ? explode(',', $elements['order']) : [];
$order = false;
if ((bool)($elements['ordering'] ?? false)) {
$order = 999999;
}
$this->order();
$elements['order'] = $order;
}
parent::filterElements($elements, true);
}
@@ -440,7 +578,7 @@ class PageObject extends FlexPageObject
{
$meta = $this->getMetaData();
$oldLang = $meta['lang'] ?? '';
$newLang = $this->getProperty('lang');
$newLang = $this->getProperty('lang') ?? '';
// Always clone the page to the new language.
if ($oldLang !== $newLang) {

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Grav\Common\Flex\Types\Pages\Storage;
use FilesystemIterator;
use Grav\Common\Debugger;
use Grav\Common\Flex\Types\Pages\PageIndex;
use Grav\Common\Grav;
@@ -19,6 +20,11 @@ use Grav\Framework\Filesystem\Filesystem;
use Grav\Framework\Flex\Storage\FolderStorage;
use RocketTheme\Toolbox\File\MarkdownFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use SplFileInfo;
use function assert;
use function in_array;
use function is_string;
/**
* Class GravPageStorage
@@ -51,8 +57,8 @@ class PageStorage extends FolderStorage
{
parent::initOptions($options);
$this->flags = \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
| \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS;
$this->flags = FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::CURRENT_AS_FILEINFO
| FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS;
$grav = Grav::instance();
@@ -102,7 +108,7 @@ class PageStorage extends FolderStorage
} else {
$frontmatter = $file->raw();
}
} catch (\RuntimeException $e) {
} catch (RuntimeException $e) {
$frontmatter = 'ERROR: ' . $e->getMessage();
}
@@ -119,7 +125,7 @@ class PageStorage extends FolderStorage
$file = $this->getFile($path);
try {
$raw = $file->raw();
} catch (\RuntimeException $e) {
} catch (RuntimeException $e) {
$raw = 'ERROR: ' . $e->getMessage();
}
@@ -139,9 +145,9 @@ class PageStorage extends FolderStorage
if ($key !== '') {
$key .= '/';
}
$order = $keys['order'] ?? 0;
$order = $keys['order'] ?? null;
$folder = $keys['folder'] ?? 'undefined';
$key .= $order ? sprintf('%02d.%s', $order, $folder) : $folder;
$key .= is_numeric($order) ? sprintf('%02d.%s', $order, $folder) : $folder;
}
$params = $includeParams ? $this->buildStorageKeyParams($keys) : '';
@@ -204,27 +210,36 @@ class PageStorage extends FolderStorage
/**
* @param array $row
* @param bool $setDefaultLang
* @return array
*/
public function extractKeysFromRow(array $row): array
public function extractKeysFromRow(array $row, bool $setDefaultLang = true): 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'] ?? '';
$order = $row['order'] ?? $meta['order'] ?? $keyMeta['order'] ?? null;
$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])) {
if ($setDefaultLang && empty($meta['markdown'][$lang])) {
$grav = Grav::instance();
/** @var Language $langauge */
/** @var Language $language */
$language = $grav['language'];
if ($lang === $language->getDefault()) {
$lang = '';
$default = $language->getDefault();
// Make sure that the default language file doesn't exist before overriding it.
if (empty($meta['markdown'][$default])) {
if ($this->include_default_lang_file_extension) {
if ($lang === '') {
$lang = $language->getDefault();
}
} elseif ($lang === $language->getDefault()) {
$lang = '';
}
}
}
@@ -232,7 +247,7 @@ class PageStorage extends FolderStorage
'key' => null,
'params' => null,
'parent_key' => $parentKey,
'order' => (int)$order,
'order' => is_numeric($order) ? (int)$order : null,
'folder' => $folder,
'template' => $template,
'lang' => $lang
@@ -271,7 +286,7 @@ class PageStorage extends FolderStorage
'key' => $key,
'params' => $params,
'parent_key' => $parentKey,
'order' => (int)$order,
'order' => is_numeric($order) ? (int)$order : null,
'folder' => $folder,
'template' => $template,
'lang' => $language
@@ -352,7 +367,7 @@ class PageStorage extends FolderStorage
try {
if ($key === '' && empty($row['root'])) {
throw new \RuntimeException('No storage key given');
throw new RuntimeException('No storage key given');
}
$grav = Grav::instance();
@@ -365,7 +380,7 @@ class PageStorage extends FolderStorage
$oldKey = $row['__META']['storage_key'] ?? null;
if (is_string($oldKey)) {
// Initialize all old key-related variables.
$oldKeys = $this->extractKeysFromRow(['__META' => $row['__META']]);
$oldKeys = $this->extractKeysFromRow(['__META' => $row['__META']], false);
$oldFolder = $this->buildFolder($oldKeys);
$oldFilename = $this->buildFilename($oldKeys);
@@ -395,7 +410,7 @@ class PageStorage extends FolderStorage
$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}");
throw new RuntimeException("Changing page template failed: {$oldFilepath} => {$newFilepath}");
}
$debugger->addMessage("Page template changed: {$oldFilename} => {$newFilename}", 'debug');
} else {
@@ -430,10 +445,10 @@ class PageStorage extends FolderStorage
if ($locator->isStream($newFolder)) {
$locator->clearCache();
}
} catch (\RuntimeException $e) {
} catch (RuntimeException $e) {
$name = isset($file) ? $file->filename() : $newKey;
throw new \RuntimeException(sprintf('Flex saveRow(%s): %s', $name, $e->getMessage()));
throw new RuntimeException(sprintf('Flex saveRow(%s): %s', $name, $e->getMessage()));
}
$row['__META'] = $this->getObjectMeta($newKey, true);
@@ -477,6 +492,8 @@ class PageStorage extends FolderStorage
*/
protected function buildIndex(): array
{
$this->clearCache();
return $this->getIndexMeta();
}
@@ -506,9 +523,9 @@ class PageStorage extends FolderStorage
if (is_string($path) && file_exists($path)) {
$modified = filemtime($path);
$iterator = new \FilesystemIterator($path, $this->flags);
$iterator = new FilesystemIterator($path, $this->flags);
/** @var \SplFileInfo $info */
/** @var SplFileInfo $info */
foreach ($iterator as $k => $info) {
// Ignore all hidden files if set.
if ($k === '' || ($this->ignore_hidden && $k[0] === '.')) {
@@ -517,14 +534,14 @@ class PageStorage extends FolderStorage
if ($info->isDir()) {
// Ignore all folders in ignore list.
if ($this->ignore_folders && \in_array($k, $this->ignore_folders, true)) {
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)) {
if ($this->ignore_files && in_array($k, $this->ignore_files, true)) {
continue;
}
@@ -546,8 +563,8 @@ class PageStorage extends FolderStorage
$rawRoute = trim(preg_replace(PageIndex::PAGE_ROUTE_REGEX, '/', "/{$key}") ?? '', '/');
$route = PageIndex::normalizeRoute($rawRoute);
ksort($markdown, SORT_NATURAL);
ksort($children, SORT_NATURAL);
ksort($markdown, SORT_NATURAL | SORT_FLAG_CASE);
ksort($children, SORT_NATURAL | SORT_FLAG_CASE);
$file = array_key_first($markdown[''] ?? (reset($markdown) ?: []));
@@ -596,6 +613,7 @@ class PageStorage extends FolderStorage
if ($current === null) {
break;
}
$meta = $this->getObjectMeta($current);
$storage_key = $meta['storage_key'];
@@ -610,7 +628,7 @@ class PageStorage extends FolderStorage
$list[$storage_key] = $meta;
} while ($queue);
ksort($list, SORT_NATURAL);
ksort($list, SORT_NATURAL | SORT_FLAG_CASE);
// Update parent timestamps.
foreach (array_reverse($list) as $storage_key => $meta) {
@@ -644,6 +662,6 @@ class PageStorage extends FolderStorage
*/
protected function getNewKey(): string
{
throw new \RuntimeException('Generating random key is disabled for pages');
throw new RuntimeException('Generating random key is disabled for pages');
}
}

View File

@@ -18,6 +18,9 @@ use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Pages;
use Grav\Common\Utils;
use Grav\Framework\Flex\Interfaces\FlexIndexInterface;
use InvalidArgumentException;
use function is_array;
use function is_string;
/**
* Implements PageLegacyInterface.
@@ -174,7 +177,7 @@ trait PageLegacyTrait
* @param string|array $params
* @param bool $pagination
* @return PageCollectionInterface|Collection
* @throws \InvalidArgumentException
* @throws InvalidArgumentException
*/
public function collection($params = 'content', $pagination = true)
{
@@ -186,7 +189,7 @@ trait PageLegacyTrait
// 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');
throw new InvalidArgumentException('Argument should be either header variable name or array of parameters');
}
if (!$pagination) {

View File

@@ -17,6 +17,7 @@ use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Pages;
use Grav\Common\Utils;
use Grav\Framework\Filesystem\Filesystem;
use RuntimeException;
/**
* Implements PageRoutableInterface.
@@ -37,7 +38,7 @@ trait PageRoutableTrait
}
if (null !== $var) {
throw new \RuntimeException('Not Implemented');
throw new RuntimeException('Not Implemented');
}
if ($this->root()) {

View File

@@ -90,8 +90,17 @@ trait PageTranslateTrait
$list[$languageCode ?: $defaultCode] = $route ?? '';
}
return array_filter($list, static function ($var) {
$list = array_filter($list, static function ($var) {
return null !== $var;
});
// Hack to get the same result as with old pages.
foreach ($list as &$path) {
if ($path === '') {
$path = null;
}
}
return $list;
}
}

View File

@@ -16,6 +16,8 @@ use Grav\Common\Flex\Traits\FlexObjectTrait;
use Grav\Common\User\Access;
use Grav\Common\User\Interfaces\UserGroupInterface;
use Grav\Framework\Flex\FlexObject;
use Grav\Framework\Flex\Traits\FlexMediaTrait;
use function is_bool;
/**
* Flex User Group
@@ -29,10 +31,10 @@ class UserGroupObject extends FlexObject implements UserGroupInterface
{
use FlexGravTrait;
use FlexObjectTrait;
use FlexMediaTrait;
/** @var Access|null */
/** @var Access */
protected $_access;
/** @var array|null */
protected $access;
@@ -57,10 +59,8 @@ class UserGroupObject extends FlexObject implements UserGroupInterface
{
if ($scope === 'test') {
$scope = null;
} else {
if (!$this->getProperty('enabled', true)) {
return null;
}
} elseif (!$this->getProperty('enabled', true)) {
return null;
}
$access = $this->getAccess();

View File

@@ -13,8 +13,28 @@ namespace Grav\Common\Flex\Types\Users\Storage;
use Grav\Framework\Flex\Storage\FileStorage;
/**
* Class UserFileStorage
* @package Grav\Common\Flex\Types\Users\Storage
*/
class UserFileStorage extends FileStorage
{
/** @var bool */
public $caseSensitive;
/**
* @param string $key
* @return string
*/
public function normalizeKey(string $key): string
{
if ($this->caseSensitive === true) {
return $key;
}
return mb_strtolower($key);
}
/**
* {@inheritdoc}
* @see FlexStorageInterface::getMediaPath()
@@ -40,4 +60,15 @@ class UserFileStorage extends FileStorage
$row['access'] = $access;
}
}
/**
* @param array $options
* @return void
*/
protected function initOptions(array $options): void
{
parent::initOptions($options);
$this->caseSensitive = $options['case_sensitive'] ?? false;
}
}

View File

@@ -13,8 +13,28 @@ namespace Grav\Common\Flex\Types\Users\Storage;
use Grav\Framework\Flex\Storage\FolderStorage;
/**
* Class UserFolderStorage
* @package Grav\Common\Flex\Types\Users\Storage
*/
class UserFolderStorage extends FolderStorage
{
/** @var bool */
public $caseSensitive;
/**
* @param string $key
* @return string
*/
public function normalizeKey(string $key): string
{
if ($this->caseSensitive === true) {
return $key;
}
return mb_strtolower($key);
}
/**
* Prepares the row for saving and returns the storage key for the record.
*
@@ -30,4 +50,15 @@ class UserFolderStorage extends FolderStorage
$row['access'] = $access;
}
}
/**
* @param array $options
* @return void
*/
protected function initOptions(array $options): void
{
parent::initOptions($options);
$this->caseSensitive = $options['case_sensitive'] ?? false;
}
}

View File

@@ -0,0 +1,92 @@
<?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\Types\Users\Traits;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Medium\StaticImageMedium;
/**
* Trait UserObjectLegacyTrait
* @package Grav\Common\Flex\Types\Users\Traits
*/
trait UserObjectLegacyTrait
{
/**
* Merge two configurations together.
*
* @param array $data
* @return $this
* @deprecated 1.6 Use `->update($data)` instead (same but with data validation & filtering, file upload support).
*/
public function merge(array $data)
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->update($data) method instead', E_USER_DEPRECATED);
$this->setElements($this->getBlueprint()->mergeData($this->toArray(), $data));
return $this;
}
/**
* Return media object for the User's avatar.
*
* @return ImageMedium|StaticImageMedium|null
* @deprecated 1.6 Use ->getAvatarImage() method instead.
*/
public function getAvatarMedia()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarImage() method instead', E_USER_DEPRECATED);
return $this->getAvatarImage();
}
/**
* Return the User's avatar URL
*
* @return string
* @deprecated 1.6 Use ->getAvatarUrl() method instead.
*/
public function avatarUrl()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarUrl() method instead', E_USER_DEPRECATED);
return $this->getAvatarUrl();
}
/**
* Checks user authorization to the action.
* Ensures backwards compatibility
*
* @param string $action
* @return bool
* @deprecated 1.5 Use ->authorize() method instead.
*/
public function authorise($action)
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->authorize() method instead', E_USER_DEPRECATED);
return $this->authorize($action) ?? false;
}
/**
* Implements Countable interface.
*
* @return int
* @deprecated 1.6 Method makes no sense for user account.
*/
public function count()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
return \count($this->jsonSerialize());
}
}

View File

@@ -16,6 +16,7 @@ use Grav\Common\Flex\Traits\FlexGravTrait;
use Grav\Common\User\Interfaces\UserCollectionInterface;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Framework\Flex\FlexCollection;
use function is_string;
class UserCollection extends FlexCollection implements UserCollectionInterface
{
@@ -43,7 +44,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
public function load($username): UserInterface
{
if ($username !== '') {
$key = mb_strtolower($username);
$key = $this->filterUsername($username);
$user = $this->get($key);
if ($user) {
return $user;
@@ -84,7 +85,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
} elseif ($field === 'flex_key') {
$user = $this->withKeyField('flex_key')->get($query);
} elseif ($field === 'username') {
$user = $this->get(mb_strtolower($query));
$user = $this->get($this->filterUsername($query));
} else {
$user = parent::find($query, $field);
}
@@ -114,4 +115,18 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
return $exists;
}
/**
* @param string $key
* @return string
*/
protected function filterUsername(string $key)
{
$storage = $this->getFlexDirectory()->getStorage();
if (method_exists($storage, 'normalizeKey')) {
return $storage->normalizeKey($key);
}
return mb_strtolower($key);
}
}

View File

@@ -20,9 +20,14 @@ use Grav\Common\User\Interfaces\UserInterface;
use Grav\Framework\Flex\FlexIndex;
use Grav\Framework\Flex\Interfaces\FlexStorageInterface;
use Monolog\Logger;
use function count;
use function is_string;
use function method_exists;
class UserIndex extends FlexIndex
{
public const VERSION = parent::VERSION . '.1';
use FlexGravTrait;
use FlexIndexTrait;
@@ -33,12 +38,35 @@ class UserIndex extends FlexIndex
public static function loadEntriesFromStorage(FlexStorageInterface $storage): array
{
// Load saved index.
$index = static::loadEntriesFromIndex($storage);
$index = static::loadIndex($storage);
$version = $index['version'] ?? 0;
$force = static::VERSION !== $version;
// TODO: Following check flex index to be out of sync after some saves, disabled until better solution is found.
//$timestamp = $index['timestamp'] ?? 0;
//if (!$force && $timestamp && $timestamp > time() - 1) {
// return $index['index'];
//}
// Load up to date index.
$entries = parent::loadEntriesFromStorage($storage);
return static::updateIndexFile($storage, $index, $entries);
return static::updateIndexFile($storage, $index['index'], $entries, ['force_update' => $force]);
}
/**
* @param array $meta
* @param array $data
* @param FlexStorageInterface $storage
* @return void
*/
public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage)
{
// Username can also be number and stored as such.
$key = (string)($data['username'] ?? $meta['key'] ?? $meta['storage_key']);
$meta['key'] = static::filterUsername($key, $storage);
$meta['email'] = isset($data['email']) ? mb_strtolower($data['email']) : null;
}
/**
@@ -52,7 +80,7 @@ class UserIndex extends FlexIndex
public function load($username): UserInterface
{
if ($username !== '') {
$key = mb_strtolower($username);
$key = static::filterUsername($username, $this->getFlexDirectory()->getStorage());
$user = $this->get($key);
if ($user) {
return $user;
@@ -95,7 +123,7 @@ class UserIndex extends FlexIndex
} elseif ($field === 'email') {
$user = $this->withKeyField('email')->get($query);
} elseif ($field === 'username') {
$user = $this->get(mb_strtolower($query));
$user = $this->get(static::filterUsername($query, $this->getFlexDirectory()->getStorage()));
} else {
$user = $this->__call('find', [$query, $field]);
}
@@ -109,13 +137,17 @@ class UserIndex extends FlexIndex
}
/**
* @param array $entry
* @param array $data
* @param string $key
* @param FlexStorageInterface $storage
* @return string
*/
protected static function updateIndexData(array &$entry, array $data)
protected static function filterUsername(string $key, FlexStorageInterface $storage): string
{
$entry['key'] = mb_strtolower($entry['key']);
$entry['email'] = isset($data['email']) ? mb_strtolower($data['email']) : null;
if (method_exists($storage, 'normalizeKey')) {
return $storage->normalizeKey($key);
}
return mb_strtolower($key);
}
/**
@@ -140,7 +172,7 @@ class UserIndex extends FlexIndex
*/
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));
$message = sprintf('Flex: User index updated, %d objects (%d added, %d updated, %d removed).', count($entries), count($added), count($updated), count($removed));
$grav = Grav::instance();

View File

@@ -11,22 +11,23 @@ declare(strict_types=1);
namespace Grav\Common\Flex\Types\Users;
use Grav\Common\Config\Config;
use Grav\Common\Data\Blueprint;
use Grav\Common\Flex\Traits\FlexGravTrait;
use Grav\Common\Flex\Traits\FlexObjectTrait;
use Grav\Common\Flex\Types\Users\Traits\UserObjectLegacyTrait;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Media\Interfaces\MediaUploadInterface;
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\Types\UserGroups\UserGroupCollection;
use Grav\Common\Flex\Types\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;
@@ -35,10 +36,15 @@ use Grav\Framework\Flex\FlexObject;
use Grav\Framework\Flex\Storage\FileStorage;
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\Event\Event;
use RocketTheme\Toolbox\File\FileInterface;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use function assert;
use function is_array;
use function is_bool;
use function is_object;
/**
* Flex User
@@ -59,7 +65,7 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
* @property bool $authenticated
* @property bool $authorized
*/
class UserObject extends FlexObject implements UserInterface, MediaManipulationInterface, \Countable
class UserObject extends FlexObject implements UserInterface, \Countable
{
use FlexGravTrait;
use FlexObjectTrait;
@@ -68,6 +74,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
getMediaFolder as private getFlexMediaFolder;
}
use UserTrait;
use UserObjectLegacyTrait;
/** @var array|null */
protected $_uploads_original;
@@ -75,10 +82,10 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
/** @var FileInterface|null */
protected $_storage;
/** @var UserGroupCollection|UserGroupIndex|null */
/** @var UserGroupIndex */
protected $_groups;
/** @var Access|null */
/** @var Access */
protected $_access;
/** @var array|null */
@@ -120,6 +127,33 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
$this->defProperty('state', 'enabled');
}
/**
* @return void
*/
public function onPrepareRegistration(): void
{
if (!$this->getProperty('access')) {
/** @var Config $config */
$config = Grav::instance()['config'];
$groups = $config->get('plugins.login.user_registration.groups', '');
$access = $config->get('plugins.login.user_registration.access', ['site' => ['login' => true]]);
$this->setProperty('groups', $groups);
$this->setProperty('access', $access);
}
}
/**
* Helper to get content editor will fall back if not set
*
* @return string
*/
public function getContentEditor(): string
{
return $this->getProperty('content_editor', 'default');
}
/**
* Get value by using dot notation for nested arrays/objects.
*
@@ -127,7 +161,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @param string|null $separator Separator, defaults to '.'
* @return mixed Value.
*/
public function get($name, $default = null, $separator = null)
@@ -142,7 +176,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value New value.
* @param string $separator Separator, defaults to '.'
* @param string|null $separator Separator, defaults to '.'
* @return $this
*/
public function set($name, $value, $separator = null)
@@ -158,7 +192,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
* @example $data->undef('this.is.my.nested.variable');
*
* @param string $name Dot separated path to the requested value.
* @param string $separator Separator, defaults to '.'
* @param string|null $separator Separator, defaults to '.'
* @return $this
*/
public function undef($name, $separator = null)
@@ -175,7 +209,7 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @param string|null $separator Separator, defaults to '.'
* @return $this
*/
public function def($name, $default = null, $separator = null)
@@ -247,7 +281,8 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
{
$value = parent::getFormValue($name, null, $separator);
if ($name === 'avatar') {
$settings = $this->getFieldSettings($name);
if ($settings['media_field'] ?? false === true) {
return $this->parseFileProperty($value);
}
@@ -320,23 +355,23 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @param string|null $separator Separator, defaults to '.'
* @return $this
* @throws \RuntimeException
* @throws RuntimeException
*/
public function join($name, $value, $separator = null)
{
$separator = $separator ?? '.';
$old = $this->get($name, null, $separator);
if ($old !== null) {
if (!\is_array($old)) {
throw new \RuntimeException('Value ' . $old);
if (!is_array($old)) {
throw new RuntimeException('Value ' . $old);
}
if (\is_object($value)) {
if (is_object($value)) {
$value = (array) $value;
} elseif (!\is_array($value)) {
throw new \RuntimeException('Value ' . $value);
} elseif (!is_array($value)) {
throw new RuntimeException('Value ' . $value);
}
$value = $this->getBlueprint()->mergeData($old, $value, $name, $separator);
@@ -364,12 +399,12 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @param string|null $separator Separator, defaults to '.'
* @return $this
*/
public function joinDefaults($name, $value, $separator = null)
{
if (\is_object($value)) {
if (is_object($value)) {
$value = (array) $value;
}
@@ -387,17 +422,17 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
* Get value from the configuration and join it with given data.
*
* @param string $name Dot separated path to the requested value.
* @param array|object $value Value to be joined.
* @param array|object $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return array
* @throws \RuntimeException
* @throws RuntimeException
*/
public function getJoined($name, $value, $separator = null)
{
if (\is_object($value)) {
if (is_object($value)) {
$value = (array) $value;
} elseif (!\is_array($value)) {
throw new \RuntimeException('Value ' . $value);
} elseif (!is_array($value)) {
throw new RuntimeException('Value ' . $value);
}
$old = $this->get($name, null, $separator);
@@ -407,8 +442,8 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
return $value;
}
if (!\is_array($old)) {
throw new \RuntimeException('Value ' . $old);
if (!is_array($old)) {
throw new RuntimeException('Value ' . $old);
}
// Return joined data.
@@ -501,6 +536,8 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
/**
* Save user without the username
*
* @return static
*/
public function save()
{
@@ -521,7 +558,98 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
$this->setProperty('hashed_password', Authentication::create($password));
}
return parent::save();
// Backwards compatibility with older plugins.
$fireEvents = $this->isAdminSite() && $this->getFlexDirectory()->getConfig('object.compat.events', true);
$grav = $this->getContainer();
if ($fireEvents) {
$self = $this;
$grav->fireEvent('onAdminSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => &$self]));
if ($self !== $this) {
throw new RuntimeException('Switching Flex User object during onAdminSave event is not supported! Please update plugin.');
}
}
$instance = parent::save();
// Backwards compatibility with older plugins.
if ($fireEvents) {
$grav->fireEvent('onAdminAfterSave', new Event(['type' => 'flex', 'directory' => $this->getFlexDirectory(), 'object' => $this]));
}
return $instance;
}
/**
* @return array
*/
public function prepareStorage(): array
{
$elements = parent::prepareStorage();
// Do not save authorization information.
unset($elements['authenticated'], $elements['authorized']);
return $elements;
}
/**
* @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();
// Check for shared media
if (!$folder && !$this->getFlexDirectory()->getMediaFolder()) {
$this->_loadMedia = false;
$folder = $this->getBlueprint()->fields()['avatar']['destination'] ?? 'user://accounts/avatars';
}
return $folder;
}
/**
* @param string $name
* @return Blueprint
*/
protected function doGetBlueprint(string $name = ''): Blueprint
{
$blueprint = $this->getFlexDirectory()->getBlueprint($name ? '.' . $name : $name);
// HACK: With folder storage we need to ignore the avatar destination.
if ($this->getFlexDirectory()->getMediaFolder()) {
$field = $blueprint->get('form/fields/avatar');
if ($field) {
unset($field['destination']);
$blueprint->set('form/fields/avatar', $field);
}
}
return $blueprint;
}
/**
@@ -541,91 +669,236 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
return parent::isAuthorizedOverride($user, $action, $scope, $isMe);
}
/**
* @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).
*
* @return MediaCollectionInterface Representation of associated media.
*/
protected function getOriginalMedia()
{
$folder = $this->getMediaFolder();
if ($folder) {
$folder .= '/original';
}
return (new Media($folder ?? '', $this->getMediaOrder()))->setTimestamps();
}
/**
* @param array $files
*/
protected function setUpdatedMedia(array $files): void
{
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$media = $this->getMedia();
$list = [];
$list_original = [];
foreach ($files as $field => $group) {
if ($field === '') {
continue;
}
$field = (string)$field;
// Load settings for the field.
$settings = $this->getMediaFieldSettings($field);
foreach ($group as $filename => $file) {
if ($file) {
// File upload.
$filename = $file->getClientFilename();
/** @var FormFlashFile $file */
$data = $file->jsonSerialize();
unset($data['tmp_name'], $data['path']);
} else {
// File delete.
$data = null;
}
if ($file) {
// Check file upload against media limits.
$filename = $media->checkUploadedFile($file, $filename, $settings);
}
$self = $settings['self'];
if ($this->_loadMedia && $self) {
$filepath = $filename;
} else {
$filepath = "{$settings['destination']}/{$filename}";
// For backwards compatibility we are always using relative path from the installation root.
if ($locator->isStream($filepath)) {
$filepath = $locator->findResource($filepath, false, true);
}
}
// Special handling for original images.
if (strpos($field, '/original')) {
if ($this->_loadMedia && $self) {
$list_original[$filename] = [$file, $settings];
}
continue;
}
$list[$filename] = [$file, $settings];
if (null !== $data) {
$data['name'] = $filename;
$data['path'] = $filepath;
$this->setNestedProperty("{$field}\n{$filepath}", $data, "\n");
} else {
$this->unsetNestedProperty("{$field}\n{$filepath}", "\n");
}
}
}
$this->_uploads = $list;
$this->_uploads_original = $list_original;
}
protected function saveUpdatedMedia(): void
{
$media = $this->getMedia();
if (!$media instanceof MediaUploadInterface) {
throw new RuntimeException('Internal error UO101');
}
// Upload/delete original sized images.
/**
* @var string $filename
* @var UploadedFileInterface|array|null $file
*/
foreach ($this->_uploads_original ?? [] as $filename => $file) {
$filename = 'original/' . $filename;
if (is_array($file)) {
[$file, $settings] = $file;
} else {
$settings = null;
}
if ($file instanceof UploadedFileInterface) {
$media->copyUploadedFile($file, $filename, $settings);
} else {
$media->deleteFile($filename, $settings);
}
}
// Upload/delete altered files.
/**
* @var string $filename
* @var UploadedFileInterface|array|null $file
*/
foreach ($this->getUpdatedMedia() as $filename => $file) {
if (is_array($file)) {
[$file, $settings] = $file;
} else {
$settings = null;
}
if ($file instanceof UploadedFileInterface) {
$media->copyUploadedFile($file, $filename, $settings);
} else {
$media->deleteFile($filename, $settings);
}
}
$this->setUpdatedMedia([]);
$this->clearMediaCache();
}
/**
* @param array|mixed $value
* @return array|mixed
*/
protected function parseFileProperty($value)
{
if (!is_array($value)) {
return $value;
}
$originalMedia = $this->getOriginalMedia();
$resizedMedia = $this->getMedia();
$list = [];
foreach ($value as $filename => $info) {
if (!is_array($info)) {
continue;
}
/** @var Medium|null $thumbFile */
$thumbFile = $resizedMedia[$filename];
/** @var Medium|null $imageFile */
$imageFile = $originalMedia[$filename] ?? $thumbFile;
if ($thumbFile && $imageFile) {
$list[$filename] = [
'name' => $info['name'] ?? null,
'type' => $info['type'] ?? null,
'size' => $info['size'] ?? null,
'path' => $info['path'] ?? null,
'image_url' => $imageFile->url(),
'thumb_url' => $thumbFile->url(),
'cropData' => (object)($imageFile->metadata()['upload']['crop'] ?? [])
];
}
}
return $list;
}
/**
* @return array
*/
public function prepareStorage(): array
protected function doSerialize(): array
{
$elements = parent::prepareStorage();
// Do not save authorization information.
unset($elements['authenticated'], $elements['authorized']);
return $elements;
return [
'type' => $this->getFlexType(),
'key' => $this->getKey(),
'elements' => $this->jsonSerialize(),
'storage' => $this->getMetaData()
];
}
/**
* Merge two configurations together.
*
* @param array $data
* @return $this
* @deprecated 1.6 Use `->update($data)` instead (same but with data validation & filtering, file upload support).
* @return UserGroupIndex
*/
public function merge(array $data)
protected function getUserGroups()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->update($data) method instead', E_USER_DEPRECATED);
$grav = Grav::instance();
$this->setElements($this->getBlueprint()->mergeData($this->toArray(), $data));
/** @var Flex $flex */
$flex = $grav['flex'];
return $this;
/** @var UserGroupCollection|null $groups */
$groups = $flex->getDirectory('user-groups');
if ($groups) {
/** @var UserGroupIndex $index */
$index = $groups->getIndex();
return $index;
}
return $grav['user_groups'];
}
/**
* Return media object for the User's avatar.
*
* @return ImageMedium|StaticImageMedium|null
* @deprecated 1.6 Use ->getAvatarImage() method instead.
*/
public function getAvatarMedia()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarImage() method instead', E_USER_DEPRECATED);
return $this->getAvatarImage();
}
/**
* Return the User's avatar URL
*
* @return string
* @deprecated 1.6 Use ->getAvatarUrl() method instead.
*/
public function avatarUrl()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getAvatarUrl() method instead', E_USER_DEPRECATED);
return $this->getAvatarUrl();
}
/**
* Checks user authorization to the action.
* Ensures backwards compatibility
*
* @param string $action
* @return bool
* @deprecated 1.5 Use ->authorize() method instead.
*/
public function authorise($action)
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use ->authorize() method instead', E_USER_DEPRECATED);
return $this->authorize($action) ?? false;
}
/**
* Implements Countable interface.
*
* @return int
* @deprecated 1.6 Method makes no sense for user account.
*/
public function count()
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6', E_USER_DEPRECATED);
return \count($this->jsonSerialize());
}
/**
* @return UserGroupIndex|UserGroupCollection
* @return UserGroupIndex
*/
protected function getGroups()
{
@@ -680,238 +953,4 @@ class UserObject extends FlexObject implements UserInterface, MediaManipulationI
{
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).
*
* @return MediaCollectionInterface Representation of associated media.
*/
protected function getOriginalMedia()
{
$folder = $this->getMediaFolder();
if ($folder) {
$folder .= '/original';
}
return (new Media($folder ?? '', $this->getMediaOrder()))->setTimestamps();
}
/**
* @param array $files
*/
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 ($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;
}
$list[$filename] = $file;
if ($data) {
$this->setNestedProperty("{$field}\n{$filepath}", $data, "\n");
} else {
$this->unsetNestedProperty("{$field}\n{$filepath}", "\n");
}
}
}
$this->_uploads = $list;
$this->_uploads_original = $list_original;
}
protected function saveUpdatedMedia(): void
{
// Upload/delete original sized images.
/** @var FormFlashFile|null $file */
foreach ($this->_uploads_original ?? [] as $name => $file) {
$name = 'original/' . $name;
if ($file) {
$this->uploadMediaFile($file, $name);
} else {
$this->deleteMediaFile($name);
}
}
/**
* @var string $filename
* @var UploadedFileInterface|null $file
*/
foreach ($this->getUpdatedMedia() as $filename => $file) {
if ($file) {
$this->uploadMediaFile($file, $filename);
} else {
$this->deleteMediaFile($filename);
}
}
$this->setUpdatedMedia([]);
}
/**
* @param array $value
* @return array
*/
protected function parseFileProperty($value)
{
if (!\is_array($value)) {
return $value;
}
$originalMedia = $this->getOriginalMedia();
$resizedMedia = $this->getMedia();
$list = [];
foreach ($value as $filename => $info) {
if (!\is_array($info)) {
continue;
}
/** @var Medium|null $thumbFile */
$thumbFile = $resizedMedia[$filename];
/** @var Medium|null $imageFile */
$imageFile = $originalMedia[$filename] ?? $thumbFile;
if ($thumbFile && $imageFile) {
$list[$filename] = [
'name' => $info['name'],
'type' => $info['type'],
'size' => $info['size'],
'image_url' => $imageFile->url(),
'thumb_url' => $thumbFile->url(),
'cropData' => (object)($imageFile->metadata()['upload']['crop'] ?? [])
];
}
}
return $list;
}
/**
* @return array
*/
protected function doSerialize(): array
{
return [
'type' => $this->getFlexType(),
'key' => $this->getKey(),
'elements' => $this->jsonSerialize(),
'storage' => $this->getMetaData()
];
}
/**
* @return UserGroupCollection|UserGroupIndex
*/
protected function getUserGroups()
{
$grav = Grav::instance();
/** @var Flex $flex */
$flex = $grav['flex'];
/** @var UserGroupCollection|null $groups */
$groups = $flex->getDirectory('user-groups');
if ($groups) {
/** @var UserGroupIndex $index */
$index = $groups->getIndex();
return $index;
}
return $grav['user_groups'];
}
}

View File

@@ -11,7 +11,12 @@ namespace Grav\Common\Form;
use Grav\Common\Filesystem\Folder;
use Grav\Framework\Form\FormFlash as FrameworkFormFlash;
use function is_array;
/**
* Class FormFlash
* @package Grav\Common\Form
*/
class FormFlash extends FrameworkFormFlash
{
/**
@@ -26,7 +31,7 @@ class FormFlash extends FrameworkFormFlash
continue;
}
foreach ($files as $file) {
if (\is_array($file)) {
if (is_array($file)) {
$file['tmp_name'] = $this->getTmpDir() . '/' . $file['tmp_name'];
$fields[$field][$file['path'] ?? $file['name']] = $file;
}

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\GPM;
use Grav\Common\Iterator;
/**
* Class AbstractCollection
* @package Grav\Common\GPM
*/
abstract class AbstractCollection extends Iterator
{
/**

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Common;
use Grav\Common\Iterator;
/**
* Class AbstractPackageCollection
* @package Grav\Common\GPM\Common
*/
abstract class AbstractPackageCollection extends Iterator
{
/** @var string */

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Common;
use Grav\Common\Iterator;
/**
* Class CachedCollection
* @package Grav\Common\GPM\Common
*/
class CachedCollection extends Iterator
{
/** @var array */

View File

@@ -9,18 +9,28 @@
namespace Grav\Common\GPM;
use Exception;
use Grav\Common\Grav;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Inflector;
use Grav\Common\Iterator;
use Grav\Common\Utils;
use RocketTheme\Toolbox\File\YamlFile;
use RuntimeException;
use function array_key_exists;
use function count;
use function in_array;
use function is_array;
use function is_object;
/**
* Class GPM
* @package Grav\Common\GPM
*/
class GPM extends Iterator
{
/** @var Local\Packages Local installed Packages */
private $installed;
/** @var Remote\Packages Remote available Packages */
private $repository;
@@ -29,7 +39,6 @@ class GPM extends Iterator
/** @var array Internal cache */
protected $cache;
/** @var array */
protected $install_paths = [
'plugins' => 'user/plugins/%name%',
@@ -51,7 +60,7 @@ class GPM extends Iterator
try {
$this->repository = new Remote\Packages($refresh, $callback);
$this->grav = new Remote\GravCore($refresh, $callback);
} catch (\Exception $e) {
} catch (Exception $e) {
}
}
@@ -214,7 +223,6 @@ class GPM extends Iterator
*/
public function getUpdatable($list_type_update = ['plugins' => true, 'themes' => true])
{
$items = ['total' => 0];
foreach ($list_type_update as $type => $type_updatable) {
if ($type_updatable === false) {
@@ -225,6 +233,7 @@ class GPM extends Iterator
$items[$type] = $to_update;
$items['total'] += count($to_update);
}
return $items;
}
@@ -237,6 +246,11 @@ class GPM extends Iterator
public function getUpdatablePlugins()
{
$items = [];
if (!$this->repository) {
return $items;
}
$repository = $this->repository['plugins'];
// local cache to speed things up
@@ -318,6 +332,11 @@ class GPM extends Iterator
public function getUpdatableThemes()
{
$items = [];
if (!$this->repository) {
return $items;
}
$repository = $this->repository['themes'];
// local cache to speed things up
@@ -365,6 +384,10 @@ class GPM extends Iterator
*/
public function getReleaseType($package_name)
{
if (!$this->repository) {
return null;
}
$repository = $this->repository['plugins'];
if (isset($repository[$package_name])) {
return $repository[$package_name]->release_type;
@@ -412,7 +435,7 @@ class GPM extends Iterator
*/
public function getRepositoryPlugin($slug)
{
return @$this->repository['plugins'][$slug];
return $this->repository['plugins'][$slug] ?? null;
}
/**
@@ -422,7 +445,7 @@ class GPM extends Iterator
*/
public function getRepositoryPlugins()
{
return $this->repository['plugins'];
return $this->repository['plugins'] ?? null;
}
/**
@@ -433,7 +456,7 @@ class GPM extends Iterator
*/
public function getRepositoryTheme($slug)
{
return @$this->repository['themes'][$slug];
return $this->repository['themes'][$slug] ?? null;
}
/**
@@ -443,7 +466,7 @@ class GPM extends Iterator
*/
public function getRepositoryThemes()
{
return $this->repository['themes'];
return $this->repository['themes'] ?? null;
}
/**
@@ -483,14 +506,14 @@ class GPM extends Iterator
if (!$themes && !$plugins) {
if (!is_writable(ROOT_DIR . '/cache/gpm')) {
throw new \RuntimeException("The cache/gpm folder is not writable. Please check the folder permissions.");
throw new RuntimeException("The cache/gpm folder is not writable. Please check the folder permissions.");
}
if ($ignore_exception) {
return false;
}
throw new \RuntimeException("GPM not reachable. Please check your internet connection or check the Grav site is reachable");
throw new RuntimeException("GPM not reachable. Please check your internet connection or check the Grav site is reachable");
}
if ($themes) {
@@ -517,7 +540,7 @@ class GPM extends Iterator
*
* @param string $package_file
* @param string $tmp
* @return null|string
* @return string|null
*/
public static function downloadPackage($package_file, $tmp)
{
@@ -525,7 +548,7 @@ class GPM extends Iterator
$filename = basename($package['path']);
if (Grav::instance()['config']->get('system.gpm.official_gpm_only') && $package['host'] !== 'getgrav.org') {
throw new \RuntimeException("Only official GPM URLs are allowed. You can modify this behavior in the System configuration.");
throw new RuntimeException('Only official GPM URLs are allowed. You can modify this behavior in the System configuration.');
}
$output = Response::get($package_file, []);
@@ -544,7 +567,7 @@ class GPM extends Iterator
*
* @param string $package_file
* @param string $tmp
* @return null|string
* @return string|null
*/
public static function copyPackage($package_file, $tmp)
{
@@ -564,7 +587,7 @@ class GPM extends Iterator
* Try to guess the package type from the source files
*
* @param string $source
* @return bool|string
* @return string|false
*/
public static function getPackageType($source)
{
@@ -608,7 +631,7 @@ class GPM extends Iterator
* Try to guess the package name from the source files
*
* @param string $source
* @return bool|string
* @return string|false
*/
public static function getPackageName($source)
{
@@ -628,7 +651,7 @@ class GPM extends Iterator
* Find/Parse the blueprint file
*
* @param string $source
* @return array|bool
* @return array|false
*/
public static function getBlueprints($source)
{
@@ -755,8 +778,7 @@ class GPM extends Iterator
*
* @param string $package_slug
* @param string $dependency_slug
*
* @return mixed
* @return mixed|null
*/
public function getVersionOfDependencyRequiredByPackage($package_slug, $dependency_slug)
{
@@ -778,7 +800,7 @@ class GPM extends Iterator
* @param string $version_with_operator
* @param array $ignore_packages_list
* @return bool
* @throws \RuntimeException
* @throws RuntimeException
*/
public function checkNoOtherPackageNeedsThisDependencyInALowerVersion(
$slug,
@@ -806,7 +828,7 @@ class GPM extends Iterator
);
if (!$compatible) {
if (!in_array($dependent_package, $ignore_packages_list, true)) {
throw new \RuntimeException(
throw new RuntimeException(
"Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
2
);
@@ -823,7 +845,8 @@ class GPM extends Iterator
* Check the passed packages list can be updated
*
* @param array $packages_names_list
* @throws \Exception
* @return void
* @throws Exception
*/
public function checkPackagesCanBeInstalled($packages_names_list)
{
@@ -845,14 +868,14 @@ class GPM extends Iterator
* `update` means the package is already installed and must be updated as a dependency needs a higher version.
*
* @param array $packages
* @return mixed
* @throws \Exception
* @return array
* @throws RuntimeException
*/
public function getDependencies($packages)
{
$dependencies = $this->calculateMergedDependenciesOfPackages($packages);
foreach ($dependencies as $dependency_slug => $dependencyVersionWithOperator) {
if (\in_array($dependency_slug, $packages, true)) {
if (in_array($dependency_slug, $packages, true)) {
unset($dependencies[$dependency_slug]);
continue;
}
@@ -866,7 +889,7 @@ class GPM extends Iterator
) === 1
) {
//Needs a Grav update first
throw new \RuntimeException("<red>One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
throw new RuntimeException("<red>One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
}
unset($dependencies[$dependency_slug]);
@@ -881,7 +904,7 @@ class GPM extends Iterator
) === 1
) {
//Needs a Grav update first
throw new \RuntimeException("<red>One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
throw new RuntimeException("<red>One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
}
unset($dependencies[$dependency_slug]);
@@ -913,7 +936,7 @@ class GPM extends Iterator
);
if (!$compatible) {
throw new \RuntimeException(
throw new RuntimeException(
'Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the one installed. This package must be updated. Please get in touch with its developer.',
2
);
@@ -926,7 +949,7 @@ class GPM extends Iterator
if ($this->firstVersionIsLower($latestRelease, $dependencyVersion)) {
//throw an exception if a required version cannot be found in the GPM yet
throw new \RuntimeException(
throw new RuntimeException(
'Dependency <cyan>' . $package_yaml['name'] . '</cyan> is required in version <cyan>' . $dependencyVersion . '</cyan> which is higher than the latest release, <cyan>' . $latestRelease . '</cyan>. Try running `bin/gpm -f index` to force a refresh of the GPM cache',
1
);
@@ -955,7 +978,7 @@ class GPM extends Iterator
);
if (!$compatible) {
throw new \Exception(
throw new RuntimeException(
'Dependency <cyan>' . $dependency_slug . '</cyan> is required in an older version than the latest release available, and it cannot be installed. This package must be updated. Please get in touch with its developer.',
2
);
@@ -975,6 +998,7 @@ class GPM extends Iterator
/**
* @param array $dependencies_slugs
* @return void
*/
public function checkNoOtherPackageNeedsTheseDependenciesInALowerVersion($dependencies_slugs)
{
@@ -1003,7 +1027,7 @@ class GPM extends Iterator
* @param string $packageName The package information
* @param array $dependencies The dependencies array
* @return array
* @throws \Exception
* @throws Exception
*/
private function calculateMergedDependenciesOfPackage($packageName, $dependencies)
{
@@ -1046,7 +1070,7 @@ class GPM extends Iterator
$current_package_version_number = $this->calculateVersionNumberFromDependencyVersion($current_package_version_information);
if (!$current_package_version_number) {
throw new \RuntimeException(
throw new RuntimeException(
'Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName,
1
);
@@ -1076,7 +1100,7 @@ class GPM extends Iterator
$current_package_version_number
);
if (!$compatible) {
throw new \RuntimeException(
throw new RuntimeException(
'Dependency ' . $current_package_name . ' is required in two incompatible versions',
2
);
@@ -1095,8 +1119,7 @@ class GPM extends Iterator
* Calculates and merges the dependencies of the passed packages
*
* @param array $packages
* @return mixed
* @throws \Exception
* @return array
*/
public function calculateMergedDependenciesOfPackages($packages)
{
@@ -1119,7 +1142,7 @@ class GPM extends Iterator
* $versionInformation == '' => returns null
*
* @param string $version
* @return null|string
* @return string|null
*/
public function calculateVersionNumberFromDependencyVersion($version)
{
@@ -1182,12 +1205,12 @@ class GPM extends Iterator
$version1array = explode('.', $version1);
$version2array = explode('.', $version2);
if (\count($version1array) > \count($version2array)) {
list($version1array, $version2array) = [$version2array, $version1array];
if (count($version1array) > count($version2array)) {
[$version1array, $version2array] = [$version2array, $version1array];
}
$i = 0;
while ($i < \count($version1array) - 1) {
while ($i < count($version1array) - 1) {
if ($version1array[$i] != $version2array[$i]) {
return false;
}

View File

@@ -9,9 +9,18 @@
namespace Grav\Common\GPM;
use DirectoryIterator;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use ZipArchive;
use function count;
use function in_array;
use function is_string;
/**
* Class Installer
* @package Grav\Common\GPM
*/
class Installer
{
/** @const No error */
@@ -62,7 +71,7 @@ class Installer
* @param string $zip the local path to ZIP package
* @param string $destination The local path to the Grav Instance
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
* @param string $extracted The local path to the extacted ZIP package
* @param string|null $extracted The local path to the extacted ZIP package
* @param bool $keepExtracted True if you want to keep the original files
* @return bool True if everything went fine, False otherwise.
*/
@@ -157,11 +166,11 @@ class Installer
*
* @param string $zip_file
* @param string $destination
* @return bool|string
* @return string|false
*/
public static function unZip($zip_file, $destination)
{
$zip = new \ZipArchive();
$zip = new ZipArchive();
$archive = $zip->open($zip_file);
if ($archive === true) {
@@ -185,6 +194,7 @@ class Installer
self::$error = self::ZIP_EXTRACT_ERROR;
self::$error_zip = $archive;
return false;
}
@@ -193,7 +203,7 @@ class Installer
*
* @param string $installer_file_folder The folder path that contains install.php
* @param bool $is_install True if install, false if removal
* @return null|string
* @return string|null
*/
private static function loadInstaller($installer_file_folder, $is_install)
{
@@ -203,12 +213,12 @@ class Installer
$install_file = $installer_file_folder . DS . 'install.php';
if (file_exists($install_file)) {
require_once $install_file;
} else {
if (!file_exists($install_file)) {
return null;
}
require_once $install_file;
if ($is_install) {
$slug = '';
if (($pos = strpos($installer_file_folder, 'grav-plugin-')) !== false) {
@@ -281,8 +291,8 @@ class Installer
*/
public static function sophisticatedInstall($source_path, $install_path, $ignores = [], $keep_source = false)
{
foreach (new \DirectoryIterator($source_path) as $file) {
if ($file->isLink() || $file->isDot() || \in_array($file->getFilename(), $ignores, true)) {
foreach (new DirectoryIterator($source_path) as $file) {
if ($file->isLink() || $file->isDot() || in_array($file->getFilename(), $ignores, true)) {
continue;
}
@@ -373,7 +383,7 @@ class Installer
self::$error = self::NOT_DIRECTORY;
}
if (\count($exclude) && \in_array(self::$error, $exclude, true)) {
if (count($exclude) && in_array(self::$error, $exclude, true)) {
return true;
}
@@ -456,35 +466,35 @@ class Installer
$msg = 'Unable to extract the package. ';
if (self::$error_zip) {
switch (self::$error_zip) {
case \ZipArchive::ER_EXISTS:
case ZipArchive::ER_EXISTS:
$msg .= 'File already exists.';
break;
case \ZipArchive::ER_INCONS:
case ZipArchive::ER_INCONS:
$msg .= 'Zip archive inconsistent.';
break;
case \ZipArchive::ER_MEMORY:
case ZipArchive::ER_MEMORY:
$msg .= 'Memory allocation failure.';
break;
case \ZipArchive::ER_NOENT:
case ZipArchive::ER_NOENT:
$msg .= 'No such file.';
break;
case \ZipArchive::ER_NOZIP:
case ZipArchive::ER_NOZIP:
$msg .= 'Not a zip archive.';
break;
case \ZipArchive::ER_OPEN:
case ZipArchive::ER_OPEN:
$msg .= "Can't open file.";
break;
case \ZipArchive::ER_READ:
case ZipArchive::ER_READ:
$msg .= 'Read error.';
break;
case \ZipArchive::ER_SEEK:
case ZipArchive::ER_SEEK:
$msg .= 'Seek error.';
break;
}

View File

@@ -12,6 +12,7 @@ namespace Grav\Common\GPM;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use RocketTheme\Toolbox\File\FileInterface;
use function is_string;
/**
* Class Licenses
@@ -35,7 +36,6 @@ class Licenses
public static function set($slug, $license)
{
$licenses = self::getLicenseFile();
/** @var array $data */
$data = (array)$licenses->content();
$slug = strtolower($slug);
@@ -43,7 +43,7 @@ class Licenses
return false;
}
if (!\is_string($license)) {
if (!is_string($license)) {
if (isset($data['licenses'][$slug])) {
unset($data['licenses'][$slug]);
} else {
@@ -62,13 +62,12 @@ class Licenses
/**
* Returns the license for a Premium package
*
* @param string $slug
* @param string|null $slug
* @return array|string
*/
public static function get($slug = null)
{
$licenses = self::getLicenseFile();
/** @var array $data */
$data = (array)$licenses->content();
$licenses->free();
$slug = strtolower($slug);

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Local;
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
/**
* Class AbstractPackageCollection
* @package Grav\Common\GPM\Local
*/
abstract class AbstractPackageCollection extends BaseCollection
{
/**

View File

@@ -11,7 +11,12 @@ namespace Grav\Common\GPM\Local;
use Grav\Common\Data\Data;
use Grav\Common\GPM\Common\Package as BasePackage;
use Parsedown;
/**
* Class Package
* @package Grav\Common\GPM\Local
*/
class Package extends BasePackage
{
/** @var array */
@@ -29,7 +34,7 @@ class Package extends BasePackage
$this->settings = $package->toArray();
$html_description = \Parsedown::instance()->line($this->__get('description'));
$html_description = Parsedown::instance()->line($this->__get('description'));
$this->data->set('slug', $package->__get('slug'));
$this->data->set('description_html', $html_description);
$this->data->set('description_plain', strip_tags($html_description));

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Local;
use Grav\Common\GPM\Common\CachedCollection;
/**
* Class Packages
* @package Grav\Common\GPM\Local
*/
class Packages extends CachedCollection
{
public function __construct()

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Local;
use Grav\Common\Grav;
/**
* Class Plugins
* @package Grav\Common\GPM\Local
*/
class Plugins extends AbstractPackageCollection
{
/** @var string */

View File

@@ -11,6 +11,10 @@ namespace Grav\Common\GPM\Local;
use Grav\Common\Grav;
/**
* Class Themes
* @package Grav\Common\GPM\Local
*/
class Themes extends AbstractPackageCollection
{
/** @var string */
@@ -21,6 +25,9 @@ class Themes extends AbstractPackageCollection
*/
public function __construct()
{
parent::__construct(Grav::instance()['themes']->all());
/** @var \Grav\Common\Themes $themes */
$themes = Grav::instance()['themes'];
parent::__construct($themes->all());
}
}

View File

@@ -13,21 +13,24 @@ use Grav\Common\Grav;
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
use Grav\Common\GPM\Response;
use \Doctrine\Common\Cache\FilesystemCache;
use RuntimeException;
/**
* Class AbstractPackageCollection
* @package Grav\Common\GPM\Remote
*/
class AbstractPackageCollection extends BaseCollection
{
/** @var string The cached data previously fetched */
protected $raw;
/** @var string */
protected $repository;
/** @var FilesystemCache */
protected $cache;
/** @var int The lifetime to store the entry in seconds */
private $lifetime = 86400;
/** @var string */
protected $repository;
/** @var FilesystemCache */
protected $cache;
/**
* AbstractPackageCollection constructor.
*
@@ -39,7 +42,7 @@ class AbstractPackageCollection extends BaseCollection
{
parent::__construct();
if ($repository === null) {
throw new \RuntimeException('A repository is required to indicate the origin of the remote collection');
throw new RuntimeException('A repository is required to indicate the origin of the remote collection');
}
$channel = Grav::instance()['config']->get('system.gpm.releases', 'stable');
@@ -51,7 +54,7 @@ class AbstractPackageCollection extends BaseCollection
$this->fetch($refresh, $callback);
foreach (json_decode($this->raw, true) as $slug => $data) {
// Temporarily fix for using multisites
// Temporarily fix for using multi-sites
if (isset($data['install_path'])) {
$path = preg_replace('~^user/~i', 'user://', $data['install_path']);
$data['install_path'] = Grav::instance()['locator']->findResource($path, false, true);

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