Compare commits

...

100 Commits

Author SHA1 Message Date
Andy Miller
2bc6848464 Merge branch 'release/1.6.4' 2019-04-15 14:48:10 -06:00
Andy Miller
abefbfc776 prepare for release 2019-04-15 14:48:01 -06:00
Andy Miller
ad173ca129 Improved redirect_default_route logic as well as Uri::toArray() to take into account root_path and extension 2019-04-15 12:44:48 -06:00
Andy Miller
d69ef0e39c Refactored rounded logic in Utils::parseSize() #2394 2019-04-15 11:00:11 -06:00
Matias Griese
436be17881 Fixed Flex simple storage not being properly initialized if used with caching 2019-04-15 14:26:55 +03:00
Andy Miller
9a5fa7e699 Extra forcing of first level arrays in taxonomy 2019-04-14 13:08:57 -06:00
Andy Miller
d502ff08c1 Attempt to resolve issue in form#332 2019-04-13 17:20:45 -06:00
Andy Miller
14eb1281f9 Fix to force all Page::taxonomy to be treated as strings #2446 2019-04-13 13:53:46 -06:00
Andy Miller
8fd7a5aebe Fixes #2445 pipeline excluded assets with cache on 2019-04-13 13:18:13 -06:00
Andy Miller
08423df547 code tidy 2019-04-13 12:27:19 -06:00
Andy Miller
40563ed2f8 Better Utils::normalizePath() logic #2216 2019-04-13 12:21:54 -06:00
Andy Miller
b639f09ca7 Merge branch 'release/1.6.3' 2019-04-12 19:48:13 -06:00
Andy Miller
dd134ad551 Merge tag '1.6.3' into develop
Release v1.6.3
2019-04-12 19:48:13 -06:00
Andy Miller
3d93d50cf0 prepare for release 2019-04-12 19:48:03 -06:00
Andy Miller
f1da7b6063 Fix for vUndefined errors when upgrading 2019-04-12 09:49:20 -06:00
Andy Miller
ef7b33f9b6 Fixed issue with Utils::normalizePath messing with external URLs #2216 2019-04-12 07:55:51 -06:00
Andy Miller
0f0e6ab1c8 Missed this one! #2442 2019-04-12 07:26:44 -06:00
Matias Griese
5362b312d1 Added Blueprint::addDynamicHandler() method to allow custom dynamic handlers, for example custom-options@: getCustomOptions 2019-04-12 12:23:31 +03:00
Andy Miller
ca4d6a398f Merge branch 'release/1.6.2' 2019-04-11 18:25:47 -06:00
Andy Miller
ed00d480f2 Merge tag '1.6.2' into develop
Release v1.6.2
2019-04-11 18:25:47 -06:00
Andy Miller
057bdd546b prepare for release 2019-04-11 18:25:38 -06:00
Andy Miller
7762f0c85e * Revert renaming of ClearCacheCommand to ensure CLI GPM upgrades go smoothly 2019-04-11 18:22:38 -06:00
Andy Miller
1e6c01ea65 Merge branch 'release/1.6.1' 2019-04-11 16:46:51 -06:00
Andy Miller
4b777f508b Merge tag '1.6.1' into develop
Release v1.6.1
2019-04-11 16:46:51 -06:00
Andy Miller
d15f125964 prepare for release 2019-04-11 16:46:43 -06:00
Andy Miller
32b435b7e6 Revert attempted fixes 2019-04-11 16:45:31 -06:00
Andy Miller
31d301911f Merge branch 'release/1.6.1' 2019-04-11 16:33:08 -06:00
Andy Miller
911eec5e68 Merge tag '1.6.1' into develop
Release v1.6.1
2019-04-11 16:33:08 -06:00
Andy Miller
2e4bb25e2e prepare for release 2019-04-11 16:32:59 -06:00
Andy Miller
d99c80eae9 More backwards compatibility 2019-04-11 16:29:55 -06:00
Andy Miller
0e03240d13 Merge branch 'release/1.6.1' 2019-04-11 15:25:47 -06:00
Andy Miller
02064117bc Merge tag '1.6.1' into develop
Release v1.6.1
2019-04-11 15:25:47 -06:00
Andy Miller
a9e0cc7159 prepare for release 2019-04-11 15:25:31 -06:00
Andy Miller
6b4332db72 not sure if needed, but might be same issue as DebuggerAssetsProcessor 2019-04-11 15:24:54 -06:00
Andy Miller
0f591953a0 update changelog 2019-04-11 15:19:55 -06:00
Andy Miller
0a5e78ccc6 Hopefully fix the DebuggerAssetsProcess error on upgrade of older versions 2019-04-11 15:19:43 -06:00
Andy Miller
fbf7c50a12 no longer needed 2019-04-11 15:06:19 -06:00
Andy Miller
09c1255239 fixes #2440 2019-04-11 15:05:50 -06:00
Andy Miller
75650ceba3 Better CSS for DebugBar 2019-04-11 15:04:00 -06:00
Andy Miller
e2a65004f3 Merge tag '1.6.0' into develop
Release v1.6.0
2019-04-11 12:38:38 -06:00
Andy Miller
d4e1bcc660 Merge branch 'release/1.6.0' 2019-04-11 12:38:37 -06:00
Andy Miller
b1b1670c77 prepare for release 2019-04-11 12:38:27 -06:00
Andy Miller
5a3674f6f6 set releases to stable 2019-04-11 12:36:14 -06:00
Andy Miller
e622e204bf update to include PHP 7.3 next time 2019-04-11 12:31:13 -06:00
Andy Miller
c378b06a90 Merge tag '1.6.0' into develop
Release v1.6.0
2019-04-11 12:23:38 -06:00
Andy Miller
c6ce6e6c32 Merge branch 'release/1.6.0' 2019-04-11 12:23:37 -06:00
Andy Miller
355f7ee748 prepare for release (take 2) 2019-04-11 12:23:27 -06:00
Andy Miller
c19fa22a26 update changelog 2019-04-11 12:22:51 -06:00
Matias Griese
5ba4c8ee5b Merge remote-tracking branch 'origin/1.6' into 1.6 2019-04-11 19:43:12 +03:00
Matias Griese
306df5837d Added FlexStorageInterface::hasKeys() 2019-04-11 19:43:01 +03:00
Andy Miller
66bba376db minor fixes and tests for Utils::url 2019-04-11 07:30:12 -06:00
Andy Miller
ec07d37623 Merge branch '1.6' of github.com:getgrav/grav into 1.6 2019-04-10 16:51:07 -06:00
Andy Miller
cab78d36f3 Missing file 2019-04-10 16:51:02 -06:00
Andy Miller
449682baea Allow reseting of Page::metadtta 2019-04-10 16:50:55 -06:00
Andy Miller
6eb11b9717 Fix for Utils::url and extra base_url 2019-04-10 16:50:30 -06:00
Matias Griese
5a1d138d08 Merge remote-tracking branch 'origin/1.6' into 1.6 2019-04-10 20:59:21 +03:00
Matias Griese
dfd75efbe5 Fixed join() in flex user 2019-04-10 20:59:13 +03:00
Andy Miller
76ed48fc7c escape spaces 2019-04-10 11:27:49 -06:00
Andy Miller
1043b9a189 Added languages.default_lang option to override the default lang 2019-04-09 17:27:35 -06:00
Matias Griese
e401c683f5 Grav 1.6: Fixed bad key in Flex Users if file storage is being used 2019-04-09 12:06:20 +03:00
Matias Griese
9d4fe331fa Fixed error in FlexUserIndex if there are users without an email address 2019-04-09 09:58:55 +03:00
Matias Griese
fbae3fd194 Fixed Flex erroring out with all numeric keys in storage (changelog) 2019-04-08 21:38:25 +03:00
Matias Griese
022f1ce758 Composer update 2019-04-08 21:37:41 +03:00
Andy Miller
b367c664c7 tidy up 2019-04-05 17:42:30 -06:00
Andy Miller
8512968726 this closes #2432 2019-04-05 17:38:45 -06:00
Matias Griese
2ffc110f03 Composer update 2019-04-05 10:21:51 +03:00
Matias Griese
36836c516f Fixed deleting last list item in the form 2019-04-05 10:19:55 +03:00
Andy Miller
1f288c25de typo! 2019-04-04 17:06:12 -06:00
Matias Griese
a4b62d48b2 Fixed minor bugs in Flex 2019-04-04 11:28:30 +03:00
Andy Miller
397f6902f3 Improved Utils::normalizePath() to support non-protocol URLs 2019-04-03 16:52:52 -06:00
Andy Miller
e97bceed56 changelog updated 2019-04-02 12:25:27 -06:00
Andy Miller
7efc0f418e Fixed cached images not being updated when source image is modified 2019-04-02 12:20:38 -06:00
Matias Griese
ac2a4e1c06 Try fixing cache issue with media 2019-04-02 20:28:43 +03:00
Matias Griese
ef43348020 Changelog update 2019-04-01 19:53:14 +03:00
Matias Griese
b6e68bb362 PSR-16, not 15 2019-04-01 19:51:28 +03:00
Matias Griese
8d9ceb5d99 MediaTrait: Use PSR-15 cache 2019-04-01 19:49:15 +03:00
Andy Miller
af47825b76 Merge branch '1.6' of github.com:getgrav/grav into 1.6
# Conflicts:
#	CHANGELOG.md
2019-03-29 11:31:32 -06:00
Andy Miller
a4c88697af set_time_limit(0) for Archiver class 2019-03-29 11:30:55 -06:00
Matias Griese
45fbfb098a Added FlexObjectInterface::getDefaultValue() and FormInterface::getDefaultValue() 2019-03-29 18:19:03 +02:00
Matias Griese
9172e442f2 Blueprint: cache defaults to the object to speed multiple getDefaults() calls up 2019-03-29 18:14:26 +02:00
Matias Griese
3b91f9af8c FlexObjects: Remove null values during save 2019-03-29 13:01:55 +02:00
Matias Griese
d5e9cc4bfe Added RenderInterface 2019-03-28 12:52:34 +02:00
Matias Griese
bb719c5d53 Improve deprecated error logic to better track twig/yaml/markdown issues 2019-03-27 12:06:48 +02:00
Matias Griese
05a6775b08 Minor tweak 2019-03-26 14:07:58 +02:00
Matias Griese
1accbb8edc DebugBar: Fixed some instances where twig template wasn't properly assigned 2019-03-26 14:02:04 +02:00
Matias Griese
a04a7f5714 DebugBar: Resolve twig templates in deprecated backtraces in order to help locating Twig issues 2019-03-26 12:56:53 +02:00
Matias Griese
f31f7f7499 Merge remote-tracking branch 'origin/1.6' into 1.6 2019-03-26 07:51:04 +02:00
Matias Griese
354d6f307c Added Flex[Name]::getFlexType() to all flex classes to have the interface for all Flex classes 2019-03-26 07:50:54 +02:00
Andy Miller
90aa4083ca improved order of page types - fixes #2337 2019-03-25 17:29:35 -07:00
Matias Griese
5fbd252db9 Merge branch 'develop' of github.com:getgrav/grav into 1.6
# Conflicts:
#	CHANGELOG.md
#	composer.json
#	composer.lock
#	system/defines.php
2019-03-25 18:56:09 +02:00
Matias Griese
04d3237a89 Changelog update 2019-03-25 18:53:04 +02:00
Antoine Goutenoir
55aaaeed47 Add the page to the onMarkdownInitialized event. (#2418)
This will allow plugins hooking this event to mergeConfig #2412
2019-03-25 18:51:06 +02:00
Matias Griese
84d995335e Fixed Undefined method closure::fields() when getting avatar for user, thanks @Romarain [#2422] 2019-03-22 18:01:22 +02:00
Djamil Legato
f7d3299ebb Added Awesome Grav link to the Exploring More 2019-03-21 19:06:12 -07:00
Andy Miller
e762c3add9 Merge branch 'release/1.5.10' 2019-03-21 14:16:21 -06:00
Andy Miller
df6bb065d3 Merge tag '1.5.10' into develop
Release v1.5.10
2019-03-21 14:16:21 -06:00
Andy Miller
2fed02affa Prepare for release 2019-03-21 14:16:10 -06:00
Matias Griese
a739ed6825 Improved Exceptions middleware 2019-03-21 12:17:48 +02:00
Matias Griese
90c708db2b Added Content-Type: application/json body support for PSR-7 ServerRequest 2019-03-21 12:01:05 +02:00
Andy Miller
fcbd819f48 Added deferred twig extension 2019-03-20 16:31:19 -06:00
63 changed files with 1519 additions and 666 deletions

View File

@@ -2,6 +2,7 @@ language: php
php:
- '7.1'
- '7.2'
- '7.3'
branches:
only:
- develop

View File

@@ -1,105 +1,62 @@
# v1.6.0-rc.4
## 03/20/2019
# v1.6.4
## 04/15/2019
1. [](#new)
* Added new `onPageContent()` event for every call to `Page::content()`
* Added phpstan: PHP Static Analysis Tool [#2393](https://github.com/getgrav/grav/pull/2393)
* Added `composer test-plugins` to test plugin issues with the current version of Grav
* Grav 1.6: Renamed `$grav['users']` service to `$grav['accounts']`
* Added `Flex::getObjects()` and `Flex::getMixedCollection()` methods for co-mingled collections
* Added support to use single Flex key parameter in `Flex::getObject()` method
* Added `FlexObjectInterface::search()` and `FlexCollectionInterface::search()` methods
* Override `system.media.upload_limit` with PHP's `post_max_size` or `upload_max_filesize`
* Class `Grav\Common\Page\Medium\AbstractMedia` now use array traits instead of extending `Grav\Common\Getters`
1. [](#improved)
* Renamed `Grav\Framework\File\Formatter\FormatterInterface` to `Grav\Framework\File\Interfaces\FileFormatterInterface`
* Improved `File::save()` to use a temporary file if file isn't locked
* Improved `|t` filter to better support admin `|tu` style filter if in admin
* Update all classes to rely on `PageInterface` instead of `Page` class
* Better error checking in `bin/plugin` for existence and enabled
* Removed `media.upload_limit` references
* Twig `nicenumber`: do not use 0 + string casting hack
* Converted Twig tags to use namespaced Twig classes
* Site shows error on page rather than hard-crash when page has invalid frontmatter [#2343](https://github.com/getgrav/grav/issues/2343)
* Grav 1.6: Greatly improved Flex interfaces
1. [](#bugfix)
* Grav 1.6: Fixed `FlexUser` loosing ACL information
* Grav 1.6: Fixed `FlexUser::find()` breaking when nothing is found
* Grav 1.6: Fixed `FlexObject::update()` removing fields on save
* Fixed `mkdir(...)` race condition
* Fixed `Obtaining write lock failed on file...`
* Fixed potential undefined property in `onPageNotFound` event handling
* Grav 1.6: Fixed settion caching in `FlexIndex`
* Fixed some potential issues/bugs found by phpstan
* Fixed regression in GPM packages casted to Array (ref, getgrav/grav-plugin-admin@e3fc4ce)
* Fixed session_start(): Setting option 'session.name' failed [#2408](https://github.com/getgrav/grav/issues/2408)
* Grav 1.6: Fixed file saving when temporary file cannot be created to the current folder / stream
* Grav 1.6: Fixed `File::save()` silently ignoring failures with read only streams
* Improved `redirect_default_route` logic as well as `Uri::toArray()` to take into account `root_path` and `extension`
* Rework logic to pull out excluded files from pipeline more reliably [#2445](https://github.com/getgrav/grav/issues/2445)
* Better logic in `Utils::normalizePath` to handle externals properly [#2216](https://github.com/getgrav/grav/issues/2216)
* Fixed to force all `Page::taxonomy` to be treated as strings [#2446](https://github.com/getgrav/grav/issues/2446)
* Fixed issue with `Grav['user']` not being available [form#332](https://github.com/getgrav/grav-plugin-form/issues/332)
* Updated rounding logic for `Utils::parseSize()` [#2394](https://github.com/getgrav/grav/issues/2394)
* Fixed Flex simple storage not being properly initialized if used with caching
# v1.6.0-rc.3
## 02/18/2019
# v1.6.3
## 04/12/2019
1. [](#new)
* Implemented `Grav\Framework\Psr7` classes as `Nyholm/psr7` decorators
* Grav 1.6: Renamed `blueprints/user/accounts.yaml` to `blueprints/user/users.yaml`
* Grav 1.6: Moved FlexUser index into `user-data://flex/indexes/users.yaml` [#2378](https://github.com/getgrav/grav/issues/2378)
* Added a new `cache-clear` scheduled job to go along with `cache-purge`
1. [](#improved)
* More code cleanup
* Grav 1.6: Fixed `FlexUser` caching
* Added back missing `page.types` field in system content configuration [admin#1612](https://github.com/getgrav/grav-plugin-admin/issues/1612)
* Console commands: add method for invalidating cache
* Updated languages
* Added `Blueprint::addDynamicHandler()` method to allow custom dynamic handlers, for example `custom-options@: getCustomOptions`
1. [](#bugfix)
* Fixed validation for select field type with selectize
* Fixed validation for boolean toggles
* Grav 1.6: Fixed `Flex[class]::getType()` to return the same value in every class
* Grav 1.6: Fixed `FlexIndex` keys being lost when `FlexCollection` gets loaded
* Grav 1.6: Fixed missing `form_nonce` for JS when using `FlexForm`
* Grav 1.6: Fixed slow loading of `FlexUser` objects on `$grav['users']->find()` and `load()` calls
* Missed a `CacheCommand` reference in `bin/grav` [#2442](https://github.com/getgrav/grav/issues/2442)
* Fixed issue with `Utils::normalizePath` messing with external URLs [#2216](https://github.com/getgrav/grav/issues/2216)
* Fix for `vUndefined` versions when upgrading
# v1.6.0-rc.2
## 02/07/2019
# v1.6.2
## 04/11/2019
1. [](#new)
* New experimental **FlexObjects** powered `Users` for increased performance and capability (**disabled** by default)
* New `$grav['users']` service to allow custom user classes implementing `UserInterface`
* Grav 1.6: Added index file support for Flex Objects
* Added `LogViewer` helper class and CLI command: `bin/grav logviewer`
1. [](#improved)
* Grav 1.6: Improved error detection for broken Flex Objects
* Removed `apc` and `xcache` support, made `apc` alias of `apcu`
* Support admin and regular translations via the `|t` twig filter and `t()` twig function
* Improved Grav Core installer/updater to run installer script
* Updated vendor libraries including Symfony `4.2.3`
* Renamed old `User` class to `Grav\Common\User\DataUser\User` with multiple improvements and small fixes
* `User` class now acts as a compatibility layer to older versions of Grav
* Deprecated `new User()`, `User::load()`, `User::find()` and `User::delete()` in favor of `$grav['users']` service
* `Media` constructor has now support to not to initialize the media objects
* Cleanly handle session corruption due to changing Flex object types
* Grav 1.6: Renamed `FlexAuthorizeInterface::authorize()` to `isAuthorized()`
1. [](#bugfix)
* Fixed non-namespaced exceptions in scheduler
* Fixed trailing slash redirect in multlang environment [#2350](https://github.com/getgrav/grav/issues/2350)
* Grav 1.6: Fixed Flex from indexing hidden folders/files as objects
* Grav 1.6 regression: `$session->getFlashObject('files-upload')` did not work with Form 3.0
* Revert renaming of `ClearCacheCommand` to ensure CLI GPM upgrades go smoothly
# v1.6.0-rc.1
## 01/30/2019
# v1.6.1
## 04/11/2019
1. [](#improved)
* Improved `$page->forms()` call, added `$page->addForms()`
* Grav 1.6: Made `FormFlashFile` more robust against deleted files
* Updated languages from crowdin
* Grav 1.6: Fixed a bug in `FormFlashFile::moveTo()` not deleting the old file
* Grav 1.6: Fixed `FlexMediaTrait::getMedia()` trying to include uploaded but already moved media
* Fixed `ImageMedium` constructor warning when file does not exist
* Grav 1.6: Fixed bad host header in PSR-7 (if using `php -S localhost:8000 system/router.php`)
# v1.6.0-beta.8
## 01/25/2019
* Improved CSS for the bottom filter bar of DebugBar
1. [](#bugfix)
* Fixed issue with `@import` not being added to top of pipelined css [#2440](https://github.com/getgrav/grav/issues/2440)
# v1.6.0
## 04/11/2019
1. [](#new)
* Set minimum requirements to [PHP 7.1.3](https://getgrav.org/blog/raising-php-requirements-2018)
* New `Scheduler` functionality for periodic jobs
* New `Backup` functionality with multiple backup profiles and scheduler integration
* Refactored `Assets Manager` to be more powerful and flexible
* Updated Doctrine Collections to 1.6
* Updated Doctrine Cache to 1.8
* Updated Symfony Components to 4.2
* Added new Cache purge functionality old cache manually via CLI/Admin as well as scheduler integration
* Added new `{% throw 404 'Not Found' %}` twig tag (with custom code/message)
* Added `Grav\Framework\File` classes for handling YAML, Markdown, JSON, INI and PHP serialized files
* Added `Grav\Framework\Collection\AbstractIndexCollection` class
* Added `Grav\Framework\Object\ObjectIndex` class
* Added `Grav\Framework\Flex` classes
* Added support for hiding form fields in blueprints by using dynamic property like `security@: admin.foobar`, `scope@: object` or `scope-ignore@: object` to any field
* New experimental **FlexObjects** powered `Users` for increased performance and capability (**disabled** by default)
* Added PSR-7 and PSR-15 classes
* Added `Grav\Framework\DI\Container` class
* Added `Grav\Framework\RequestHandler\RequestHandler` class
* Added `Page::httpResponseCode()` and `Page::httpHeaders()` methods
* Added `Grav\Framework\Form\Interfaces\FormInterface`
* Added `Grav\Framework\Form\Interfaces\FormFactoryInterface`
* Added `Grav\Framework\Form\FormTrait`
@@ -111,25 +68,8 @@
* Added form preview support for `FlexObject`, including a way to render newly uploaded files before saving them
* Added `FlexObject::getChanges()` to determine what fields change during an update
* Added `arrayDiffMultidimensional`, `arrayIsAssociative`, `arrayCombine` Util functions
1. [](#improved)
* Grav 1.6: Added method argument `Data::filter($missingValuesAsNull)`, defaulting to `false`
* Improved `Grav\Common\User` class; added `$user->update()` method
* Added trim support for text input fields `validate: trim: true`
1. [](#bugfix)
* Grav 1.6: Fixed environment getting port added [#2284](https://github.com/getgrav/grav/issues/2284)
* Grav 1.6: Fixed `FlexForm::updateObject()` to update array values when they are empty in the form
* Fixed some issues related to Medium objects losing query string attributes
* Broke out Medium timestamp so it's not cleared on `reset()`s
* Fixed issue with `redirect_trailing_slash` losing query string [#2269](https://github.com/getgrav/grav/issues/2269)
* Fixed failed login if user attempts to log in with upper case non-english letters
* Removed extra authenticated/authorized fields when saving existing user from a form
* Fixed `Grav\Framework\Route::__toString()` returning relative URL, not relative route
# v1.6.0-beta.7
## 12/14/2018
1. [](#new)
* Updated Symfony Components to 4.2
* New `$grav['users']` service to allow custom user classes implementing `UserInterface`
* Added `LogViewer` helper class and CLI command: `bin/grav logviewer`
* Added `select()` and `unselect()` methods to `CollectionInterface` and its base classes
* Added `orderBy()` and `limit()` methods to `ObjectCollectionInterface` and its base classes
* Added `user-data://` which is a writable stream (`user://data` is not and should be avoided)
@@ -140,46 +80,64 @@
* Added `Grav\Framework\Filesystem\Filesystem` class with methods to manipulate stream URLs
* Added new `$grav['filesystem']` service using an instance of the new `Filesystem` object
* Added `{% render object layout: 'default' with { variable: true } %}` for Flex objects and collections
* Grav 1.6: Flex: Added support for custom object index classes (API compatibility break)
1. [](#improved)
* Improved `Grav\Framework\File\Formatter` classes to have abstract parent class and some useful methods
* Grav 1.6: Improved Flex storage classes
* Grav 1.6: Improved `Grav\Framework\File` classes to use better type hints and the new `Filesystem` class
1. [](#bugfix)
* Fixed handling of `append_url_extension` inside of `Page::templateFormat()` [#2264](https://github.com/getgrav/grav/issues/2264)
* Fixed a broken language string [#2261](https://github.com/getgrav/grav/issues/2261)
* Fixed clearing cache having no effect on Doctrine cache
* Fixed `Medium::relativePath()` for streams
* Fixed `Object` serialization breaking if overriding `jsonSerialize()` method
* Grav 1.6: Fixed `FlexObject::update()` call with partial object update
* Fixed `YamlFormatter::decode()` when calling `init_set()` with integer
* Fixed session throwing error in CLI if initialized
# v1.6.0-beta.6
## 11/12/2018
1. [](#new)
* Added `$grav->setup()` to simplify CLI and custom access points
* Grav 1.6: Added `CsvFormatter` and `CsvFile` classes
* Added `CsvFormatter` and `CsvFile` classes
* Added new system config option to `pages.hide_empty_folders` if a folder has no valid `.md` file available. Default behavior is `false` for compatibility.
* Added new system config option for `languages.pages_fallback_only` forcing only 'fallback' to find page content through supported languages, default behavior is to display any language found if active language is missing
* Added `Utils::arrayFlattenDotNotation()` and `Utils::arrayUnflattenDotNotation()` helper methods
1. [](#improved)
* Add the page to onMarkdownInitialized event [#2412](https://github.com/getgrav/grav/issues/2412)
* Doctrine filecache is now namespaced with prefix to support purging
* Register all page types into `blueprint://pages` stream
* Removed `apc` and `xcache` support, made `apc` alias of `apcu`
* Support admin and regular translations via the `|t` twig filter and `t()` twig function
* Improved Grav Core installer/updater to run installer script
* Updated vendor libraries including Symfony `4.2.3`
* Renamed old `User` class to `Grav\Common\User\DataUser\User` with multiple improvements and small fixes
* `User` class now acts as a compatibility layer to older versions of Grav
* Deprecated `new User()`, `User::load()`, `User::find()` and `User::delete()` in favor of `$grav['users']` service
* `Media` constructor has now support to not to initialize the media objects
* Cleanly handle session corruption due to changing Flex object types
* Added `FlexObjectInterface::getDefaultValue()` and `FormInterface::getDefaultValue()`
* Added new `onPageContent()` event for every call to `Page::content()`
* Added phpstan: PHP Static Analysis Tool [#2393](https://github.com/getgrav/grav/pull/2393)
* Added `composer test-plugins` to test plugin issues with the current version of Grav
* Added `Flex::getObjects()` and `Flex::getMixedCollection()` methods for co-mingled collections
* Added support to use single Flex key parameter in `Flex::getObject()` method
* Added `FlexObjectInterface::search()` and `FlexCollectionInterface::search()` methods
* Override `system.media.upload_limit` with PHP's `post_max_size` or `upload_max_filesize`
* Class `Grav\Common\Page\Medium\AbstractMedia` now use array traits instead of extending `Grav\Common\Getters`
* Implemented `Grav\Framework\Psr7` classes as `Nyholm/psr7` decorators
* Added a new `cache-clear` scheduled job to go along with `cache-purge`
* Renamed `Grav\Framework\File\Formatter\FormatterInterface` to `Grav\Framework\File\Interfaces\FileFormatterInterface`
* Improved `File::save()` to use a temporary file if file isn't locked
* Improved `|t` filter to better support admin `|tu` style filter if in admin
* Update all classes to rely on `PageInterface` instead of `Page` class
* Better error checking in `bin/plugin` for existence and enabled
* Removed `media.upload_limit` references
* Twig `nicenumber`: do not use 0 + string casting hack
* Converted Twig tags to use namespaced Twig classes
* Site shows error on page rather than hard-crash when page has invalid frontmatter [#2343](https://github.com/getgrav/grav/issues/2343)
* Added `languages.default_lang` option to override the default lang (usually first supported language)
* Added `Content-Type: application/json` body support for PSR-7 `ServerRequest`
* Remove PHP time limit in `ZipArchive`
* DebugBar: Resolve twig templates in deprecated backtraces in order to help locating Twig issues
* Added `$grav['cache']->getSimpleCache()` method for getting PSR-16 compatible cache
* MediaTrait: Use PSR-16 cache
* Improved `Utils::normalizePath()` to support non-protocol URLs
* Added ability to reset `Page::metadata` to allow rebuilding from automatically generated values
* Added back missing `page.types` field in system content configuration [admin#1612](https://github.com/getgrav/grav-plugin-admin/issues/1612)
* Console commands: add method for invalidating cache
* Updated languages
* Improved `$page->forms()` call, added `$page->addForms()`
* Updated languages from crowdin
* Fixed `ImageMedium` constructor warning when file does not exist
* Improved `Grav\Common\User` class; added `$user->update()` method
* Added trim support for text input fields `validate: trim: true`
* Improved `Grav\Framework\File\Formatter` classes to have abstract parent class and some useful methods
* Support negotiated content types set via the Request `Accept:` header
* Support negotiated language types set via the Request `Accept-Language:` header
* Cleaned up and sorted the Service `idMap`
* Grav 1.6: Allow custom Flex form views
1. [](#bugfix)
* Fixed `Uri::hasStandardPort()` to support reverse proxy configurations [#1786](https://github.com/getgrav/grav/issues/1786)
* Use `append_url_extension` from page header to set template format if set [#2604](https://github.com/getgrav/grav/pull/2064)
* Fixed some bugs in Grav environment selection logic
# v1.6.0-beta.5
## 11/05/2018
1. [](#new)
* Added PSR-7 and PSR-15 classes
* Added `Grav\Framework\DI\Container` class
* Added `Grav\Framework\RequestHandler\RequestHandler` class
* Added `Page::httpResponseCode()` and `Page::httpHeaders()` methods
1. [](#improved)
* Updated `Grav` container object to implement PSR-11 `ContainerInterface`
* Updated Grav `Processor` classes to implement PSR-15 `MiddlewareInterface`
* Make `Data` class to extend `JsonSerializable`
@@ -188,67 +146,55 @@
* Set session name based on `security.salt` rather than `GRAV_ROOT` [#2242](https://github.com/getgrav/grav/issues/2242)
* Added option to configure list of `xss_invalid_protocols` in `Security` config [#2250](https://github.com/getgrav/grav/issues/2250)
* Smarter `security.salt` checking now we use `security.yaml` for other options
* Grav 1.6: Merged Grav 1.5.4 fixes in
# v1.6.0-beta.4
## 10/24/2018
1. [](#new)
* Added new system config option to `pages.hide_empty_folders` if a folder has no valid `.md` file available. Default behavior is `false` for compatibility.
* Added new system config option for `languages.pages_fallback_only` forcing only 'fallback' to find page content through supported languages, default behavior is to display any language found if active language is missing
* Added `Utils::arrayFlattenDotNotation()` and `Utils::arrayUnflattenDotNotation()` helper methods
1. [](#improved)
* Added apcu autoloader optimization
* Additional helper methods in `Language`, `Languages`, and `LanguageCodes` classes
1. [](#bugfix)
* Use login provider User avatar if set
* Fixed `Folder::doDelete($folder, false)` removing symlink when it should not
# v1.6.0-beta.3
## 10/15/2018
1. [](#improved)
* Call `onFatalException` event also on internal PHP errors
* Built-in PHP Webserver: log requests before handling them
1. [](#bugfix)
* Grav 1.6: Scheduler Fallback for never runs and Windows support [#2202](https://github.com/getgrav/grav/pull/2202)
# v1.6.0-beta.2
## 10/09/2018
1. [](#new)
* Grav 1.6: Added Flex support for custom media tasks
1. [](#improved)
* Built-in PHP Webserver: log requests before handling them
* Added support for syslog and syslog facility logging (default: 'file')
* Improved usability of `System` configuration blueprint with side-tabs
1. [](#bugfix)
1. [](#bugfix)
* Fixed issue with `Truncator::truncateWords` and `Truncator::truncateLetters` when string not wrapped in tags [#2432](https://github.com/getgrav/grav/issues/2432)
* Fixed `Undefined method closure::fields()` when getting avatar for user, thanks @Romarain [#2422](https://github.com/getgrav/grav/issues/2422)
* Fixed cached images not being updated when source image is modified
* Fixed deleting last list item in the form
* Fixed issue with `Utils::url()` method would append extra `base_url` if URL already included it
* Fixed `mkdir(...)` race condition
* Fixed `Obtaining write lock failed on file...`
* Fixed potential undefined property in `onPageNotFound` event handling
* Fixed some potential issues/bugs found by phpstan
* Fixed regression in GPM packages casted to Array (ref, getgrav/grav-plugin-admin@e3fc4ce)
* Fixed session_start(): Setting option 'session.name' failed [#2408](https://github.com/getgrav/grav/issues/2408)
* Fixed validation for select field type with selectize
* Fixed validation for boolean toggles
* Fixed non-namespaced exceptions in scheduler
* Fixed trailing slash redirect in multlang environment [#2350](https://github.com/getgrav/grav/issues/2350)
* Fixed some issues related to Medium objects losing query string attributes
* Broke out Medium timestamp so it's not cleared on `reset()`s
* Fixed issue with `redirect_trailing_slash` losing query string [#2269](https://github.com/getgrav/grav/issues/2269)
* Fixed failed login if user attempts to log in with upper case non-english letters
* Removed extra authenticated/authorized fields when saving existing user from a form
* Fixed `Grav\Framework\Route::__toString()` returning relative URL, not relative route
* Fixed handling of `append_url_extension` inside of `Page::templateFormat()` [#2264](https://github.com/getgrav/grav/issues/2264)
* Fixed a broken language string [#2261](https://github.com/getgrav/grav/issues/2261)
* Fixed clearing cache having no effect on Doctrine cache
* Fixed `Medium::relativePath()` for streams
* Fixed `Object` serialization breaking if overriding `jsonSerialize()` method
* Fixed `YamlFormatter::decode()` when calling `init_set()` with integer
* Fixed session throwing error in CLI if initialized
* Fixed `Uri::hasStandardPort()` to support reverse proxy configurations [#1786](https://github.com/getgrav/grav/issues/1786)
* Use `append_url_extension` from page header to set template format if set [#2604](https://github.com/getgrav/grav/pull/2064)
* Fixed some bugs in Grav environment selection logic
* Use login provider User avatar if set
* Fixed `Folder::doDelete($folder, false)` removing symlink when it should not
* Fixed asset manager to not add empty assets when they don't exist in the filesystem
* Update `script` and `style` Twig tags to use the new `Assets` classes
* Fixed asset pipeline to rewrite remote URLs as well as local [#2216](https://github.com/getgrav/grav/issues/2216)
* Grav 1.6 regression: Fixed asset manager methods with default legacy attributes
# v1.6.0-beta.1
## 10/01/2018
# v1.5.10
## 03/21/2019
1. [](#new)
* Set minimum requirements to [PHP 7.1.3](https://getgrav.org/blog/raising-php-requirements-2018)
* New `Scheduler` functionality for periodic jobs
* New `Backup` functionality with multiple backup profiles and scheduler integration
* Refactored `Assets Manager` to be more powerful and flexible
* Updated Doctrine Collections to 1.5
* Updated Doctrine Cache to 1.8
* Updated Symfony Components to 4.1
* Added a new Deferred Twig extension to allow adding content to Twig blocks after render
* Added new Cache purge functionality old cache manually via CLI/Admin as well as scheduler integration
* Added new `{% throw 404 'Not Found' %}` twig tag (with custom code/message)
* Added `Grav\Framework\File` classes for handling YAML, Markdown, JSON, INI and PHP serialized files
* Added `Grav\Framework\Collection\AbstractIndexCollection` class
* Added `Grav\Framework\Object\ObjectIndex` class
* Added `Grav\Framework\Flex` classes
* Added support for hiding form fields in blueprints by using dynamic property like `security@: admin.foobar`, `scope@: object` or `scope-ignore@: object` to any field
1. [](#improved)
* Doctrine filecache is now namespaced with prefix to support purging
* Register all page types into `blueprint://pages` stream
* Added new `deferred` Twig extension
# v1.5.9
## 03/20/2019

View File

@@ -106,6 +106,7 @@ If you discover a possible security issue related to Grav or one of its plugins,
* Dive into more [advanced](https://learn.getgrav.org/advanced) functions
* Learn about the [Grav CLI](https://learn.getgrav.org/cli-console/grav-cli)
* Review examples in the [Grav Cookbook](https://learn.getgrav.org/cookbook)
* More [Awesome Grav Stuff](https://github.com/getgrav/awesome-grav)
# Backers
Support Grav with a monthly donation to help us continue development. [[Become a backer](https://opencollective.com/grav#backer)]

View File

@@ -40,7 +40,7 @@ $app->addCommands(array(
new \Grav\Console\Cli\ComposerCommand(),
new \Grav\Console\Cli\SandboxCommand(),
new \Grav\Console\Cli\CleanCommand(),
new \Grav\Console\Cli\CacheCommand(),
new \Grav\Console\Cli\ClearCacheCommand(),
new \Grav\Console\Cli\BackupCommand(),
new \Grav\Console\Cli\NewProjectCommand(),
new \Grav\Console\Cli\SchedulerCommand(),

View File

@@ -12,6 +12,7 @@
"ext-openssl": "*",
"ext-curl": "*",
"ext-zip": "*",
"ext-dom": "*",
"symfony/polyfill-iconv": "^1.9",
"symfony/polyfill-php72": "^1.9",
"symfony/polyfill-php73": "^1.9",

410
composer.lock generated
View File

@@ -183,34 +183,36 @@
},
{
"name": "doctrine/collections",
"version": "v1.5.0",
"version": "v1.6.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/collections.git",
"reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf"
"reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf",
"reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf",
"url": "https://api.github.com/repos/doctrine/collections/zipball/d2ae4ef05e25197343b6a39bae1d3c427a2f6956",
"reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956",
"shasum": ""
},
"require": {
"php": "^7.1"
"php": "^7.1.3"
},
"require-dev": {
"doctrine/coding-standard": "~0.1@dev",
"phpunit/phpunit": "^5.7"
"doctrine/coding-standard": "^6.0",
"phpstan/phpstan-shim": "^0.9.2",
"phpunit/phpunit": "^7.0",
"vimeo/psalm": "^3.2.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
"dev-master": "1.6.x-dev"
}
},
"autoload": {
"psr-0": {
"Doctrine\\Common\\Collections\\": "lib/"
"psr-4": {
"Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -239,14 +241,15 @@
"email": "schmittjoh@gmail.com"
}
],
"description": "Collections Abstraction library",
"homepage": "http://www.doctrine-project.org",
"description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.",
"homepage": "https://www.doctrine-project.org/projects/collections.html",
"keywords": [
"array",
"collections",
"iterator"
"iterators",
"php"
],
"time": "2017-07-22T10:37:32+00:00"
"time": "2019-03-25T19:03:48+00:00"
},
{
"name": "donatj/phpuseragentparser",
@@ -1771,16 +1774,16 @@
},
{
"name": "symfony/console",
"version": "v4.2.4",
"version": "v4.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "9dc2299a016497f9ee620be94524e6c0af0280a9"
"reference": "24206aff3efe6962593297e57ef697ebb220e384"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/9dc2299a016497f9ee620be94524e6c0af0280a9",
"reference": "9dc2299a016497f9ee620be94524e6c0af0280a9",
"url": "https://api.github.com/repos/symfony/console/zipball/24206aff3efe6962593297e57ef697ebb220e384",
"reference": "24206aff3efe6962593297e57ef697ebb220e384",
"shasum": ""
},
"require": {
@@ -1839,7 +1842,7 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2019-02-23T15:17:42+00:00"
"time": "2019-04-01T07:32:59+00:00"
},
{
"name": "symfony/contracts",
@@ -1911,16 +1914,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v4.2.4",
"version": "v4.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb"
"reference": "ca5af306fbc37f3cf597e91bc9cfa0c7d3f33544"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3354d2e6af986dd71f68b4e5cf4a933ab58697fb",
"reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ca5af306fbc37f3cf597e91bc9cfa0c7d3f33544",
"reference": "ca5af306fbc37f3cf597e91bc9cfa0c7d3f33544",
"shasum": ""
},
"require": {
@@ -1971,20 +1974,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2019-02-23T15:17:42+00:00"
"time": "2019-03-30T15:58:42+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.10.0",
"version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
"reference": "82ebae02209c21113908c229e9883c419720738a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
"reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
"reference": "82ebae02209c21113908c229e9883c419720738a",
"shasum": ""
},
"require": {
@@ -1996,7 +1999,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
"dev-master": "1.11-dev"
}
},
"autoload": {
@@ -2029,20 +2032,20 @@
"polyfill",
"portable"
],
"time": "2018-08-06T14:22:27+00:00"
"time": "2019-02-06T07:57:58+00:00"
},
{
"name": "symfony/polyfill-iconv",
"version": "v1.10.0",
"version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-iconv.git",
"reference": "97001cfc283484c9691769f51cdf25259037eba2"
"reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/97001cfc283484c9691769f51cdf25259037eba2",
"reference": "97001cfc283484c9691769f51cdf25259037eba2",
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f037ea22acfaee983e271dd9c3b8bb4150bd8ad7",
"reference": "f037ea22acfaee983e271dd9c3b8bb4150bd8ad7",
"shasum": ""
},
"require": {
@@ -2054,7 +2057,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
"dev-master": "1.11-dev"
}
},
"autoload": {
@@ -2088,20 +2091,20 @@
"portable",
"shim"
],
"time": "2018-09-21T06:26:08+00:00"
"time": "2019-02-06T07:57:58+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.10.0",
"version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
"reference": "fe5e94c604826c35a32fa832f35bd036b6799609"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
"reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609",
"reference": "fe5e94c604826c35a32fa832f35bd036b6799609",
"shasum": ""
},
"require": {
@@ -2113,7 +2116,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
"dev-master": "1.11-dev"
}
},
"autoload": {
@@ -2147,20 +2150,20 @@
"portable",
"shim"
],
"time": "2018-09-21T13:07:52+00:00"
"time": "2019-02-06T07:57:58+00:00"
},
{
"name": "symfony/polyfill-php72",
"version": "v1.10.0",
"version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631"
"reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631",
"reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c",
"reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c",
"shasum": ""
},
"require": {
@@ -2169,7 +2172,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
"dev-master": "1.11-dev"
}
},
"autoload": {
@@ -2202,20 +2205,20 @@
"portable",
"shim"
],
"time": "2018-09-21T13:07:52+00:00"
"time": "2019-02-06T07:57:58+00:00"
},
{
"name": "symfony/polyfill-php73",
"version": "v1.10.0",
"version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
"reference": "47ad352296d61aae366f075b8609f4dcc28853ef"
"reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/47ad352296d61aae366f075b8609f4dcc28853ef",
"reference": "47ad352296d61aae366f075b8609f4dcc28853ef",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/d1fb4abcc0c47be136208ad9d68bf59f1ee17abd",
"reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd",
"shasum": ""
},
"require": {
@@ -2224,7 +2227,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
"dev-master": "1.11-dev"
}
},
"autoload": {
@@ -2233,6 +2236,9 @@
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -2257,20 +2263,20 @@
"portable",
"shim"
],
"time": "2018-09-25T06:33:47+00:00"
"time": "2019-02-06T07:57:58+00:00"
},
{
"name": "symfony/process",
"version": "v4.2.4",
"version": "v4.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad"
"reference": "1e6cbb41dadcaf29e0db034d6ad0d039a9df06e6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/6c05edb11fbeff9e2b324b4270ecb17911a8b7ad",
"reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad",
"url": "https://api.github.com/repos/symfony/process/zipball/1e6cbb41dadcaf29e0db034d6ad0d039a9df06e6",
"reference": "1e6cbb41dadcaf29e0db034d6ad0d039a9df06e6",
"shasum": ""
},
"require": {
@@ -2306,11 +2312,11 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2019-01-24T22:05:03+00:00"
"time": "2019-03-10T20:07:02+00:00"
},
{
"name": "symfony/var-dumper",
"version": "v4.2.4",
"version": "v4.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
@@ -2386,16 +2392,16 @@
},
{
"name": "symfony/yaml",
"version": "v4.2.4",
"version": "v4.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "761fa560a937fd7686e5274ff89dcfa87a5047df"
"reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/761fa560a937fd7686e5274ff89dcfa87a5047df",
"reference": "761fa560a937fd7686e5274ff89dcfa87a5047df",
"url": "https://api.github.com/repos/symfony/yaml/zipball/6712daf03ee25b53abb14e7e8e0ede1a770efdb1",
"reference": "6712daf03ee25b53abb14e7e8e0ede1a770efdb1",
"shasum": ""
},
"require": {
@@ -2441,20 +2447,20 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2019-02-23T15:17:42+00:00"
"time": "2019-03-30T15:58:42+00:00"
},
{
"name": "twig/twig",
"version": "v1.38.2",
"version": "v1.38.4",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "874adbd9222f928f6998732b25b01b41dff15b0c"
"reference": "7732e9e7017d751313811bd118de61302e9c8b35"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/874adbd9222f928f6998732b25b01b41dff15b0c",
"reference": "874adbd9222f928f6998732b25b01b41dff15b0c",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/7732e9e7017d751313811bd118de61302e9c8b35",
"reference": "7732e9e7017d751313811bd118de61302e9c8b35",
"shasum": ""
},
"require": {
@@ -2507,7 +2513,7 @@
"keywords": [
"templating"
],
"time": "2019-03-12T18:45:24+00:00"
"time": "2019-03-23T14:27:19+00:00"
},
{
"name": "willdurand/negotiation",
@@ -2624,16 +2630,16 @@
},
{
"name": "codeception/codeception",
"version": "2.5.4",
"version": "2.5.5",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
"reference": "a2ecfe2f3ad36cc29904d2d566b0d7280854e6c9"
"reference": "547a64cb31edcf1902b296c511f5ca74101bcb4c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/a2ecfe2f3ad36cc29904d2d566b0d7280854e6c9",
"reference": "a2ecfe2f3ad36cc29904d2d566b0d7280854e6c9",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/547a64cb31edcf1902b296c511f5ca74101bcb4c",
"reference": "547a64cb31edcf1902b296c511f5ca74101bcb4c",
"shasum": ""
},
"require": {
@@ -2712,7 +2718,7 @@
"functional testing",
"unit testing"
],
"time": "2019-02-20T20:45:25+00:00"
"time": "2019-03-23T17:57:45+00:00"
},
{
"name": "codeception/phpunit-wrapper",
@@ -2833,27 +2839,29 @@
},
{
"name": "doctrine/instantiator",
"version": "1.1.0",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda"
"reference": "a2c590166b2133a4633738648b6b064edae0814a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
"reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a",
"reference": "a2c590166b2133a4633738648b6b064edae0814a",
"shasum": ""
},
"require": {
"php": "^7.1"
},
"require-dev": {
"athletic/athletic": "~0.1.8",
"doctrine/coding-standard": "^6.0",
"ext-pdo": "*",
"ext-phar": "*",
"phpunit/phpunit": "^6.2.3",
"squizlabs/php_codesniffer": "^3.0.2"
"phpbench/phpbench": "^0.13",
"phpstan/phpstan-phpunit": "^0.11",
"phpstan/phpstan-shim": "^0.11",
"phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
@@ -2878,12 +2886,12 @@
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"homepage": "https://github.com/doctrine/instantiator",
"homepage": "https://www.doctrine-project.org/projects/instantiator.html",
"keywords": [
"constructor",
"instantiate"
],
"time": "2017-07-22T11:58:36+00:00"
"time": "2019-03-17T17:37:11+00:00"
},
{
"name": "facebook/webdriver",
@@ -3212,39 +3220,36 @@
},
{
"name": "nette/bootstrap",
"version": "v2.4.6",
"version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/nette/bootstrap.git",
"reference": "268816e3f1bb7426c3a4ceec2bd38a036b532543"
"reference": "e1075af05c211915e03e0c86542f3ba5433df4a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/bootstrap/zipball/268816e3f1bb7426c3a4ceec2bd38a036b532543",
"reference": "268816e3f1bb7426c3a4ceec2bd38a036b532543",
"url": "https://api.github.com/repos/nette/bootstrap/zipball/e1075af05c211915e03e0c86542f3ba5433df4a3",
"reference": "e1075af05c211915e03e0c86542f3ba5433df4a3",
"shasum": ""
},
"require": {
"nette/di": "~2.4.7",
"nette/utils": "~2.4",
"php": ">=5.6.0"
},
"conflict": {
"nette/nette": "<2.2"
"nette/di": "^3.0",
"nette/utils": "^3.0",
"php": ">=7.1"
},
"require-dev": {
"latte/latte": "~2.2",
"nette/application": "~2.3",
"nette/caching": "~2.3",
"nette/database": "~2.3",
"nette/forms": "~2.3",
"nette/http": "~2.4.0",
"nette/mail": "~2.3",
"nette/robot-loader": "^2.4.2 || ^3.0",
"nette/safe-stream": "~2.2",
"nette/security": "~2.3",
"nette/tester": "~2.0",
"tracy/tracy": "^2.4.1"
"latte/latte": "^2.2",
"nette/application": "^3.0",
"nette/caching": "^3.0",
"nette/database": "^3.0",
"nette/forms": "^3.0",
"nette/http": "^3.0",
"nette/mail": "^3.0",
"nette/robot-loader": "^3.0",
"nette/safe-stream": "^2.2",
"nette/security": "^3.0",
"nette/tester": "^2.0",
"tracy/tracy": "^2.6"
},
"suggest": {
"nette/robot-loader": "to use Configurator::createRobotLoader()",
@@ -3253,7 +3258,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@@ -3284,46 +3289,50 @@
"configurator",
"nette"
],
"time": "2018-05-17T12:52:20+00:00"
"time": "2019-03-26T12:59:07+00:00"
},
{
"name": "nette/di",
"version": "v2.4.15",
"version": "v3.0.0",
"source": {
"type": "git",
"url": "https://github.com/nette/di.git",
"reference": "d0561b8f77e8ef2ed6d83328860e16c81a5a8649"
"reference": "19d83539245aaacb59470828919182411061841f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/di/zipball/d0561b8f77e8ef2ed6d83328860e16c81a5a8649",
"reference": "d0561b8f77e8ef2ed6d83328860e16c81a5a8649",
"url": "https://api.github.com/repos/nette/di/zipball/19d83539245aaacb59470828919182411061841f",
"reference": "19d83539245aaacb59470828919182411061841f",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"nette/neon": "^2.3.3 || ~3.0.0",
"nette/php-generator": "^2.6.1 || ^3.0.0",
"nette/utils": "^2.5.0 || ~3.0.0",
"php": ">=5.6.0"
"nette/neon": "^3.0",
"nette/php-generator": "^3.2.2",
"nette/robot-loader": "^3.2",
"nette/schema": "^1.0",
"nette/utils": "^3.0",
"php": ">=7.1"
},
"conflict": {
"nette/bootstrap": "<2.4",
"nette/nette": "<2.2"
"nette/bootstrap": "<3.0"
},
"require-dev": {
"nette/tester": "^2.0",
"nette/tester": "^2.2",
"tracy/tracy": "^2.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
],
"files": [
"src/compatibility.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -3353,7 +3362,7 @@
"nette",
"static"
],
"time": "2019-01-30T13:26:05+00:00"
"time": "2019-04-03T19:35:46+00:00"
},
{
"name": "nette/finder",
@@ -3407,7 +3416,7 @@
"homepage": "https://nette.org/contributors"
}
],
"description": "🔍 Nette Finder: find files and directories with an intuitive API.",
"description": "? Nette Finder: find files and directories with an intuitive API.",
"homepage": "https://nette.org",
"keywords": [
"filesystem",
@@ -3467,7 +3476,7 @@
"homepage": "https://nette.org/contributors"
}
],
"description": "🍸 Nette NEON: encodes and decodes NEON file format.",
"description": "? Nette NEON: encodes and decodes NEON file format.",
"homepage": "http://ne-on.org",
"keywords": [
"export",
@@ -3480,25 +3489,22 @@
},
{
"name": "nette/php-generator",
"version": "v3.2.1",
"version": "v3.2.2",
"source": {
"type": "git",
"url": "https://github.com/nette/php-generator.git",
"reference": "9de4e093a130f7a1bd175198799ebc0efbac6924"
"reference": "acff8b136fad84b860a626d133e791f95781f9f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/php-generator/zipball/9de4e093a130f7a1bd175198799ebc0efbac6924",
"reference": "9de4e093a130f7a1bd175198799ebc0efbac6924",
"url": "https://api.github.com/repos/nette/php-generator/zipball/acff8b136fad84b860a626d133e791f95781f9f5",
"reference": "acff8b136fad84b860a626d133e791f95781f9f5",
"shasum": ""
},
"require": {
"nette/utils": "^2.4.2 || ~3.0.0",
"php": ">=7.1"
},
"conflict": {
"nette/nette": "<2.2"
},
"require-dev": {
"nette/tester": "^2.0",
"tracy/tracy": "^2.3"
@@ -3538,30 +3544,27 @@
"php",
"scaffolding"
],
"time": "2018-11-27T19:00:14+00:00"
"time": "2019-03-15T03:41:13+00:00"
},
{
"name": "nette/robot-loader",
"version": "v3.1.1",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/nette/robot-loader.git",
"reference": "3e8d75d6d976e191bdf46752ca40a286671219d2"
"reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/robot-loader/zipball/3e8d75d6d976e191bdf46752ca40a286671219d2",
"reference": "3e8d75d6d976e191bdf46752ca40a286671219d2",
"url": "https://api.github.com/repos/nette/robot-loader/zipball/0712a0e39ae7956d6a94c0ab6ad41aa842544b5c",
"reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"nette/finder": "^2.3 || ^3.0",
"nette/utils": "^2.4 || ^3.0",
"php": ">=5.6.0"
},
"conflict": {
"nette/nette": "<2.2"
"nette/finder": "^2.5",
"nette/utils": "^3.0",
"php": ">=7.1"
},
"require-dev": {
"nette/tester": "^2.0",
@@ -3570,7 +3573,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1-dev"
"dev-master": "3.2-dev"
}
},
"autoload": {
@@ -3594,7 +3597,7 @@
"homepage": "https://nette.org/contributors"
}
],
"description": "🍀 Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.",
"description": "? Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.",
"homepage": "https://nette.org",
"keywords": [
"autoload",
@@ -3603,27 +3606,81 @@
"nette",
"trait"
],
"time": "2019-03-01T20:23:02+00:00"
"time": "2019-03-08T21:57:24+00:00"
},
{
"name": "nette/utils",
"version": "v2.5.3",
"name": "nette/schema",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "17b9f76f2abd0c943adfb556e56f2165460b15ce"
"url": "https://github.com/nette/schema.git",
"reference": "6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/17b9f76f2abd0c943adfb556e56f2165460b15ce",
"reference": "17b9f76f2abd0c943adfb556e56f2165460b15ce",
"url": "https://api.github.com/repos/nette/schema/zipball/6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d",
"reference": "6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d",
"shasum": ""
},
"require": {
"php": ">=5.6.0"
"nette/utils": "^3.0.1",
"php": ">=7.1"
},
"conflict": {
"nette/nette": "<2.2"
"require-dev": {
"nette/tester": "^2.2",
"tracy/tracy": "^2.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause",
"GPL-2.0",
"GPL-3.0"
],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"description": "📐 Nette Schema: validating data structures against a given Schema.",
"homepage": "https://nette.org",
"keywords": [
"config",
"nette"
],
"time": "2019-04-03T15:53:25+00:00"
},
{
"name": "nette/utils",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "bd961f49b211997202bda1d0fbc410905be370d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/bd961f49b211997202bda1d0fbc410905be370d4",
"reference": "bd961f49b211997202bda1d0fbc410905be370d4",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"require-dev": {
"nette/tester": "~2.0",
@@ -3632,7 +3689,7 @@
"suggest": {
"ext-gd": "to use Image",
"ext-iconv": "to use Strings::webalize() and toAscii()",
"ext-intl": "for script transliteration in Strings::webalize() and toAscii()",
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
"ext-json": "to use Nette\\Utils\\Json",
"ext-mbstring": "to use Strings::lower() etc...",
"ext-xml": "to use Strings::length() etc. when mbstring is not available"
@@ -3640,15 +3697,12 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
],
"files": [
"src/loader.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -3685,7 +3739,7 @@
"utility",
"validation"
],
"time": "2018-09-18T10:22:16+00:00"
"time": "2019-03-22T01:00:30+00:00"
},
{
"name": "nikic/php-parser",
@@ -4153,16 +4207,16 @@
},
{
"name": "phpstan/phpstan",
"version": "0.11.4",
"version": "0.11.5",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "ccc4f854748664cc61d1f3d4ecb26810df1f0cd4"
"reference": "24ce5a566a798b81343138ed5d41d6877554cf9a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/ccc4f854748664cc61d1f3d4ecb26810df1f0cd4",
"reference": "ccc4f854748664cc61d1f3d4ecb26810df1f0cd4",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/24ce5a566a798b81343138ed5d41d6877554cf9a",
"reference": "24ce5a566a798b81343138ed5d41d6877554cf9a",
"shasum": ""
},
"require": {
@@ -4222,7 +4276,7 @@
"MIT"
],
"description": "PHPStan - PHP Static Analysis Tool",
"time": "2019-03-14T14:46:15+00:00"
"time": "2019-03-25T16:40:09+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -4524,16 +4578,16 @@
},
{
"name": "phpunit/phpunit",
"version": "7.5.7",
"version": "7.5.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "eb343b86753d26de07ecba7868fa983104361948"
"reference": "c29c0525cf4572c11efe1db49a8b8aee9dfac58a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/eb343b86753d26de07ecba7868fa983104361948",
"reference": "eb343b86753d26de07ecba7868fa983104361948",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c29c0525cf4572c11efe1db49a8b8aee9dfac58a",
"reference": "c29c0525cf4572c11efe1db49a8b8aee9dfac58a",
"shasum": ""
},
"require": {
@@ -4604,7 +4658,7 @@
"testing",
"xunit"
],
"time": "2019-03-16T07:31:17+00:00"
"time": "2019-03-26T13:23:54+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@@ -5174,7 +5228,7 @@
},
{
"name": "symfony/browser-kit",
"version": "v4.2.4",
"version": "v4.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
@@ -5231,7 +5285,7 @@
},
{
"name": "symfony/css-selector",
"version": "v4.2.4",
"version": "v4.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@@ -5284,7 +5338,7 @@
},
{
"name": "symfony/dom-crawler",
"version": "v4.2.4",
"version": "v4.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
@@ -5341,7 +5395,7 @@
},
{
"name": "symfony/finder",
"version": "v4.2.4",
"version": "v4.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
@@ -5390,16 +5444,16 @@
},
{
"name": "theseer/tokenizer",
"version": "1.1.0",
"version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b"
"reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b",
"reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/1c42705be2b6c1de5904f8afacef5895cab44bf8",
"reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8",
"shasum": ""
},
"require": {
@@ -5426,7 +5480,7 @@
}
],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"time": "2017-04-07T12:08:54+00:00"
"time": "2019-04-04T09:56:43+00:00"
},
{
"name": "victorjonsson/markdowndocs",

View File

@@ -30,9 +30,25 @@ div.phpdebugbar {
}
.phpdebugbar .phpdebugbar-widgets-toolbar {
border-top: 1px solid #ddd;
padding-left: 5px;
padding-right: 2px;
padding-top: 2px;
background-color: #fafafa !important;
width: auto !important;
left: 0;
right: 0;
}
.phpdebugbar .phpdebugbar-widgets-toolbar input {
background: transparent !important;
}
.phpdebugbar .phpdebugbar-widgets-toolbar .phpdebugbar-widgets-filter {
}
.phpdebugbar input[type=text] {
padding: 0;
display: inline;

View File

@@ -323,6 +323,12 @@ form:
validate:
type: commalist
languages.default_lang:
type: text
size: x-small
label: PLUGIN_ADMIN.DEFAULT_LANG
help: PLUGIN_ADMIN.DEFAULT_LANG_HELP
languages.include_default_lang:
type: toggle
label: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG

View File

@@ -13,6 +13,7 @@ intl_enabled: true # Special logic for PHP Interna
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
pages_fallback_only: false # Only fallback to find page content through supported languages
translations: true # Enable translations by default
@@ -53,7 +54,7 @@ pages:
special_chars: # List of special characters to automatically convert to entities
'>': 'gt'
'<': 'lt'
types: [html,htm,json,xml,txt,rss,atom] # list of valid page types
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)
cache_control: # Can be blank for no setting, or a valid `cache-control` text value

View File

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

View File

@@ -37,12 +37,6 @@ class Assets extends PropertyObject
/** @const Regex to match JavaScript files */
const JS_REGEX = '/.\.js$/i';
/**
* @const Regex to match <script> or <style> tag when adding inline style/script. Note that this only supports a
* single tag, so the check is greedy to avoid issues in JS.
*/
const HTML_TAG_REGEX = '#(<([A-Z][A-Z0-9]*)>)+(.*)(<\/\2>)#is';
protected $assets_dir;
protected $assets_url;
@@ -51,8 +45,10 @@ class Assets extends PropertyObject
// Config Options
protected $css_pipeline;
protected $css_pipeline_include_externals;
protected $css_pipeline_before_excludes;
protected $js_pipeline;
protected $js_pipeline_include_externals;
protected $js_pipeline_before_excludes;
protected $pipeline_options = [];
@@ -270,6 +266,21 @@ class Assets extends PropertyObject
protected function filterAssets($assets, $key, $value, $sort = false)
{
$results = array_filter($assets, function($asset) use ($key, $value) {
if ($key === 'position' && $value === 'pipeline') {
$type = $asset->getType();
if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false) {
if ($this->{$type . '_pipeline_before_excludes'}) {
$asset->setPosition('after');
} else {
$asset->setPosition('before');
}
return false;
}
}
if ($asset[$key] === $value) return true;
return false;
});
@@ -298,11 +309,9 @@ class Assets extends PropertyObject
$before_output = '';
$pipeline_output = '';
$after_output = '';
$no_pipeline = [];
$assets = 'assets_' . $type;
$pipeline_enabled = $type . '_pipeline';
$before_excludes = $type . '_pipeline_before_excludes';
$render_pipeline = 'render' . ucfirst($type);
$group_assets = $this->filterAssets($this->$assets, 'group', $group);
@@ -315,22 +324,13 @@ class Assets extends PropertyObject
$options = array_merge($this->pipeline_options, ['timestamp' => $this->timestamp]);
$pipeline = new Pipeline($options);
$pipeline_output = $pipeline->$render_pipeline($pipeline_assets, $group, $attributes, $no_pipeline);
$pipeline_output = $pipeline->$render_pipeline($pipeline_assets, $group, $attributes);
} else {
foreach ($pipeline_assets as $asset) {
$pipeline_output .= $asset->render();
}
}
// Handle stuff that couldn't be pipelined
if (!empty($no_pipeline)) {
if ($this->{$before_excludes}) {
$after_assets = array_merge($after_assets, $no_pipeline);
} else {
$before_assets = array_merge($before_assets, $no_pipeline);
}
}
// Before Pipeline
foreach ($before_assets as $asset) {
$before_output .= $asset->render();

View File

@@ -129,6 +129,12 @@ abstract class BaseAsset extends PropertyObject
return $this->remote;
}
public function setPosition($position)
{
$this->position = $position;
return $this;
}
/**
*

View File

@@ -33,6 +33,8 @@ class Pipeline extends PropertyObject
/** @const Regex to match CSS import content */
protected const CSS_IMPORT_REGEX = '{@import(.*?);}';
protected const FIRST_FORWARDSLASH_REGEX = '{^\/{1}\w}';
protected $css_minify;
protected $css_minify_windows;
protected $css_rewrite;
@@ -48,9 +50,6 @@ class Pipeline extends PropertyObject
protected $query;
protected $asset;
protected $css_pipeline_include_externals;
protected $js_pipeline_include_externals;
/**
* Closure used by the pipeline to fetch assets.
*
@@ -89,11 +88,10 @@ class Pipeline extends PropertyObject
* @param array $assets
* @param string $group
* @param array $attributes
* @param array $no_pipeline
*
* @return bool|string URL or generated content if available, else false
*/
public function renderCss($assets, $group, $attributes = [], &$no_pipeline = [])
public function renderCss($assets, $group, $attributes = [])
{
// temporary list of assets to pipeline
$inline_group = false;
@@ -117,21 +115,13 @@ class Pipeline extends PropertyObject
if (file_exists($this->assets_dir . $file)) {
$buffer = file_get_contents($this->assets_dir . $file) . "\n";
} else {
foreach ($assets as $id => $asset) {
if ($this->css_pipeline_include_externals === false && $asset->getRemote()) {
$no_pipeline[$id] = $asset;
unset($assets[$id]);
}
}
//if nothing found get out of here!
if (empty($assets) && empty($no_pipeline)) {
if (empty($assets)) {
return false;
}
// Concatenate files
$buffer = $this->gatherLinks($assets, self::CSS_ASSET, $no_pipeline);
$buffer = $this->gatherLinks($assets, self::CSS_ASSET);
// Minify if required
if ($this->shouldMinify('css')) {
@@ -162,11 +152,10 @@ class Pipeline extends PropertyObject
* @param array $assets
* @param string $group
* @param array $attributes
* @param array $no_pipeline
*
* @return bool|string URL or generated content if available, else false
*/
public function renderJs($assets, $group, $attributes = [], &$no_pipeline = [])
public function renderJs($assets, $group, $attributes = [])
{
// temporary list of assets to pipeline
$inline_group = false;
@@ -190,21 +179,13 @@ class Pipeline extends PropertyObject
if (file_exists($this->assets_dir . $file)) {
$buffer = file_get_contents($this->assets_dir . $file) . "\n";
} else {
foreach ($assets as $id => $asset) {
if ($this->js_pipeline_include_externals === false && $asset->getRemote()) {
$no_pipeline[$id] = $asset;
unset($assets[$id]);
}
}
//if nothing found get out of here!
if (empty($assets) && empty($no_pipeline)) {
if (empty($assets)) {
return false;
}
// Concatenate files
$buffer = $this->gatherLinks($assets, self::JS_ASSET, $no_pipeline);
$buffer = $this->gatherLinks($assets, self::JS_ASSET);
// Minify if required
if ($this->shouldMinify('js')) {
@@ -251,13 +232,21 @@ class Pipeline extends PropertyObject
$old_url = $matches[2];
// Ensure link is not rooted to web server, a data URL, or to a remote host
if (Utils::startsWith($old_url, '/') || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url) || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
return $matches[0];
}
$new_url = ($local ? $this->base_url: '') . ltrim(Utils::normalizePath($dir . '/' . $old_url), '/');
// clean leading /
$old_url = Utils::normalizePath($dir . '/' . $old_url);
if (preg_match(self::FIRST_FORWARDSLASH_REGEX, $old_url)) {
$old_url = ltrim($old_url, '/');
}
return str_replace($old_url, $new_url, $matches[0]);
$new_url = ($local ? $this->base_url: '') . $old_url;
$fixed = str_replace($matches[2], $new_url, $matches[0]);
return $fixed;
}, $file);
return $file;

View File

@@ -38,11 +38,10 @@ trait AssetUtilsTrait
*
* @param array $assets
* @param bool $css
* @param array $no_pipeline
*
* @return string
*/
protected function gatherLinks(array $assets, $css = true, &$no_pipeline = [])
protected function gatherLinks(array $assets, $css = true)
{
$buffer = '';
@@ -74,9 +73,6 @@ trait AssetUtilsTrait
// No file found, skip it...
if ($file === false) {
if (!$local) { // Assume we couldn't download this file for some reason assume it's not pipeline compatible
$no_pipeline[$id] = $asset;
}
continue;
}
@@ -113,7 +109,7 @@ trait AssetUtilsTrait
{
$imports = [];
$file = (string)preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) {
$file = (string)preg_replace_callback(self::CSS_IMPORT_REGEX, function ($matches) use (&$imports) {
$imports[] = $matches[0];
return '';

View File

@@ -13,6 +13,7 @@ use \Doctrine\Common\Cache as DoctrineCache;
use Grav\Common\Config\Config;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Scheduler\Scheduler;
use Psr\SimpleCache\CacheInterface;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
@@ -44,6 +45,11 @@ class Cache extends Getters
*/
protected $driver;
/**
* @var CacheInterface
*/
protected $simpleCache;
protected $driver_name;
protected $driver_setting;
@@ -140,6 +146,23 @@ class Cache extends Getters
$dispatcher->addListener('onSchedulerInitialized', [$this, 'onSchedulerInitialized']);
}
/**
* @return CacheInterface
*/
public function getSimpleCache()
{
if (null === $this->simpleCache) {
$cache = new \Grav\Framework\Cache\Adapter\DoctrineCache($this->driver, '', $this->getLifetime());
// Disable cache key validation.
$cache->setValidation(false);
$this->simpleCache = $cache;
}
return $this->simpleCache;
}
/**
* Deletes the old out of date file-based caches
*

View File

@@ -25,6 +25,18 @@ class Blueprint extends BlueprintForm
/** @var BlueprintSchema */
protected $blueprintSchema;
/** @var array */
protected $defaults;
protected $handlers = [];
public function __clone()
{
if ($this->blueprintSchema) {
$this->blueprintSchema = clone $this->blueprintSchema;
}
}
public function setScope($scope)
{
$this->scope = $scope;
@@ -56,7 +68,60 @@ class Blueprint extends BlueprintForm
{
$this->initInternals();
return $this->blueprintSchema->getDefaults();
if (null === $this->defaults) {
$this->defaults = $this->blueprintSchema->getDefaults();
}
return $this->defaults;
}
/**
* Initialize blueprints with its dynamic fields.
*
* @return $this
*/
public function init()
{
foreach ($this->dynamic as $key => $data) {
// Locate field.
$path = explode('/', $key);
$current = &$this->items;
foreach ($path as $field) {
if (\is_object($current)) {
// Handle objects.
if (!isset($current->{$field})) {
$current->{$field} = [];
}
$current = &$current->{$field};
} else {
// Handle arrays and scalars.
if (!\is_array($current)) {
$current = [$field => []];
} elseif (!isset($current[$field])) {
$current[$field] = [];
}
$current = &$current[$field];
}
}
// Set dynamic property.
foreach ($data as $property => $call) {
$action = $call['action'];
$method = 'dynamic' . ucfirst($action);
if (isset($this->handlers[$action])) {
$callable = $this->handlers[$action];
$callable($current, $property, $call);
} elseif (method_exists($this, $method)) {
$this->{$method}($current, $property, $call);
}
}
}
return $this;
}
/**
@@ -158,6 +223,11 @@ class Blueprint extends BlueprintForm
return $this->blueprintSchema;
}
public function addDynamicHandler(string $name, callable $callable): void
{
$this->handlers[$name] = $callable;
}
/**
* Initialize validator.
*/
@@ -174,6 +244,7 @@ class Blueprint extends BlueprintForm
$this->blueprintSchema->embed('', $this->items);
$this->blueprintSchema->init();
$this->defaults = null;
}
}

View File

@@ -216,12 +216,12 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
}
/**
* @param array $data
* @param array|null $data
* @param array $toggles
* @param array $nested
* @return array
* @return array|null
*/
protected function processFormRecursive(array $data, array $toggles, array $nested)
protected function processFormRecursive(?array $data, array $toggles, array $nested)
{
foreach ($nested as $key => $value) {
if ($key === '') {
@@ -233,11 +233,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
}
if (is_array($value)) {
// Recursively fetch the items.
$array = $this->processFormRecursive($data[$key] ?? [], $toggles[$key] ?? [], $value);
if (!empty($array)) {
$data[$key] = $array;
}
$data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggles[$key] ?? [], $value);
} else {
$field = $this->get($value);
// Do not add the field if:

View File

@@ -21,6 +21,9 @@ use DebugBar\DebugBar;
use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
use Grav\Common\Config\Config;
use Grav\Common\Processors\ProcessorInterface;
use Twig\Template;
use Twig\TemplateWrapper;
class Debugger
{
@@ -44,7 +47,7 @@ class Debugger
/** @var array */
protected $timers = [];
/** @var string[] $deprecations */
/** @var array $deprecations */
protected $deprecations = [];
/** @var callable */
@@ -356,57 +359,183 @@ class Debugger
return true;
}
$backtrace = debug_backtrace(false);
// Figure out error scope from the error.
$scope = 'unknown';
if (stripos($errstr, 'grav') !== false) {
$scope = 'grav';
} elseif (strpos($errfile, '/twig/') !== false) {
$scope = 'twig';
} elseif (stripos($errfile, '/yaml/') !== false) {
$scope = 'yaml';
} elseif (strpos($errfile, '/vendor/') !== false) {
$scope = 'vendor';
}
// Clean up backtrace to make it more useful.
$backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
// Skip current call.
array_shift($backtrace);
// Find yaml file where the error happened.
if ($scope === 'yaml') {
foreach ($backtrace as $current) {
if (isset($current['args'])) {
foreach ($current['args'] as $arg) {
if ($arg instanceof \SplFileInfo) {
$arg = $arg->getPathname();
}
if (\is_string($arg) && preg_match('/.+\.(yaml|md)$/i', $arg)) {
$errfile = $arg;
$errline = 0;
break 2;
}
}
}
}
}
// Filter arguments.
$cut = 0;
$previous = null;
foreach ($backtrace as $i => &$current) {
if (isset($current['args'])) {
$args = [];
foreach ($current['args'] as $arg) {
if (\is_string($arg)) {
$arg = "'" . $arg . "'";
if (mb_strlen($arg) > 100) {
$arg = 'string';
}
} elseif (\is_bool($arg)) {
$arg = $arg ? 'true' : 'false';
} elseif (\is_scalar($arg)) {
$arg = $arg;
} elseif (\is_object($arg)) {
$arg = get_class($arg) . ' $object';
} elseif (\is_array($arg)) {
$arg = '$array';
} else {
$arg = '$object';
}
$args[] = $arg;
}
$current['args'] = $args;
}
$object = $current['object'] ?? null;
unset($current['object']);
$reflection = null;
if ($object instanceof TemplateWrapper) {
$reflection = new \ReflectionObject($object);
$property = $reflection->getProperty('template');
$property->setAccessible(true);
$object = $property->getValue($object);
}
if ($object instanceof Template) {
$file = $current['file'] ?? null;
if (preg_match('`(Template.php|TemplateWrapper.php)$`', $file)) {
$current = null;
continue;
}
$debugInfo = $object->getDebugInfo();
$line = 1;
if (!$reflection) {
foreach ($debugInfo as $codeLine => $templateLine) {
if ($codeLine <= $current['line']) {
$line = $templateLine;
break;
}
}
}
$src = $object->getSourceContext();
//$code = preg_split('/\r\n|\r|\n/', $src->getCode());
//$current['twig']['twig'] = trim($code[$line - 1]);
$current['twig']['file'] = $src->getPath();
$current['twig']['line'] = $line;
$prevFile = $previous['file'] ?? null;
if ($prevFile && $file === $prevFile) {
$prevLine = $previous['line'];
$line = 1;
foreach ($debugInfo as $codeLine => $templateLine) {
if ($codeLine <= $prevLine) {
$line = $templateLine;
break;
}
}
//$previous['twig']['twig'] = trim($code[$line - 1]);
$previous['twig']['file'] = $src->getPath();
$previous['twig']['line'] = $line;
}
$cut = $i;
} elseif ($object instanceof ProcessorInterface) {
$cut = $cut ?: $i;
break;
}
$previous = &$backtrace[$i];
}
unset($current);
if ($cut) {
$backtrace = array_slice($backtrace, 0, $cut + 1);
}
$backtrace = array_values(array_filter($backtrace));
// Skip vendor libraries and the method where error was triggered.
while ($current = array_shift($backtrace)) {
if (isset($current['file']) && strpos($current['file'], 'vendor') !== false) {
foreach ($backtrace as $i => $current) {
if (!isset($current['file'])) {
continue;
}
if (strpos($current['file'], '/vendor/') !== false) {
$cut = $i + 1;
continue;
}
if (isset($current['function']) && ($current['function'] === 'user_error' || $current['function'] === 'trigger_error')) {
$current = array_shift($backtrace);
$cut = $i + 1;
continue;
}
break;
}
// Add back last call.
array_unshift($backtrace, $current);
// Filter arguments.
foreach ($backtrace as &$current) {
if (isset($current['args'])) {
$args = [];
foreach ($current['args'] as $arg) {
if (\is_string($arg)) {
$args[] = "'" . $arg . "'";
} elseif (\is_bool($arg)) {
$args[] = $arg ? 'true' : 'false';
} elseif (\is_scalar($arg)) {
$args[] = $arg;
} elseif (\is_object($arg)) {
$args[] = get_class($arg) . ' $object';
} elseif (\is_array($arg)) {
$args[] = '$array';
} else {
$args[] = '$object';
}
}
$current['args'] = $args;
}
if ($cut) {
$backtrace = array_slice($backtrace, $cut);
}
unset($current);
$backtrace = array_values(array_filter($backtrace));
$this->deprecations[] = [
$current = reset($backtrace);
// If the issue happened inside twig file, change the file and line to match that file.
$file = $current['twig']['file'] ?? '';
if ($file) {
$errfile = $file;
$errline = $current['twig']['line'] ?? 0;
}
$deprecation = [
'scope' => $scope,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'trace' => $backtrace,
'count' => 1
];
$this->deprecations[] = $deprecation;
// Do not pass forward.
return true;
}
@@ -431,38 +560,37 @@ class Debugger
protected function getDepracatedMessage($deprecated)
{
$scope = 'unknown';
if (stripos($deprecated['message'], 'grav') !== false) {
$scope = 'grav';
} elseif (!isset($deprecated['file'])) {
$scope = 'unknown';
} elseif (stripos($deprecated['file'], 'twig') !== false) {
$scope = 'twig';
} elseif (stripos($deprecated['file'], 'yaml') !== false) {
$scope = 'yaml';
} elseif (stripos($deprecated['file'], 'vendor') !== false) {
$scope = 'vendor';
}
$scope = $deprecated['scope'];
$trace = [];
foreach ($deprecated['trace'] as $current) {
$class = $current['class'] ?? '';
$type = $current['type'] ?? '';
$function = $this->getFunction($current);
if (isset($current['file'])) {
$current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
if (isset($deprecated['trace'])) {
foreach ($deprecated['trace'] as $current) {
$class = $current['class'] ?? '';
$type = $current['type'] ?? '';
$function = $this->getFunction($current);
if (isset($current['file'])) {
$current['file'] = str_replace(GRAV_ROOT . '/', '', $current['file']);
}
unset($current['class'], $current['type'], $current['function'], $current['args']);
if (isset($current['twig'])) {
$trace[] = $current['twig'];
} else {
$trace[] = ['call' => $class . $type . $function] + $current;
}
}
unset($current['class'], $current['type'], $current['function'], $current['args']);
$trace[] = ['call' => $class . $type . $function] + $current;
}
$array = [
'message' => $deprecated['message'],
'file' => $deprecated['file'],
'line' => $deprecated['line'],
'trace' => $trace
];
return [
[
'message' => $deprecated['message'],
'trace' => $trace
],
array_filter($array),
$scope
];
}
@@ -473,6 +601,6 @@ class Debugger
return '';
}
return $trace['function'] . '(' . implode(', ', $trace['args']) . ')';
return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
}
}

View File

@@ -9,6 +9,8 @@
namespace Grav\Common\Filesystem;
use Grav\Common\Utils;
abstract class Archiver
{
protected $options = [
@@ -35,6 +37,11 @@ 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')) {
set_time_limit(0);
}
$this->options = $options + $this->options;
return $this;
}

View File

@@ -12,11 +12,16 @@ namespace Grav\Common\GPM\Remote;
use Grav\Common\Data\Data;
use Grav\Common\GPM\Common\Package as BasePackage;
class Package extends BasePackage
class Package extends BasePackage implements \JsonSerializable
{
public function __construct($package, $package_type = null)
{
$data = new Data($package);
parent::__construct($data, $package_type);
}
public function jsonSerialize()
{
return $this->data;
}
}

View File

@@ -41,13 +41,12 @@ class Truncator {
return $html;
}
$dom = self::htmlToDomDocument($html);
// Grab the body of our DOM.
$body = $dom->getElementsByTagName('body')->item(0);
$doc = self::htmlToDomDocument($html);
$container = $doc->getElementsByTagName('div')->item(0);
$container = $container->parentNode->removeChild($container);
// Iterate over words.
$words = new DOMWordsIterator($body);
$words = new DOMWordsIterator($container);
$truncated = false;
foreach ($words as $word) {
@@ -66,7 +65,7 @@ class Truncator {
$words[$offset][1] + strlen($words[$offset][0])
);
self::removeProceedingNodes($curNode, $body);
self::removeProceedingNodes($curNode, $container);
if (!empty($ellipsis)) {
self::insertEllipsis($curNode, $ellipsis);
@@ -81,7 +80,7 @@ class Truncator {
// Return original HTML if not truncated.
if ($truncated) {
return self::innerHTML($body);
$html = self::getCleanedHtml($doc, $container);
}
return $html;
@@ -100,13 +99,12 @@ class Truncator {
return $html;
}
$dom = self::htmlToDomDocument($html);
// Grab the body of our DOM.
$body = $dom->getElementsByTagName('body')->item(0);
$doc = self::htmlToDomDocument($html);
$container = $doc->getElementsByTagName('div')->item(0);
$container = $container->parentNode->removeChild($container);
// Iterate over letters.
$letters = new DOMLettersIterator($body);
$letters = new DOMLettersIterator($container);
$truncated = false;
foreach ($letters as $letter) {
@@ -115,7 +113,7 @@ class Truncator {
$currentText = $letters->currentTextPosition();
$currentText[0]->nodeValue = mb_substr($currentText[0]->nodeValue, 0, $currentText[1] + 1);
self::removeProceedingNodes($currentText[0], $body);
self::removeProceedingNodes($currentText[0], $container);
if (!empty($ellipsis)) {
self::insertEllipsis($currentText[0], $ellipsis);
@@ -129,7 +127,7 @@ class Truncator {
// Return original HTML if not truncated.
if ($truncated) {
return self::innerHTML($body);
$html = self::getCleanedHtml($doc, $container);
}
return $html;
@@ -143,7 +141,7 @@ class Truncator {
public static function htmlToDomDocument($html)
{
if (!$html) {
$html = '<p></p>';
$html = '';
}
// Transform multibyte entities which otherwise display incorrectly.
@@ -155,7 +153,7 @@ class Truncator {
// Instantiate new DOMDocument object, and then load in UTF-8 HTML.
$dom = new DOMDocument();
$dom->encoding = 'UTF-8';
$dom->loadHTML($html);
$dom->loadHTML("<div>$html</div>");
return $dom;
}
@@ -188,6 +186,27 @@ class Truncator {
}
}
/**
* Clean extra code
*
* @param DOMDocument $doc
* @param $container
* @return string
*/
private static function getCleanedHTML(DOMDocument $doc, $container)
{
while ($doc->firstChild) {
$doc->removeChild($doc->firstChild);
}
while ($container->firstChild ) {
$doc->appendChild($container->firstChild);
}
$html = trim($doc->saveHTML());
return $html;
}
/**
* Inserts an ellipsis
* @param DOMNode|DOMElement $domNode Element to insert after.
@@ -215,21 +234,102 @@ class Truncator {
}
/**
* Returns the innerHTML of a particular DOMElement
*
* @param DOMElement $element
* @return string
* @inheritDoc
*/
private static function innerHTML($element) {
$innerHTML = '';
$children = $element->childNodes;
foreach ($children as $child)
{
$tmp_dom = new DOMDocument();
$tmp_dom->appendChild($tmp_dom->importNode($child, true));
$innerHTML.=trim($tmp_dom->saveHTML());
public function truncate(
$text,
$length = 100,
$ending = '...',
$exact = false,
$considerHtml = true
) {
if ($considerHtml) {
// if the plain text is shorter than the maximum length, return the whole text
if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
return $text;
}
// splits all html-tags to scanable lines
preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
$total_length = strlen($ending);
$open_tags = array();
$truncate = '';
foreach ($lines as $line_matchings) {
// if there is any html-tag in this line, handle it and add it (uncounted) to the output
if (!empty($line_matchings[1])) {
// if it's an "empty element" with or without xhtml-conform closing slash
if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
// do nothing
// if tag is a closing tag
} else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
// delete tag from $open_tags list
$pos = array_search($tag_matchings[1], $open_tags);
if ($pos !== false) {
unset($open_tags[$pos]);
}
// if tag is an opening tag
} else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
// add tag to the beginning of $open_tags list
array_unshift($open_tags, strtolower($tag_matchings[1]));
}
// add html-tag to $truncate'd text
$truncate .= $line_matchings[1];
}
// calculate the length of the plain text part of the line; handle entities as one character
$content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
if ($total_length+$content_length> $length) {
// the number of characters which are left
$left = $length - $total_length;
$entities_length = 0;
// search for html entities
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {
// calculate the real length of all entities in the legal range
foreach ($entities[0] as $entity) {
if ($entity[1]+1-$entities_length <= $left) {
$left--;
$entities_length += strlen($entity[0]);
} else {
// no more characters left
break;
}
}
}
$truncate .= substr($line_matchings[2], 0, $left+$entities_length);
// maximum lenght is reached, so get off the loop
break;
} else {
$truncate .= $line_matchings[2];
$total_length += $content_length;
}
// if the maximum length is reached, get off the loop
if($total_length>= $length) {
break;
}
}
} else {
if (strlen($text) <= $length) {
return $text;
} else {
$truncate = substr($text, 0, $length - strlen($ending));
}
}
return $innerHTML;
// if the words shouldn't be cut in the middle...
if (!$exact) {
// ...search the last occurance of a space...
$spacepos = strrpos($truncate, ' ');
if (isset($spacepos)) {
// ...and cut the text in this position
$truncate = substr($truncate, 0, $spacepos);
}
}
// add the defined ending to the text
$truncate .= $ending;
if($considerHtml) {
// close all unclosed html-tags
foreach ($open_tags as $tag) {
$truncate .= '</' . $tag . '>';
}
}
return $truncate;
}
}

View File

@@ -51,7 +51,13 @@ class Language
*/
public function init()
{
$this->default = reset($this->languages);
$default = $this->config->get('system.languages.default_lang');
if (isset($default) && $this->validate($default)) {
$this->default = $default;
} else {
$this->default = reset($this->languages);
}
$this->page_extensions = null;
if (empty($this->languages)) {

View File

@@ -48,7 +48,7 @@ trait ParsedownGravTrait
$this->setMarkupEscaped($defaults['escape_markup']);
$this->setSpecialChars($defaults['special_chars']);
$grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $this]));
$grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $this, 'page' => $page]));
}

View File

@@ -13,6 +13,7 @@ use Grav\Common\Cache;
use Grav\Common\Grav;
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
use Grav\Common\Page\Media;
use Psr\SimpleCache\CacheInterface;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
trait MediaTrait
@@ -71,9 +72,9 @@ trait MediaTrait
// Use cached media if possible.
$cacheKey = md5('media' . $this->getCacheKey());
if (!$media = $cache->fetch($cacheKey)) {
if (!$media = $cache->get($cacheKey)) {
$media = new Media($this->getMediaFolder(), $this->getMediaOrder());
$cache->save($cacheKey, $media);
$cache->set($cacheKey, $media);
}
$this->media = $media;
}
@@ -91,7 +92,7 @@ trait MediaTrait
{
$cache = $this->getMediaCache();
$cacheKey = md5('media' . $this->getCacheKey());
$cache->save($cacheKey, $media);
$cache->set($cacheKey, $media);
$this->media = $media;
@@ -111,11 +112,14 @@ trait MediaTrait
}
/**
* @return Cache
* @return CacheInterface
*/
protected function getMediaCache()
{
return Grav::instance()['cache'];
/** @var Cache $cache */
$cache = Grav::instance()['cache'];
return $cache->getSimpleCache();
}
/**

View File

@@ -33,13 +33,14 @@ class ImageFile extends Image
/**
* This is the same as the Gregwar Image class except this one fires a Grav Event on creation of new cached file
*
* @param string $type the image type
* @param int $quality the quality (for JPEG)
* @param bool $actual
* @param string $type the image type
* @param int $quality the quality (for JPEG)
* @param bool $actual
* @param array $extras
*
* @return string
*/
public function cacheFile($type = 'jpg', $quality = 80, $actual = false)
public function cacheFile($type = 'jpg', $quality = 80, $actual = false, $extras = [])
{
if ($type === 'guess') {
$type = $this->guessType();
@@ -50,7 +51,7 @@ class ImageFile extends Image
}
// Computes the hash
$this->hash = $this->getHash($type, $quality);
$this->hash = $this->getHash($type, $quality, $extras);
// Seo friendly image names
$seofriendly = Grav::instance()['config']->get('system.images.seofriendly', false);
@@ -103,4 +104,42 @@ class ImageFile extends Image
return $this->getFilename($file);
}
/**
* Gets the hash.
* @param string $type
* @param int $quality
* @param [] $extras
* @return null
*/
public function getHash($type = 'guess', $quality = 80, $extras = [])
{
if (null === $this->hash) {
$this->generateHash($type, $quality, $extras);
}
return $this->hash;
}
/**
* Generates the hash.
* @param string $type
* @param int $quality
* @param array $extras
*/
public function generateHash($type = 'guess', $quality = 80, $extras = [])
{
$inputInfos = $this->source->getInfos();
$datas = array(
$inputInfos,
$this->serializeOperations(),
$type,
$quality,
$extras
);
$this->hash = sha1(serialize($datas));
}
}

View File

@@ -619,7 +619,7 @@ class ImageMedium extends Medium
$this->image->merge(ImageFile::open($overlay));
}
return $this->image->cacheFile($this->format, $this->quality);
return $this->image->cacheFile($this->format, $this->quality, false, [$this->get('width'), $this->get('height'), $this->get('modified')]);
}
/**

View File

@@ -414,9 +414,7 @@ class Page implements PageInterface
$this->markdown_extra = (bool)$this->header->markdown_extra;
}
if (isset($this->header->taxonomy)) {
foreach ((array)$this->header->taxonomy as $taxonomy => $taxitems) {
$this->taxonomy[$taxonomy] = (array)$taxitems;
}
$this->taxonomy($this->header->taxonomy);
}
if (isset($this->header->max_count)) {
$this->max_count = (int)$this->header->max_count;
@@ -1744,6 +1742,14 @@ class Page implements PageInterface
return $this->metadata;
}
/**
* Reset the metadata and pull from header again
*/
public function resetMetadata()
{
$this->metadata = null;
}
/**
* Gets and Sets the slug for the Page. The slug is used in the URL routing. If not set it uses
* the parent folder from the path
@@ -2288,6 +2294,14 @@ class Page implements PageInterface
public function taxonomy($var = null)
{
if ($var !== null) {
// make sure first level are arrays
array_walk($var, function(&$value) {
$value = (array) $value;
});
// make sure all values are strings
array_walk_recursive($var, function(&$value) {
$value = (string) $value;
});
$this->taxonomy = $var;
}

View File

@@ -9,6 +9,7 @@
namespace Grav\Common\Processors;
use Grav\Framework\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
@@ -25,5 +26,6 @@ class DebuggerAssetsProcessor extends ProcessorBase
$this->stopTimer();
return $handler->handle($request);
}
}

View File

@@ -23,6 +23,13 @@ class RequestProcessor extends ProcessorBase
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
$this->startTimer();
$header = $request->getHeaderLine('Content-Type');
$type = trim(strstr($header, ';', true) ?: $header);
if ($type === 'application/json') {
$request = $request->withParsedBody(json_decode($request->getBody()->getContents(), true));
}
$request = $request
->withAttribute('grav', $this->container)
->withAttribute('time', $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME)

View File

@@ -234,7 +234,7 @@ class Scheduler
{
$phpBinaryFinder = new PhpExecutableFinder();
$php = $phpBinaryFinder->find();
$command = 'cd ' . GRAV_ROOT . ';' . $php . ' bin/grav scheduler';
$command = 'cd ' . str_replace(' ', '\ ', GRAV_ROOT) . ';' . $php . ' bin/grav scheduler';
return "(crontab -l; echo \"* * * * * {$command} 1>> /dev/null 2>&1\") | crontab -";
}

View File

@@ -10,6 +10,7 @@
namespace Grav\Common\Service;
use Grav\Common\Config\Config;
use Grav\Common\Debugger;
use Grav\Common\User\DataUser;
use Grav\Common\User\FlexUser;
use Grav\Common\User\User;
@@ -26,7 +27,10 @@ class AccountsServiceProvider implements ServiceProviderInterface
public function register(Container $container)
{
$container['accounts'] = function (Container $container) {
/** @var Debugger $debugger */
$debugger = $container['debugger'];
if ($container['config']->get('system.accounts.type') === 'flex') {
$debugger->addMessage('User Accounts: Flex Directory');
return $this->flexAccounts($container);
}
@@ -100,8 +104,9 @@ class AccountsServiceProvider implements ServiceProviderInterface
'formatter' => ['class' => YamlFormatter::class],
'folder' => 'account://',
'pattern' => '{FOLDER}/{KEY:2}/{KEY}/user.yaml',
'key' => 'username',
'indexed' => true
]
],
];
}
@@ -111,8 +116,9 @@ class AccountsServiceProvider implements ServiceProviderInterface
'formatter' => ['class' => YamlFormatter::class],
'folder' => 'account://',
'pattern' => '{FOLDER}/{KEY}.yaml',
'key' => 'storage_key',
'indexed' => true
]
],
];
}
}

View File

@@ -80,6 +80,8 @@ class PagesServiceProvider implements ServiceProviderInterface
}
// Default route test and redirect
if ($config->get('system.pages.redirect_default_route') && $page->route() !== $path) {
$uri->setUriProperties(['path' => $page->route()]);
$url = (string) $uri;
$c->redirect($url);
}
}

View File

@@ -187,11 +187,9 @@ class Uri
$this->extension = $parts['extension'];
}
$valid_page_types = implode('|', $config->get('system.pages.types'));
// Strip the file extension for valid page types
if (preg_match('/\.(' . $valid_page_types . ')$/', $parts['basename'])) {
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, DS, $parts['dirname']), DS) . '/' . $parts['filename'];
if ($this->isValidExtension($this->extension)) {
$path = Utils::replaceLastOccurrence(".{$this->extension}", '', $path);
}
// set the new url
@@ -586,13 +584,16 @@ class Uri
public function toArray()
{
$root_path = $this->root_path ?? '';
$extension = isset($this->extension) && $this->isValidExtension($this->extension) ? '.' . $this->extension : '';
$path = $root_path . $this->path . $extension;
return [
'scheme' => $this->scheme,
'host' => $this->host,
'port' => $this->port,
'user' => $this->user,
'pass' => $this->password,
'path' => $this->path,
'path' => $path,
'params' => $this->params,
'query' => $this->query,
'fragment' => $this->fragment
@@ -767,7 +768,7 @@ class Uri
$normalized_path = Utils::normalizePath($pages_dir . $url_path);
} else {
$page_route = ($page->home() && !empty($url_path)) ? $page->rawRoute() : $page->route();
$normalized_url = $base_url . Utils::normalizePath($page_route . '/' . $url_path);
$normalized_url = $base_url . Utils::normalizePath(rtrim($page_route, '/') . '/' . $url_path);
$normalized_path = Utils::normalizePath($page->path() . '/' . $url_path);
}
@@ -1326,6 +1327,38 @@ class Uri
return null;
}
/**
* Check if this is a valid Grav extension
*
* @param $extension
* @return bool
*/
public function isValidExtension($extension)
{
$valid_page_types = implode('|', Grav::instance()['config']->get('system.pages.types'));
// Strip the file extension for valid page types
if (preg_match('/(' . $valid_page_types . ')/', $extension)) {
return true;
}
return false;
}
/**
* Allow overriding of any element (be careful!)
*
* @param $data
* @return Uri
*/
public function setUriProperties($data)
{
foreach (get_object_vars($this) as $property => $default) {
if (!array_key_exists($property, $data)) continue;
$this->{$property} = $data[$property]; // assign value to object
}
return $this;
}
/**
* Get the base URI with port if needed
*

View File

@@ -153,7 +153,7 @@ class User extends Data implements UserInterface
public function getMediaFolder()
{
return $this->blueprints->fields()['avatar']['destination'] ?? 'user://accounts/avatars';
return $this->blueprints()->fields()['avatar']['destination'] ?? 'user://accounts/avatars';
}
public function getMediaOrder()

View File

@@ -161,9 +161,9 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
* @param string|null $separator
* @return mixed
*/
public function value($name, $default = null, $separator = null)
public function getFormValue(string $name, $default = null, string $separator = null)
{
$value = parent::value($name, null, $separator);
$value = parent::getFormValue($name, null, $separator);
if ($name === 'avatar') {
return $this->parseFileProperty($value);
@@ -245,6 +245,7 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
*/
public function join($name, $value, $separator = null)
{
$separator = $separator ?? '.';
$old = $this->get($name, null, $separator);
if ($old !== null) {
if (!\is_array($old)) {

View File

@@ -102,8 +102,8 @@ class UserIndex extends FlexIndex
protected static function updateIndexData(array &$entry, array $data)
{
$entry['key'] = mb_strtolower($data['username'] ?? $data['email'] ?? $entry['key']);
$entry['email'] = mb_strtolower($data['email']) ?? null;
$entry['key'] = mb_strtolower($entry['key']);
$entry['email'] = isset($data['email']) ? mb_strtolower($data['email']) : null;
}
protected static function getIndexFile(FlexStorageInterface $storage)

View File

@@ -20,6 +20,10 @@ abstract class Utils
{
protected static $nonces = [];
protected const ROOTURL_REGEX = '{^((?:http[s]?:\/\/[^\/]+)|(?:\/\/[^\/]+))(.*)}';
// ^((?:http[s]?:)?[\/]?(?:\/))
/**
* Simple helper method to make getting a Grav URL easier
*
@@ -30,7 +34,7 @@ abstract class Utils
public static function url($input, $domain = false)
{
if (!trim((string)$input)) {
return false;
$input = '/';
}
if (Grav::instance()['config']->get('system.absolute_urls', false)) {
@@ -41,6 +45,12 @@ abstract class Utils
return $input;
}
/** @var Uri $uri */
$uri = Grav::instance()['uri'];
$root = $uri->rootUrl();
$input = Utils::replaceFirstOccurrence($root, '', $input);
$input = ltrim((string)$input, '/');
if (Utils::contains((string)$input, '://')) {
@@ -65,10 +75,9 @@ abstract class Utils
$resource = $input;
}
/** @var Uri $uri */
$uri = Grav::instance()['uri'];
return $resource ? rtrim($uri->rootUrl($domain), '/') . '/' . $resource : null;
return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
}
/**
@@ -777,22 +786,47 @@ abstract class Utils
*/
public static function normalizePath($path)
{
$root = strpos($path, '/') === 0 ? '/' : '';
$segments = explode('/', trim($path, '/'));
$ret = [];
foreach ($segments as $segment) {
if (($segment === '.') || $segment === '') {
continue;
}
if ($segment === '..') {
array_pop($ret);
} else {
$ret[] = $segment;
}
// Resolve any streams
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
if ($locator->isStream($path)) {
$path = $locator->findResource($path);
}
return $root . implode('/', $ret);
// Set root properly for any URLs
$root = '';
preg_match(self::ROOTURL_REGEX, $path, $matches);
if ($matches) {
$root = $matches[1];
$path = $matches[2];
}
// Strip off leading / to ensure explode is accurate
if (Utils::startsWith($path,'/')) {
$root .= '/';
$path = ltrim($path, '/');
}
// If there are any relative paths (..) handle those
if (Utils::contains($path, '..')) {
$segments = explode('/', trim($path, '/'));
$ret = [];
foreach ($segments as $segment) {
if (($segment === '.') || $segment === '') {
continue;
}
if ($segment === '..') {
array_pop($ret);
} else {
$ret[] = $segment;
}
}
$path = implode('/', $ret);
}
// Stick everything back together
$normalized = $root . $path;
return $normalized;
}
/**
@@ -1371,10 +1405,10 @@ abstract class Utils
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
$size = preg_replace('/[^0-9\.]/', '', $size);
if ($unit) {
return (int)((int)$size * (1024 ** stripos('bkmgtpezy', $unit[0])));
return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
} else {
return round($size);
}
return (int)$size;
}
/**

View File

@@ -13,7 +13,7 @@ use Grav\Common\Cache;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputOption;
class CacheCommand extends ConsoleCommand
class ClearCacheCommand extends ConsoleCommand
{
protected function configure()
{

View File

@@ -13,7 +13,7 @@ use Grav\Common\Cache;
use Grav\Common\Grav;
use Grav\Common\Composer;
use Grav\Common\GravTrait;
use Grav\Console\Cli\CacheCommand;
use Grav\Console\Cli\ClearCacheCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\ArrayInput;
@@ -107,7 +107,7 @@ trait ConsoleTrait
$all = ['--all' => true];
}
$command = new CacheCommand();
$command = new ClearCacheCommand();
$input = new ArrayInput($all);
return $command->run($input, $this->output);
}

View File

@@ -63,7 +63,7 @@ class Flex implements \Countable
*/
public function addDirectory(FlexDirectory $directory)
{
$this->types[$directory->getType()] = $directory;
$this->types[$directory->getFlexType()] = $directory;
return $this;
}

View File

@@ -87,7 +87,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
parent::__construct($entries);
if ($directory) {
$this->setFlexDirectory($directory)->setKey($directory->getType());
$this->setFlexDirectory($directory)->setKey($directory->getFlexType());
}
}
@@ -129,7 +129,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
*/
public function getFlexType(): string
{
return $this->_flexDirectory->getType();
return $this->_flexDirectory->getFlexType();
}
/**
@@ -245,7 +245,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
* {@inheritdoc}
* @see FlexCollectionInterface::render()
*/
public function render($layout = null, array $context = [])
public function render(string $layout = null, array $context = [])
{
if (null === $layout) {
$layout = 'default';

View File

@@ -91,8 +91,19 @@ class FlexDirectory implements FlexAuthorizeInterface
/**
* @return string
* @deprecated 1.6 Use ->getFlexType() method instead.
*/
public function getType(): string
{
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, use ->getFlexType() method instead', E_USER_DEPRECATED);
return $this->type;
}
/**
* @return string
*/
public function getFlexType(): string
{
return $this->type;
}
@@ -102,7 +113,7 @@ class FlexDirectory implements FlexAuthorizeInterface
*/
public function getTitle(): string
{
return $this->getBlueprintInternal()->get('title', ucfirst($this->getType()));
return $this->getBlueprintInternal()->get('title', ucfirst($this->getFlexType()));
}
/**
@@ -312,13 +323,13 @@ class FlexDirectory implements FlexAuthorizeInterface
if (Utils::isAdminPlugin()) {
$key = substr($key, 0, -1);
}
$cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getType() . $key, $timeout);
$cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getFlexType() . $key, $timeout);
} catch (\Exception $e) {
/** @var Debugger $debugger */
$debugger = Grav::instance()['debugger'];
$debugger->addException($e);
$cache = new MemoryCache('flex-objects-' . $this->getType());
$cache = new MemoryCache('flex-objects-' . $this->getFlexType());
}
// Disable cache key validation.

View File

@@ -45,7 +45,7 @@ class FlexForm implements FlexFormInterface
{
$this->name = $name;
$this->form = $form;
$uniqueId = $object->exists() ? $object->getStorageKey() : "{$object->getType()}:new";
$uniqueId = $object->exists() ? $object->getStorageKey() : "{$object->getFlexType()}:new";
$this->setObject($object);
$this->setId($this->getName());
$this->setUniqueId(md5($uniqueId));
@@ -73,7 +73,7 @@ class FlexForm implements FlexFormInterface
$object = $this->getObject();
$name = $this->name ?: 'object';
return "flex-{$object->getType()}-{$name}";
return "flex-{$object->getFlexType()}-{$name}";
}
/**
@@ -101,6 +101,26 @@ class FlexForm implements FlexFormInterface
return $value ?? $this->getObject()->value($name);
}
public function getDefaultValue(string $name)
{
return $this->object->getDefaultValue($name);
}
/**
* @return array
*/
public function getDefaultValues(): array
{
return $this->object->getDefaultValues();
}
/**
* @return string
*/
public function getFlexType(): string
{
return $this->object->getFlexType();
}
/**
* @return FlexObjectInterface
*/

View File

@@ -104,7 +104,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
*/
public function getFlexType(): string
{
return $this->_flexDirectory->getType();
return $this->_flexDirectory->getFlexType();
}
/**
@@ -171,7 +171,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
{
// Get storage keys for the objects.
$keys = [];
$type = $this->_flexDirectory->getType() . '.obj:';
$type = $this->_flexDirectory->getFlexType() . '.obj:';
foreach ($this->getEntries() as $key => $value) {
$keys[$key] = $value['flex_key'] ?? $type . $value['storage_key'];
@@ -191,7 +191,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
return $this;
}
$type = $keyField === 'flex_key' ? $this->_flexDirectory->getType() . '.obj:' : '';
$type = $keyField === 'flex_key' ? $this->_flexDirectory->getFlexType() . '.obj:' : '';
$entries = [];
foreach ($this->getEntries() as $key => $value) {
if (!isset($value['key'])) {
@@ -221,7 +221,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
* {@inheritdoc}
* @see FlexCollectionInterface::render()
*/
public function render($layout = null, array $context = [])
public function render(string $layout = null, array $context = [])
{
return $this->__call('render', [$layout, $context]);
}
@@ -583,11 +583,16 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
$keys = array_fill_keys(array_keys($entries), null);
$rows = $storage->readRows($keys);
$keyField = $storage->getKeyField();
// Go through all the updated objects and refresh their index data.
$updated = $added = [];
foreach ($rows as $key => $row) {
if (null !== $row) {
$entry = ['key' => $key] + $entries[$key];
if ($keyField !== 'storage_key' && isset($row[$keyField])) {
$entry['key'] = $row[$keyField];
}
static::updateIndexData($entry, $row);
if (isset($row['__error'])) {
$entry['__error'] = true;

View File

@@ -115,7 +115,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
*/
public function getFlexType(): string
{
return $this->_flexDirectory->getType();
return $this->_flexDirectory->getFlexType();
}
/**
@@ -186,7 +186,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
*/
public function getFlexKey(): string
{
return $this->_storage['flex_key'] ?? $this->_flexDirectory->getType() . '.obj:' . $this->getStorageKey();
return $this->_storage['flex_key'] ?? $this->_flexDirectory->getFlexType() . '.obj:' . $this->getStorageKey();
}
/**
@@ -357,7 +357,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
* {@inheritdoc}
* @see FlexObjectInterface::render()
*/
public function render($layout = null, array $context = [])
public function render(string $layout = null, array $context = [])
{
if (null === $layout) {
$layout = 'default';
@@ -510,7 +510,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
* {@inheritdoc}
* @see FlexObjectInterface::create()
*/
public function create($key = null)
public function create(string $key = null)
{
if ($key) {
$this->setStorageKey($key);
@@ -616,9 +616,50 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
/**
* {@inheritdoc}
* @see FlexObjectInterface::value()
* @see FlexObjectInterface::getDefaultValue()
*/
public function value($name, $default = null, $separator = null)
public function getDefaultValue(string $name, string $separator = null)
{
$separator = $separator ?: '.';
$path = explode($separator, $name) ?: [];
$offset = array_shift($path) ?? '';
$current = $this->getDefaultValues();
if (!isset($current[$offset])) {
return null;
}
$current = $current[$offset];
while ($path) {
$offset = array_shift($path);
if ((\is_array($current) || $current instanceof \ArrayAccess) && isset($current[$offset])) {
$current = $current[$offset];
} elseif (\is_object($current) && isset($current->{$offset})) {
$current = $current->{$offset};
} else {
return null;
}
};
return $current;
}
/**
* @return array
*/
public function getDefaultValues(): array
{
return $this->getBlueprint()->getDefaults();
}
/**
* {@inheritdoc}
* @see FlexObjectInterface::getFormValue()
*/
public function getFormValue(string $name, $default = null, string $separator = null)
{
if ($name === 'storage_key') {
return $this->getStorageKey();
@@ -630,6 +671,19 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
return $this->getNestedProperty($name, $default, $separator);
}
/**
* @param string $name
* @param mixed|null $default
* @param string|null $separator
* @return mixed
*
* @deprecated 1.6 Use ->getFormValue() method instead.
*/
public function value($name, $default = null, $separator = null)
{
return $this->getFormValue($name, $default, $separator);
}
/**
* Returns a string representation of this object.
*

View File

@@ -11,11 +11,8 @@ declare(strict_types=1);
namespace Grav\Framework\Flex\Interfaces;
use Grav\Framework\ContentBlock\ContentBlockInterface;
use Grav\Framework\ContentBlock\HtmlBlock;
use Grav\Framework\Flex\FlexDirectory;
use Twig\Error\LoaderError;
use Twig\Error\SyntaxError;
use Grav\Framework\Interfaces\RenderInterface;
/**
* Defines common interface shared with both Flex Objects and Collections.
@@ -23,7 +20,7 @@ use Twig\Error\SyntaxError;
* @used-by \Grav\Framework\Flex\FlexObject
* @since 1.6
*/
interface FlexCommonInterface
interface FlexCommonInterface extends RenderInterface
{
/**
* Get Flex Type of the object / collection.
@@ -64,22 +61,4 @@ interface FlexCommonInterface
* @return string Returns cache checksum.
*/
public function getCacheChecksum(): string;
/**
* Renders the object / collection.
*
* @example {% render object layout 'edit' with { limited: true } %}
* @example {% render collection layout 'list' %}
*
* @param string $layout Layout name.
* @param array $context Context given to the renderer.
*
* @return ContentBlockInterface|HtmlBlock Returns `HtmlBlock` containing the rendered output.
* @throws \Exception
* @throws \Throwable
* @throws LoaderError
* @throws SyntaxError
* @api
*/
public function render($layout = null, array $context = []);
}

View File

@@ -129,7 +129,7 @@ interface FlexObjectInterface extends FlexCommonInterface, NestedObjectInterface
* @throws \RuntimeException if object already exists.
* @api
*/
public function create($key = null);
public function create(string $key = null);
/**
* Save object into the storage.
@@ -173,12 +173,36 @@ interface FlexObjectInterface extends FlexCommonInterface, NestedObjectInterface
public function getForm(string $name = '', array $form = null);
/**
* Form field compatibility.
* Returns default value suitable to be used in a form for the given property.
*
* @param string $name Property name.
* @param mixed $default Default value.
* @param string $separator Optional nested property separator.
* @return mixed Returns value of the field.
* @see FlexObjectInterface::getForm()
*
* @param string $name Property name.
* @param string $separator Optional nested property separator.
*
* @return mixed|null Returns default value of the field, null if there is no default value.
*/
public function value($name, $default = null, $separator = null);
public function getDefaultValue(string $name, string $separator = null);
/**
* Returns default values suitable to be used in a form for the given property.
*
* @see FlexObjectInterface::getForm()
*
* @return array Returns default values.
*/
public function getDefaultValues(): array;
/**
* Returns raw value suitable to be used in a form for the given property.
*
* @see FlexObjectInterface::getForm()
*
* @param string $name Property name.
* @param mixed $default Default value.
* @param string $separator Optional nested property separator.
*
* @return mixed Returns value of the field.
*/
public function getFormValue(string $name, $default = null, string $separator = null);
}

View File

@@ -24,6 +24,11 @@ interface FlexStorageInterface
*/
public function __construct(array $options);
/**
* @return string
*/
public function getKeyField(): string;
/**
* Returns associated array of all existing storage keys with a timestamp.
*
@@ -40,6 +45,15 @@ interface FlexStorageInterface
*/
public function hasKey(string $key): bool;
/**
* Check if the key exists in the storage.
*
* @param string[] $keys Storage key of an object.
*
* @return bool[] Returns keys with `true` if the key exists in the storage, `false` otherwise.
*/
public function hasKeys(array $keys): array;
/**
* Create new rows into the storage.
*

View File

@@ -32,6 +32,31 @@ abstract class AbstractFilesystemStorage implements FlexStorageInterface
{
/** @var FileFormatterInterface */
protected $dataFormatter;
/** @var string */
protected $keyField = 'storage_key';
/**
* {@inheritdoc}
* @see FlexStorageInterface::hasKey()
*/
public function hasKeys(array $keys): array
{
$list = [];
foreach ($keys as $key) {
$list[$key] = $this->hasKey((string)$key);
}
return $list;
}
/**
* @return string
*/
public function getKeyField(): string
{
return $this->keyField;
}
protected function initDataFormatter($formatter): void
{

View File

@@ -71,7 +71,7 @@ class FolderStorage extends AbstractFilesystemStorage
*/
public function hasKey(string $key): bool
{
return $key && !strpos($key, '@@') && file_exists($this->getPathFromKey($key));
return $key && strpos($key, '@@') === false && file_exists($this->getPathFromKey($key));
}
/**
@@ -102,6 +102,7 @@ class FolderStorage extends AbstractFilesystemStorage
foreach ($rows as $key => $row) {
if (null === $row || (!\is_object($row) && !\is_array($row))) {
// Only load rows which haven't been loaded before.
$key = (string)$key;
if (!$this->hasKey($key)) {
$list[$key] = null;
} else {
@@ -129,6 +130,7 @@ class FolderStorage extends AbstractFilesystemStorage
{
$list = [];
foreach ($rows as $key => $row) {
$key = (string)$key;
if (!$this->hasKey($key)) {
$list[$key] = null;
} else {
@@ -149,6 +151,7 @@ class FolderStorage extends AbstractFilesystemStorage
{
$list = [];
foreach ($rows as $key => $row) {
$key = (string)$key;
if (!$this->hasKey($key)) {
$list[$key] = null;
} else {
@@ -175,6 +178,7 @@ class FolderStorage extends AbstractFilesystemStorage
{
$list = [];
foreach ($rows as $key => $row) {
$key = (string)$key;
if (strpos($key, '@@')) {
$key = $this->getNewKey();
}
@@ -456,6 +460,7 @@ class FolderStorage extends AbstractFilesystemStorage
$this->dataFolder = $options['folder'];
$this->prefixed = (bool)($options['prefixed'] ?? strpos($pattern, '/{KEY:2}/'));
$this->indexed = (bool)($options['indexed'] ?? false);
$this->keyField = $options['key'] ?? 'storage_key';
$pattern = preg_replace(['/{FOLDER}/', '/{KEY}/', '/{KEY:2}/'], ['%1$s', '%2$s', '%3$s'], $pattern);
$this->dataPattern = \dirname($pattern) . '/' . basename($pattern, $extension) . $extension;

View File

@@ -71,7 +71,11 @@ class SimpleStorage extends AbstractFilesystemStorage
*/
public function hasKey(string $key): bool
{
return $key && !strpos($key, '@@') && isset($this->data[$key]);
if (null === $this->data) {
$this->buildIndex();
}
return $key && strpos($key, '@@') === false && isset($this->data[$key]);
}
/**
@@ -80,6 +84,10 @@ class SimpleStorage extends AbstractFilesystemStorage
*/
public function createRows(array $rows): array
{
if (null === $this->data) {
$this->buildIndex();
}
$list = [];
foreach ($rows as $key => $row) {
$key = $this->getNewKey();
@@ -99,10 +107,15 @@ class SimpleStorage extends AbstractFilesystemStorage
*/
public function readRows(array $rows, array &$fetched = null): array
{
if (null === $this->data) {
$this->buildIndex();
}
$list = [];
foreach ($rows as $key => $row) {
if (null === $row || (!\is_object($row) && !\is_array($row))) {
// Only load rows which haven't been loaded before.
$key = (string)$key;
$list[$key] = $this->hasKey($key) ? $this->data[$key] : null;
if (null !== $fetched) {
$fetched[$key] = $list[$key];
@@ -122,8 +135,13 @@ class SimpleStorage extends AbstractFilesystemStorage
*/
public function updateRows(array $rows): array
{
if (null === $this->data) {
$this->buildIndex();
}
$list = [];
foreach ($rows as $key => $row) {
$key = (string)$key;
if ($this->hasKey($key)) {
$this->data[$key] = $list[$key] = $row;
}
@@ -142,8 +160,13 @@ class SimpleStorage extends AbstractFilesystemStorage
*/
public function deleteRows(array $rows): array
{
if (null === $this->data) {
$this->buildIndex();
}
$list = [];
foreach ($rows as $key => $row) {
$key = (string)$key;
if ($this->hasKey($key)) {
unset($this->data[$key]);
$list[$key] = $row;
@@ -163,6 +186,10 @@ class SimpleStorage extends AbstractFilesystemStorage
*/
public function replaceRows(array $rows): array
{
if (null === $this->data) {
$this->buildIndex();
}
$list = [];
foreach ($rows as $key => $row) {
$this->data[$key] = $list[$key] = $row;
@@ -181,6 +208,10 @@ class SimpleStorage extends AbstractFilesystemStorage
*/
public function renameRow(string $src, string $dst): bool
{
if (null === $this->data) {
$this->buildIndex();
}
if ($this->hasKey($dst)) {
throw new \RuntimeException("Cannot rename object: key '{$dst}' is already taken");
}
@@ -218,6 +249,10 @@ class SimpleStorage extends AbstractFilesystemStorage
protected function save() : void
{
if (null === $this->data) {
$this->buildIndex();
}
try {
$file = $this->getFile($this->getStoragePath());
$file->save($this->data);

View File

@@ -9,6 +9,7 @@ namespace Grav\Framework\Flex\Traits;
* @license MIT License; see LICENSE file for details.
*/
use Grav\Common\Cache;
use Grav\Common\Config\Config;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
@@ -312,6 +313,13 @@ trait FlexMediaTrait
return $file && file_exists($file) ? MediumFactory::fromFile($file) : null;
}
/**
* @return Cache
*/
protected function getMediaCache()
{
return $this->getCache('object');
}
protected function offsetLoad_media()
{

View File

@@ -390,11 +390,11 @@ class FormFlash implements \JsonSerializable
*/
protected function addFileInternal(?string $field, string $name, array $data, array $crop = null): void
{
$field = $field ?: 'undefined';
if (!isset($this->files[$field])) {
$this->files[$field] = [];
}
$field = $field ?: 'undefined';
$oldUpload = $this->files[$field][$name] ?? null;
if ($crop) {

View File

@@ -16,7 +16,6 @@ use Grav\Common\Form\FormFlash;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Grav\Framework\Form\Interfaces\FormInterface;
use Grav\Framework\Session\Session;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface;
@@ -113,6 +112,42 @@ trait FormTrait
return $this->data[$name] ?? null;
}
public function getDefaultValue(string $name)
{
$path = explode('.', $name) ?: [];
$offset = array_shift($path) ?? '';
$current = $this->getDefaultValues();
if (!isset($current[$offset])) {
return null;
}
$current = $current[$offset];
while ($path) {
$offset = array_shift($path);
if ((\is_array($current) || $current instanceof \ArrayAccess) && isset($current[$offset])) {
$current = $current[$offset];
} elseif (\is_object($current) && isset($current->{$offset})) {
$current = $current->{$offset};
} else {
return null;
}
};
return $current;
}
/**
* @return array
*/
public function getDefaultValues(): array
{
return $this->getBlueprint()->getDefaults();
}
/**
* @param ServerRequestInterface $request
* @return FormInterface|$this
@@ -273,24 +308,30 @@ trait FormTrait
public function getFlash(): FormFlash
{
if (null === $this->flash) {
/** @var Grav $grav */
$grav = Grav::instance();
$user = $grav['user'];
$id = null;
$rememberState = $this->getBlueprint()->get('form/remember_state');
if ($rememberState === 'user') {
$id = $user->username;
$user = $grav['user'] ?? null;
if (isset($user)) {
$rememberState = $this->getBlueprint()->get('form/remember_state');
if ($rememberState === 'user') {
$id = $user->username;
}
}
// By default store flash by the session id.
if (null === $id) {
/** @var Session $session */
$session = $grav['session'];
$id = $session->getId();
}
// Session Required for flash form
$session = $grav['session'] ?? null;
if (isset($session)) {
// By default store flash by the session id.
if (null === $id) {
$id = $session->getId();
}
$this->flash = new FormFlash($id, $this->getUniqueId(), $this->getName());
$this->flash->setUrl($grav['uri']->url)->setUser($user);
$this->flash = new FormFlash($id, $this->getUniqueId(), $this->getName());
$this->flash->setUrl($grav['uri']->url)->setUser($user);
}
}
return $this->flash;

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* @package Grav\Framework\Interfaces
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Interfaces;
use Grav\Framework\ContentBlock\ContentBlockInterface;
use Grav\Framework\ContentBlock\HtmlBlock;
/**
* Defines common interface to render any object.
*
* @used-by \Grav\Framework\Flex\FlexObject
* @since 1.6
*/
interface RenderInterface
{
/**
* Renders the object.
*
* @example $block = $object->render('custom', ['variable' => 'value']);
* @example {% render object layout 'custom' with { variable: 'value' } %}
*
* @param string|null $layout Layout to be used.
* @param array|null $context Extra context given to the renderer.
*
* @return ContentBlockInterface|HtmlBlock Returns `HtmlBlock` containing the rendered output.
* @api
*/
public function render(string $layout = null, array $context = []);
}

View File

@@ -95,7 +95,7 @@ trait ArrayPropertyTrait
*/
protected function getElements()
{
return $this->_elements;
return array_filter($this->_elements, function ($val) { return $val !== null; });
}
/**

View File

@@ -183,7 +183,10 @@ trait ObjectPropertyTrait
$elements = [];
foreach ($properties as $offset => $value) {
$elements[$offset] = $this->offsetSerialize($offset, $value);
$serialized = $this->offsetSerialize($offset, $value);
if ($serialized !== null) {
$elements[$offset] = $this->offsetSerialize($offset, $value);
}
}
return $elements;

View File

@@ -15,6 +15,9 @@ use Psr\Http\Message\ServerRequestInterface;
class NotFoundException extends RequestException
{
/** @var ServerRequestInterface */
private $request;
/**
* NotFoundException constructor.
* @param ServerRequestInterface $request
@@ -28,4 +31,9 @@ class NotFoundException extends RequestException
parent::__construct($request, 'Not Found', 404, $previous);
}
}
public function getRequest(): ServerRequestInterface
{
return $this->request;
}
}

View File

@@ -19,7 +19,7 @@ use Psr\Http\Server\RequestHandlerInterface;
class Exceptions implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
try {
return $handler->handle($request);
@@ -38,7 +38,7 @@ class Exceptions implements MiddlewareInterface
/** @var string $json */
$json = json_encode($response);
return new Response($exception->getCode() ?: 500, [], $json);
return new Response($exception->getCode() ?: 500, ['Content-Type' => 'application/json'], $json);
}
}
}

View File

@@ -53,7 +53,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"type":"text\/css",
"rel":"stylesheet"
},
"timestamp":null,
"modified":false,
"query":""
}
@@ -82,7 +81,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"attributes":[
],
"timestamp":null,
"modified":false,
"query":""
}
@@ -113,7 +111,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"type":"text\/css",
"rel":"stylesheet"
},
"timestamp":null,
"modified":false,
"query":""
}
@@ -144,8 +141,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"type":"text\/css",
"rel":"stylesheet"
},
"timestamp":null,
"modified":null,
"query":""
}
}';
@@ -178,7 +173,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"position":"pipeline",
"priority":10,
"attributes":[],
"timestamp":null,
"modified":false,
"query":""
}
@@ -211,7 +205,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"type": "text/css",
"rel": "stylesheet"
},
"timestamp": null,
"modified": false,
"query": ""
}
@@ -242,7 +235,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"position": "pipeline",
"priority": 10,
"attributes": [],
"timestamp": null,
"modified": false,
"query": ""
}
@@ -272,7 +264,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"attributes": {
"loading": "async"
},
"timestamp": null,
"modified": false,
"query": ""
}
@@ -301,7 +292,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"attributes": {
"loading": "defer"
},
"timestamp": null,
"modified": false,
"query": ""
}
@@ -410,7 +400,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"rel":"stylesheet",
"loading":"async"
},
"timestamp":null,
"modified":false,
"query":""
}
@@ -439,7 +428,6 @@ class AssetsTest extends \Codeception\TestCase\Test
"attributes": {
"loading": "defer"
},
"timestamp": null,
"modified": false,
"query": ""
}

View File

@@ -573,7 +573,7 @@ class UriTest extends \Codeception\TestCase\Test
'rootUrl' => 'http://localhost',
'extension' => 'html',
'addNonce' => 'http://localhost/a-page.html/nonce:{{nonce}}',
'__toString' => 'http://localhost/a-page', // FIXME <-
'__toString' => 'http://localhost/a-page.html',
],
'http://localhost/a-page.json' => [
'scheme' => 'http://',
@@ -596,7 +596,7 @@ class UriTest extends \Codeception\TestCase\Test
'rootUrl' => 'http://localhost',
'extension' => 'json',
'addNonce' => 'http://localhost/a-page.json/nonce:{{nonce}}',
'__toString' => 'http://localhost/a-page', // FIX ME <-
'__toString' => 'http://localhost/a-page.json',
],
'http://localhost/admin/ajax.json/task:getnewsfeed' => [
'scheme' => 'http://',
@@ -619,7 +619,7 @@ class UriTest extends \Codeception\TestCase\Test
'rootUrl' => 'http://localhost',
'extension' => 'json',
'addNonce' => 'http://localhost/admin/ajax.json/task:getnewsfeed/nonce:{{nonce}}',
'__toString' => 'http://localhost/admin/ajax/task:getnewsfeed',
'__toString' => 'http://localhost/admin/ajax.json/task:getnewsfeed',
],
'http://localhost/grav/admin/media.json/route:L1VzZXJzL3JodWsvd29ya3NwYWNlL2dyYXYtZGVtby1zYW1wbGVyL3VzZXIvYXNzZXRzL3FRMXB4Vk1ERTNJZzh5Ni5qcGc=/task:removeFileFromBlueprint/proute:/blueprint:Y29uZmlnL2RldGFpbHM=/type:config/field:deep.nested.custom_file/path:dXNlci9hc3NldHMvcVExcHhWTURFM0lnOHk2LmpwZw==' => [
'scheme' => 'http://',
@@ -642,7 +642,7 @@ class UriTest extends \Codeception\TestCase\Test
'rootUrl' => 'http://localhost',
'extension' => 'json',
'addNonce' => 'http://localhost/grav/admin/media.json/route:L1VzZXJzL3JodWsvd29ya3NwYWNlL2dyYXYtZGVtby1zYW1wbGVyL3VzZXIvYXNzZXRzL3FRMXB4Vk1ERTNJZzh5Ni5qcGc=/task:removeFileFromBlueprint/proute:/blueprint:Y29uZmlnL2RldGFpbHM=/type:config/field:deep.nested.custom_file/path:dXNlci9hc3NldHMvcVExcHhWTURFM0lnOHk2LmpwZw==/nonce:{{nonce}}',
'__toString' => 'http://localhost/grav/admin/media/route:L1VzZXJzL3JodWsvd29ya3NwYWNlL2dyYXYtZGVtby1zYW1wbGVyL3VzZXIvYXNzZXRzL3FRMXB4Vk1ERTNJZzh5Ni5qcGc=/task:removeFileFromBlueprint/proute:/blueprint:Y29uZmlnL2RldGFpbHM=/type:config/field:deep.nested.custom_file/path:dXNlci9hc3NldHMvcVExcHhWTURFM0lnOHk2LmpwZw==', // FIXME <-
'__toString' => 'http://localhost/grav/admin/media.json/route:L1VzZXJzL3JodWsvd29ya3NwYWNlL2dyYXYtZGVtby1zYW1wbGVyL3VzZXIvYXNzZXRzL3FRMXB4Vk1ERTNJZzh5Ni5qcGc=/task:removeFileFromBlueprint/proute:/blueprint:Y29uZmlnL2RldGFpbHM=/type:config/field:deep.nested.custom_file/path:dXNlci9hc3NldHMvcVExcHhWTURFM0lnOHk2LmpwZw==',
],
'http://localhost/a-page.foo' => [
'scheme' => 'http://',
@@ -1121,6 +1121,24 @@ class UriTest extends \Codeception\TestCase\Test
];
$this->assertSame('http://foo:bar@localhost:8080/test?x=2#xxx', Uri::buildUrl($parsed_url));
/** @var Uri $uri */
$uri = Grav::instance()['uri'];
$uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.html', '/subdir')->init();
$this->assertSame('https://testing.dev/subdir/path1/path2/file.html', Uri::buildUrl($uri->toArray()));
$uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.foo', '/subdir')->init();
$this->assertSame('https://testing.dev/subdir/path1/path2/file.foo', Uri::buildUrl($uri->toArray()));
$uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.html', '/subdir/path1')->init();
$this->assertSame('https://testing.dev/subdir/path1/path2/file.html', Uri::buildUrl($uri->toArray()));
$uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.html/foo:blah/bang:boom', '/subdir')->init();
$this->assertSame('https://testing.dev/subdir/path1/path2/file.html/foo:blah/bang:boom', Uri::buildUrl($uri->toArray()));
$uri->initializeWithUrlAndRootPath('https://testing.dev/subdir/path1/path2/file.html/foo:blah/bang:boom?fig=something', '/subdir')->init();
$this->assertSame('https://testing.dev/subdir/path1/path2/file.html/foo:blah/bang:boom?fig=something', Uri::buildUrl($uri->toArray()));
}
public function testConvertUrl()

View File

@@ -2,6 +2,7 @@
use Codeception\Util\Fixtures;
use Grav\Common\Grav;
use Grav\Common\Uri;
use Grav\Common\Utils;
/**
@@ -12,10 +13,14 @@ class UtilsTest extends \Codeception\TestCase\Test
/** @var Grav $grav */
protected $grav;
/** @var Uri $uri */
protected $uri;
protected function _before()
{
$grav = Fixtures::get('grav');
$this->grav = $grav();
$this->uri = $this->grav['uri'];
}
protected function _after()
@@ -146,6 +151,8 @@ class UtilsTest extends \Codeception\TestCase\Test
public function testTruncateHtml()
{
$this->assertEquals('T...', Utils::truncateHtml('This is a string to truncate', 1));
$this->assertEquals('This is...', Utils::truncateHtml('This is a string to truncate', 7));
$this->assertEquals('<p>T...</p>', Utils::truncateHtml('<p>This is a string to truncate</p>', 1));
$this->assertEquals('<p>This...</p>', Utils::truncateHtml('<p>This is a string to truncate</p>', 4));
$this->assertEquals('<p>This is a...</p>', Utils::truncateHtml('<p>This is a string to truncate</p>', 10));
@@ -157,6 +164,8 @@ class UtilsTest extends \Codeception\TestCase\Test
public function testSafeTruncateHtml()
{
$this->assertEquals('This...', Utils::safeTruncateHtml('This is a string to truncate', 1));
$this->assertEquals('This is a...', Utils::safeTruncateHtml('This is a string to truncate', 3));
$this->assertEquals('<p>This...</p>', Utils::safeTruncateHtml('<p>This is a string to truncate</p>', 1));
$this->assertEquals('<p>This is...</p>', Utils::safeTruncateHtml('<p>This is a string to truncate</p>', 2));
$this->assertEquals('<p>This is a string to...</p>', Utils::safeTruncateHtml('<p>This is a string to truncate</p>', 5));
@@ -215,7 +224,19 @@ class UtilsTest extends \Codeception\TestCase\Test
$this->assertEquals('test', Utils::normalizePath('../test'));
$this->assertEquals('/test', Utils::normalizePath('/../test'));
$this->assertEquals('/test2', Utils::normalizePath('/test/../test2'));
$this->assertEquals('/test/test2', Utils::normalizePath('/test/./test2'));
$this->assertEquals('/test3', Utils::normalizePath('/test/../test2/../test3'));
$this->assertEquals('//cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css', Utils::normalizePath('//cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css'));
$this->assertEquals('//use.fontawesome.com/releases/v5.8.1/css/all.css', Utils::normalizePath('//use.fontawesome.com/releases/v5.8.1/css/all.css'));
$this->assertEquals('//use.fontawesome.com/releases/v5.8.1/webfonts/fa-brands-400.eot', Utils::normalizePath('//use.fontawesome.com/releases/v5.8.1/css/../webfonts/fa-brands-400.eot'));
$this->assertEquals('http://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css', Utils::normalizePath('http://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css'));
$this->assertEquals('http://use.fontawesome.com/releases/v5.8.1/css/all.css', Utils::normalizePath('http://use.fontawesome.com/releases/v5.8.1/css/all.css'));
$this->assertEquals('http://use.fontawesome.com/releases/v5.8.1/webfonts/fa-brands-400.eot', Utils::normalizePath('http://use.fontawesome.com/releases/v5.8.1/css/../webfonts/fa-brands-400.eot'));
$this->assertEquals('https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css', Utils::normalizePath('https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css'));
$this->assertEquals('https://use.fontawesome.com/releases/v5.8.1/css/all.css', Utils::normalizePath('https://use.fontawesome.com/releases/v5.8.1/css/all.css'));
$this->assertEquals('https://use.fontawesome.com/releases/v5.8.1/webfonts/fa-brands-400.eot', Utils::normalizePath('https://use.fontawesome.com/releases/v5.8.1/css/../webfonts/fa-brands-400.eot'));
}
public function testIsFunctionDisabled()
@@ -353,4 +374,65 @@ class UtilsTest extends \Codeception\TestCase\Test
{
$this->assertTrue(Utils::verifyNonce(Utils::getNonce('test-action'), 'test-action'));
}
public function testUrl()
{
$this->uri->initializeWithUrl('http://testing.dev/path1/path2')->init();
$this->assertSame('http://testing.dev/', Utils::url('/', true));
$this->assertSame('http://testing.dev/', Utils::url('', true));
$this->assertSame('http://testing.dev/path1', Utils::url('/path1', true));
$this->assertSame('/', Utils::url('/'));
$this->assertSame('/', Utils::url(''));
$this->assertSame('/path1', Utils::url('/path1'));
$this->assertSame('/path1/path2', Utils::url('/path1/path2'));
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true));
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true));
$this->assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
$this->assertSame('/foobar.jpg', Utils::url('/foobar.jpg'));
$this->assertSame('/foobar.jpg', Utils::url('foobar.jpg'));
$this->assertSame('/path1/foobar.jpg', Utils::url('/path1/foobar.jpg'));
$this->assertSame('/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg'));
}
public function testUrlWithRoot()
{
$this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/path1/path2', '/subdir')->init();
$this->assertSame('http://testing.dev/subdir/', Utils::url('/', true));
$this->assertSame('http://testing.dev/subdir/', Utils::url('', true));
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/path1', true));
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true));
$this->assertSame('/subdir/', Utils::url('/'));
$this->assertSame('/subdir/', Utils::url(''));
$this->assertSame('/subdir/path1', Utils::url('/path1'));
$this->assertSame('/subdir/path1/path2', Utils::url('/path1/path2'));
$this->assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2'));
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true));
$this->assertSame('/subdir/foobar.jpg', Utils::url('/foobar.jpg'));
$this->assertSame('/subdir/foobar.jpg', Utils::url('foobar.jpg'));
$this->assertSame('/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg'));
$this->assertSame('/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg'));
$this->assertSame('/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg'));
}
public function testUrlWithStreams()
{
}
public function testUrlwithExternals()
{
$this->uri->initializeWithUrl('http://testing.dev/path1/path2')->init();
$this->assertSame('http://foo.com', Utils::url('http://foo.com'));
$this->assertSame('https://foo.com', Utils::url('https://foo.com'));
$this->assertSame('//foo.com', Utils::url('//foo.com'));
$this->assertSame('//foo.com?param=x', Utils::url('//foo.com?param=x'));
}
}

View File

@@ -41,5 +41,5 @@ debugger:
shutdown:
close_connection: true
gpm:
releases: testing
releases: stable
verify_peer: true