mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
320 Commits
1.6.14
...
1.7.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dafa2a207 | ||
|
|
5253aa6aef | ||
|
|
1b34530a4a | ||
|
|
ae41b6f5ff | ||
|
|
4d09a345a4 | ||
|
|
8ac3451fbc | ||
|
|
15f1f2a03d | ||
|
|
ffa9ef6a7e | ||
|
|
a75c0cbe62 | ||
|
|
c795ead402 | ||
|
|
91270c9c66 | ||
|
|
9259c2f660 | ||
|
|
138d1e93e3 | ||
|
|
5db9f16174 | ||
|
|
342eac1047 | ||
|
|
88c859fde2 | ||
|
|
8b5bce0b6d | ||
|
|
09c4cf66f5 | ||
|
|
46b859b8df | ||
|
|
a7c23c58e5 | ||
|
|
d206711354 | ||
|
|
0007100a97 | ||
|
|
3d999501a0 | ||
|
|
52f83775e3 | ||
|
|
a35cf5a830 | ||
|
|
39837d0826 | ||
|
|
66bd6a4046 | ||
|
|
4e9d3395e0 | ||
|
|
248c7764f0 | ||
|
|
f72eb1b002 | ||
|
|
25caa5138a | ||
|
|
dffb227df6 | ||
|
|
7c161d5cbc | ||
|
|
7b2313ef0b | ||
|
|
1ad2a3f212 | ||
|
|
1f2363b623 | ||
|
|
d1c069c2ee | ||
|
|
67e772a5ce | ||
|
|
59bb167bd4 | ||
|
|
5c9eb1cdb8 | ||
|
|
94a54d2a82 | ||
|
|
f201d48112 | ||
|
|
df51b64b35 | ||
|
|
93ff915737 | ||
|
|
e30ab9a043 | ||
|
|
8e59a08f9e | ||
|
|
5a874006b4 | ||
|
|
5d639cc633 | ||
|
|
ab7038b49e | ||
|
|
651b354d3e | ||
|
|
90c2079529 | ||
|
|
fa064301a2 | ||
|
|
5424047d02 | ||
|
|
ce2b80aeb9 | ||
|
|
e91ae5542d | ||
|
|
922f263005 | ||
|
|
01af81a0a6 | ||
|
|
bd8c274f38 | ||
|
|
05fb69daa7 | ||
|
|
21f95eba53 | ||
|
|
4ef56054ee | ||
|
|
bec9292a5c | ||
|
|
0fa6328816 | ||
|
|
b30240c340 | ||
|
|
59eb3b4cb2 | ||
|
|
5fa047c1b7 | ||
|
|
30481da51d | ||
|
|
beb8f09a9d | ||
|
|
603bb6c878 | ||
|
|
dac3e57fd4 | ||
|
|
b601d2c8fd | ||
|
|
ab75201f11 | ||
|
|
45b1b0a2ef | ||
|
|
ca2f657c98 | ||
|
|
5c33882f5b | ||
|
|
0f85214693 | ||
|
|
4570c00041 | ||
|
|
4a0c8846e2 | ||
|
|
41e51cd86d | ||
|
|
e2ed3098a3 | ||
|
|
4e9ca82a0f | ||
|
|
f032f310b5 | ||
|
|
6b665b112c | ||
|
|
d9dbe5520d | ||
|
|
5b8674122a | ||
|
|
2e245cd36f | ||
|
|
e36a2ea1b0 | ||
|
|
53b7c95b0d | ||
|
|
46975cca22 | ||
|
|
a418acc32b | ||
|
|
eeb35fc521 | ||
|
|
90ca9f9d49 | ||
|
|
ba267a389e | ||
|
|
0ab99806db | ||
|
|
c300a3b8f8 | ||
|
|
8480fb68ac | ||
|
|
8e82056afa | ||
|
|
3e34d54b9a | ||
|
|
9ad7b208ba | ||
|
|
1fa62d2bdc | ||
|
|
b659c56aec | ||
|
|
3bd02b95fe | ||
|
|
9e5ad84a48 | ||
|
|
40a6c4bf72 | ||
|
|
22acffac5c | ||
|
|
ede749821d | ||
|
|
f26e518c03 | ||
|
|
0f6a517589 | ||
|
|
3530f4fdef | ||
|
|
b92476d40d | ||
|
|
0d0bb2c229 | ||
|
|
de59bad0f8 | ||
|
|
72ad49610c | ||
|
|
dcd1f3b10d | ||
|
|
52cf554ea2 | ||
|
|
f30f6485ba | ||
|
|
dd8b503aa0 | ||
|
|
dab30673e0 | ||
|
|
13689c2065 | ||
|
|
6e23627f26 | ||
|
|
7db85cc79c | ||
|
|
9b6f218f33 | ||
|
|
829da9ee3a | ||
|
|
033b54104e | ||
|
|
e5cedd074b | ||
|
|
a6741cb761 | ||
|
|
8cbc2a27cd | ||
|
|
5f1639dc63 | ||
|
|
ed87faad92 | ||
|
|
8d8b803e66 | ||
|
|
e4ed00d84a | ||
|
|
20b9ca56fa | ||
|
|
040c34d693 | ||
|
|
8942aa8afc | ||
|
|
256cbe3f12 | ||
|
|
8b31ee173e | ||
|
|
182970eb78 | ||
|
|
9ed3da3df2 | ||
|
|
14eaa4d00a | ||
|
|
e134e3dbd9 | ||
|
|
5bfb168cd7 | ||
|
|
5aef09a410 | ||
|
|
76732ab671 | ||
|
|
f54d9af758 | ||
|
|
f883191d99 | ||
|
|
de5ead78d1 | ||
|
|
44bbdf7e39 | ||
|
|
4b4eedf467 | ||
|
|
bb477fd3b1 | ||
|
|
758e316a65 | ||
|
|
2c38e24d00 | ||
|
|
ca24e63d22 | ||
|
|
f1909d80db | ||
|
|
7718dd7e98 | ||
|
|
cc66070e85 | ||
|
|
bbdc54b406 | ||
|
|
c013f63b26 | ||
|
|
aa007badb5 | ||
|
|
bb2e7a720b | ||
|
|
c36e6abd66 | ||
|
|
c2b1142b7a | ||
|
|
e03fb200a6 | ||
|
|
a964a34a6f | ||
|
|
1c28fd4c4c | ||
|
|
e8529e7d0b | ||
|
|
a6032af594 | ||
|
|
ea49415e14 | ||
|
|
7b26022f9f | ||
|
|
443fecfeb6 | ||
|
|
c3324e3702 | ||
|
|
9e60408769 | ||
|
|
3737bc9371 | ||
|
|
3d2360c995 | ||
|
|
08b8505b6d | ||
|
|
9607a99a7d | ||
|
|
7e63935001 | ||
|
|
3d767a4d25 | ||
|
|
ee53e1be6e | ||
|
|
676924cce5 | ||
|
|
36a3a95ed9 | ||
|
|
95c58c8361 | ||
|
|
66c17a8f53 | ||
|
|
7172da8ed6 | ||
|
|
c55ea919ef | ||
|
|
53216631a6 | ||
|
|
69d6b52a0e | ||
|
|
ea1e0a76c1 | ||
|
|
833fe8b729 | ||
|
|
23d508b390 | ||
|
|
e8b24479b9 | ||
|
|
1485c23aba | ||
|
|
d43357f366 | ||
|
|
c8e5aa05f9 | ||
|
|
04ccce1f67 | ||
|
|
5826821895 | ||
|
|
3ed341304b | ||
|
|
025e73affd | ||
|
|
5635ba2bb7 | ||
|
|
95637a243c | ||
|
|
cd417a1509 | ||
|
|
631ae3d3d5 | ||
|
|
7c34224304 | ||
|
|
6062e47377 | ||
|
|
a94abb4fb2 | ||
|
|
8ab317b49a | ||
|
|
44dda3d607 | ||
|
|
75ed986437 | ||
|
|
3ac785b9ce | ||
|
|
de367e1558 | ||
|
|
4fead303d1 | ||
|
|
41898af46f | ||
|
|
fe8833876c | ||
|
|
e116998914 | ||
|
|
053f96dec1 | ||
|
|
94494c3c96 | ||
|
|
5afae3c3f2 | ||
|
|
eed3d84a10 | ||
|
|
e7b996104f | ||
|
|
3f176c1924 | ||
|
|
91d20d8840 | ||
|
|
6998505e3c | ||
|
|
ffa2e0a6f6 | ||
|
|
b54e4fb71b | ||
|
|
8f391d327a | ||
|
|
e0e29f468b | ||
|
|
89b9cc5367 | ||
|
|
f6c30cbeae | ||
|
|
cca7b6b1d4 | ||
|
|
c765787102 | ||
|
|
dff3872b43 | ||
|
|
3c91cea232 | ||
|
|
a2e9c013ad | ||
|
|
dd82ab45bc | ||
|
|
46b710b435 | ||
|
|
a6d3e1ee8e | ||
|
|
2b29b17044 | ||
|
|
5316f0f28c | ||
|
|
ac4d6cc8d0 | ||
|
|
565947e074 | ||
|
|
25d1767e6c | ||
|
|
c079f9b95b | ||
|
|
20cfb45c14 | ||
|
|
5314558a8e | ||
|
|
02b93d510a | ||
|
|
8dfb0ca993 | ||
|
|
574df5cf80 | ||
|
|
34dcd8c346 | ||
|
|
5079077e1d | ||
|
|
1f120a0127 | ||
|
|
fe05c9b87b | ||
|
|
f795a634b5 | ||
|
|
888b93926c | ||
|
|
687f29f912 | ||
|
|
273bc9d970 | ||
|
|
d593d5a392 | ||
|
|
18fb688cde | ||
|
|
d8fccb0edd | ||
|
|
5843347c46 | ||
|
|
fedb0625b8 | ||
|
|
72d012e401 | ||
|
|
8d77b50055 | ||
|
|
42eefbd34c | ||
|
|
e5e5bf1bd8 | ||
|
|
01ec334127 | ||
|
|
916469a903 | ||
|
|
55f205c801 | ||
|
|
8a3fc57cd8 | ||
|
|
6cd1cca4fc | ||
|
|
1c9926ff38 | ||
|
|
638477b06d | ||
|
|
7e3ca73b0e | ||
|
|
57a3a20868 | ||
|
|
4e45c5be95 | ||
|
|
ca89156c4f | ||
|
|
dc5390b3dc | ||
|
|
0e8c7eae99 | ||
|
|
115bdb7e10 | ||
|
|
9738c55633 | ||
|
|
23921c1a35 | ||
|
|
1f3547b15b | ||
|
|
6974a24669 | ||
|
|
2cf35ec2c7 | ||
|
|
d21bb6b8c7 | ||
|
|
9b8b480c8c | ||
|
|
ac654d56d0 | ||
|
|
db9e1a197e | ||
|
|
19bb9a6966 | ||
|
|
b7a1c7b72a | ||
|
|
042486bc73 | ||
|
|
42cc1c6b01 | ||
|
|
407d2c8323 | ||
|
|
8ab14c0639 | ||
|
|
48c281024f | ||
|
|
f7e1bec0cf | ||
|
|
41c7973fc7 | ||
|
|
bc93e70d11 | ||
|
|
320ab41435 | ||
|
|
7213c393fd | ||
|
|
6caadc8396 | ||
|
|
e0d1385061 | ||
|
|
e1eed973a2 | ||
|
|
f4645fc77e | ||
|
|
136ec07450 | ||
|
|
e01605de55 | ||
|
|
5cc48c4e01 | ||
|
|
5e2545c606 | ||
|
|
0fd2627cea | ||
|
|
cfcd955cc2 | ||
|
|
7d59b69709 | ||
|
|
e8eb1d9a44 | ||
|
|
2117748f18 | ||
|
|
e76733e8c3 | ||
|
|
2da5f90e9c | ||
|
|
81b100cd3b | ||
|
|
19be8f5104 | ||
|
|
ad64a9d857 | ||
|
|
331f416e36 | ||
|
|
3a48c79b21 | ||
|
|
0c66de25c4 | ||
|
|
0bd227c22d |
163
CHANGELOG.md
163
CHANGELOG.md
@@ -1,3 +1,166 @@
|
||||
# v1.7.0-beta.10
|
||||
## 10/03/2019
|
||||
|
||||
1. [](#new)
|
||||
1. [](#improved)
|
||||
* Flex: Removed extra exists check when creating object (messes up "non-existing" pages)
|
||||
* Support customizable null character replacement in `CSVFormatter::decode()`
|
||||
1. [](#bugfix)
|
||||
* Fixed wrong Grav param separator when using `Route` class
|
||||
* Fixed Flex User Avatar not fully backwards compatible with old user
|
||||
* Grav 1.7: Fixed prev/next page missing pages if pagination was turned on in page header
|
||||
* Grav 1.7: Reverted setting language for every page during initialization
|
||||
* Grav 1.7: Fixed numeric language inconsistencies
|
||||
|
||||
# v1.7.0-beta.9
|
||||
## 09/26/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added a new `{% cache %}` Twig tag eliminating need for `twigcache` extension.
|
||||
1. [](#improved)
|
||||
* Improved blueprint initialization in Flex Objects (fixes content aware fields)
|
||||
* Improved Flex FolderStorage class to better hide storage specific logic
|
||||
* Exception will output a badly formatted line in `CsvFormatter::decode()`
|
||||
1. [](#bugfix)
|
||||
* Fixed error when activating Flex Accounts in GRAV system configuration (PHP 7.1)
|
||||
* Fixed Grav parameter handling in `RouteFactory::createFromString()`
|
||||
|
||||
# v1.7.0-beta.8
|
||||
## 09/19/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added new `Security::sanitizeSVG()` function
|
||||
* Backwards compatibility break: `FlexStorageInterface::getStoragePath()` and `getMediaPath()` can now return null
|
||||
1. [](#improved)
|
||||
* Several FlexObject loading improvements
|
||||
* Added `bin/grav page-system-validator [-r|--record] [-c|--check]` to test Flex Pages
|
||||
* Improved language support for `Route` class
|
||||
1. [](#bugfix)
|
||||
* Regression: Fixed language fallback
|
||||
* Regression: Fixed translations when language code is used for non-language purposes
|
||||
* Regression: Allow SVG avatar images for users
|
||||
* Fixed error in `Session::getFlashObject()` if Flex Form is being used
|
||||
* Fixed broken Twig `dump()`
|
||||
* Fixed `Page::modular()` and `Page::modularTwig()` returning `null` for folders and other non-initialized pages
|
||||
* Fixed 404 error when you click to non-routable menu item with children: redirect to the first child instead
|
||||
* Fixed wrong `Pages::dispatch()` calls (with redirect) when we really meant to call `Pages::find()`
|
||||
* Fixed avatars not being displayed with flex users [#2431](https://github.com/getgrav/grav/issues/2431)
|
||||
* Fixed initial Flex Object state when creating a new objects in a form
|
||||
|
||||
# v1.7.0-beta.7
|
||||
## 08/30/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Improved language support
|
||||
1. [](#bugfix)
|
||||
* `FlexForm`: Fixed some compatibility issues with Form plugin
|
||||
|
||||
# v1.7.0-beta.6
|
||||
## 08/29/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added experimental support for `Flex Pages` (**Flex Objects** plugin required)
|
||||
1. [](#improved)
|
||||
* Improved `bin/grav yamllinter` CLI command by adding an option to find YAML Linting issues from the whole site or custom folder
|
||||
* Added support for not instantiating pages, useful to speed up tasks
|
||||
* Greatly improved speed of loading Flex collections
|
||||
1. [](#bugfix)
|
||||
* Fixed `$page->summary()` always striping HTML tags if the summary was set by `$page->setSummary()`
|
||||
* Fixed `Flex->getObject()` when using Flex Key
|
||||
* Grav 1.7: Fixed enabling PHP Debug Bar causes fatal error in Gantry [#2634](https://github.com/getgrav/grav/issues/2634)
|
||||
* Grav 1.7: Fixed broken taxonomies [#2633](https://github.com/getgrav/grav/issues/2633)
|
||||
* Grav 1.7: Fixed unpublished blog posts being displayed on the front-end [#2650](https://github.com/getgrav/grav/issues/2650)
|
||||
|
||||
# v1.7.0-beta.5
|
||||
## 08/11/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added a new `bin/grav server` CLI command to easily run Symfony or PHP built-in webservers
|
||||
* Added `hasFlexFeature()` method to test if `FlexObject` or `FlexCollection` implements a given feature
|
||||
* Added `getFlexFeatures()` method to return all features that `FlexObject` or `FlexCollection` implements
|
||||
* Deprecated `FlexDirectory::update()` and `FlexDirectory::remove()`
|
||||
* Added `FlexStorage::getMetaData()` to get updated object meta information for listed keys
|
||||
* Added `Language::getPageExtensions()` to get full list of supported page language extensions
|
||||
* Added `$grav->close()` method to properly terminate the request with a response
|
||||
* Added `Pages::getCollection()` method
|
||||
1. [](#improved)
|
||||
* Better support for Symfony local server `symfony server:start`
|
||||
* Make `Route` objects immutable
|
||||
* `FlexDirectory::getObject()` can now be called without any parameters to create a new object
|
||||
* Flex objects no longer return temporary key if they do not have one; empty key is returned instead
|
||||
* Updated vendor libraries
|
||||
* Moved `collection()` and `evaluate()` logic from `Page` class into `Pages` class
|
||||
1. [](#bugfix)
|
||||
* Fixed `Form` not to use deleted flash object until the end of the request fixing issues with reset
|
||||
* Fixed `FlexForm` to allow multiple form instances with non-existing objects
|
||||
* Fixed `FlexObject` search by using `key`
|
||||
* Grav 1.7: Fixed clockwork messages with arrays and objects
|
||||
|
||||
# v1.7.0-beta.4
|
||||
## 07/01/2019
|
||||
|
||||
1. [](#new)
|
||||
* Updated with Grav 1.6.12 features, improvements & fixes
|
||||
* Added new configuration option `system.debugger.censored` to hide potentially sensitive information
|
||||
* Added new configuration option `system.languages.include_default_lang_file_extension` to keep default language in `.md` files if set to `false`
|
||||
* Added configuration option to set fallback content languages individually for every language
|
||||
1. [](#improved)
|
||||
* Updated Vendor libraries
|
||||
1. [](#bugfix)
|
||||
* Fixed `.md` page to be assigned to the default language and to be listed in translated/untranslated page list
|
||||
* Fixed `Language::getFallbackPageExtensions()` to fall back only to default language instead of going through all languages
|
||||
* Fixed `Language::getFallbackPageExtensions()` returning wrong file extensions when passing custom page extension
|
||||
|
||||
# v1.7.0-beta.3
|
||||
## 06/24/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed Clockwork on Windows machines
|
||||
* Fixed parent field issues on Windows machines
|
||||
* Fixed unreliable Clockwork calls in sub-folders
|
||||
|
||||
# v1.7.0-beta.2
|
||||
## 06/21/2019
|
||||
|
||||
1. [](#new)
|
||||
* Updated with Grav 1.6.11 fixes
|
||||
1. [](#improved)
|
||||
* Updated the Clockwork text
|
||||
|
||||
# v1.7.0-beta.1
|
||||
## 06/14/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added support for [Clockwork](https://underground.works/clockwork) developer tools (now default debugger)
|
||||
* Added support for [Tideways XHProf](https://github.com/tideways/php-xhprof-extension) PHP Extension for profiling method calls
|
||||
* Added Twig profiling for Clockwork debugger
|
||||
* Added support for Twig 2.11 (compatible with Twig 1.40+)
|
||||
* Optimization: Initialize debugbar only after the configuration has been loaded
|
||||
* Optimization: Combine some early Grav processors into a single one
|
||||
|
||||
# v1.6.17
|
||||
## mm/dd/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Safer file handling + customizable null char replacment in `CsvFormatter::decode()`
|
||||
|
||||
|
||||
# v1.6.16
|
||||
## 09/19/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Fixed Flex user creation if file storage is being used [#2444](https://github.com/getgrav/grav/issues/2444)
|
||||
* Fixed `Badly encoded JSON data` warning when uploading files [#2663](https://github.com/getgrav/grav/issues/2663)
|
||||
|
||||
# v1.6.15
|
||||
## 08/20/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Improved robots.txt [#2632](https://github.com/getgrav/grav/issues/2632)
|
||||
1. [](#bugfix)
|
||||
* Fixed broken markdown Twig tag [#2635](https://github.com/getgrav/grav/issues/2635)
|
||||
* Force Symfony 4.2 in Grav 1.6 to remove a bunch of deprecated messages
|
||||
|
||||
# v1.6.14
|
||||
## 08/18/2019
|
||||
|
||||
|
||||
26
bin/grav
26
bin/grav
@@ -3,7 +3,7 @@
|
||||
|
||||
use Grav\Common\Composer;
|
||||
use Grav\Common\Grav;
|
||||
use League\CLImate\CLImate;
|
||||
use Grav\Console\Cli;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
\define('GRAV_CLI', true);
|
||||
@@ -52,16 +52,18 @@ if (!file_exists(GRAV_ROOT . '/index.php')) {
|
||||
|
||||
$app = new Application('Grav CLI Application', GRAV_VERSION);
|
||||
$app->addCommands(array(
|
||||
new \Grav\Console\Cli\InstallCommand(),
|
||||
new \Grav\Console\Cli\ComposerCommand(),
|
||||
new \Grav\Console\Cli\SandboxCommand(),
|
||||
new \Grav\Console\Cli\CleanCommand(),
|
||||
new \Grav\Console\Cli\ClearCacheCommand(),
|
||||
new \Grav\Console\Cli\BackupCommand(),
|
||||
new \Grav\Console\Cli\NewProjectCommand(),
|
||||
new \Grav\Console\Cli\SchedulerCommand(),
|
||||
new \Grav\Console\Cli\SecurityCommand(),
|
||||
new \Grav\Console\Cli\LogViewerCommand(),
|
||||
new \Grav\Console\Cli\YamlLinterCommand(),
|
||||
new Cli\InstallCommand(),
|
||||
new Cli\ComposerCommand(),
|
||||
new Cli\SandboxCommand(),
|
||||
new Cli\CleanCommand(),
|
||||
new Cli\ClearCacheCommand(),
|
||||
new Cli\BackupCommand(),
|
||||
new Cli\NewProjectCommand(),
|
||||
new Cli\SchedulerCommand(),
|
||||
new Cli\SecurityCommand(),
|
||||
new Cli\LogViewerCommand(),
|
||||
new Cli\YamlLinterCommand(),
|
||||
new Cli\ServerCommand(),
|
||||
new Cli\PageSystemValidatorCommand(),
|
||||
));
|
||||
$app->run();
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
"kodus/psr7-server": "*",
|
||||
"nyholm/psr7": "^1.0",
|
||||
|
||||
"twig/twig": "~1.40",
|
||||
"twig/twig": "~1.0",
|
||||
"erusev/parsedown": "1.6.4",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "~4.2",
|
||||
"symfony/console": "~4.2",
|
||||
"symfony/event-dispatcher": "~4.2",
|
||||
"symfony/var-dumper": "~4.2",
|
||||
"symfony/process": "~4.2",
|
||||
"symfony/console": "~4.2.0",
|
||||
"symfony/event-dispatcher": "~4.2.0",
|
||||
"symfony/var-dumper": "~4.2.0",
|
||||
"symfony/process": "~4.2.0",
|
||||
"doctrine/cache": "^1.8",
|
||||
"doctrine/collections": "^1.5",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
@@ -50,7 +50,9 @@
|
||||
"composer/ca-bundle": "^1.0",
|
||||
"dragonmantank/cron-expression": "^1.2",
|
||||
"phive/twig-extensions-deferred": "^1.0",
|
||||
"willdurand/negotiation": "^2.3"
|
||||
"willdurand/negotiation": "^2.3",
|
||||
"itsgoingd/clockwork": "~4.0",
|
||||
"enshrined/svg-sanitize": "^0.10.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "^2.4",
|
||||
@@ -77,6 +79,10 @@
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/trilbymedia/PHP-Markdown-Documentation-Generator"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/itsgoingd/clockwork"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
|
||||
529
composer.lock
generated
529
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -16,8 +16,11 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
die(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli-server' && !isset($_SERVER['PHP_CLI_ROUTER'])) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
if (PHP_SAPI === 'cli-server') {
|
||||
$symfony_server = strpos(getenv('_'), 'symfony') !== false;
|
||||
if (!isset($_SERVER['PHP_CLI_ROUTER']) && !$symfony_server) {
|
||||
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure vendor libraries exist
|
||||
|
||||
@@ -10,3 +10,4 @@ Disallow: /user/
|
||||
Allow: /user/pages/
|
||||
Allow: /user/themes/
|
||||
Allow: /user/images/
|
||||
Allow: /
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
div.phpdebugbar {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.phpdebugbar pre {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div > * {
|
||||
padding: 5px 15px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div.phpdebugbar-header-right > * {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn {
|
||||
background-image: url(grav.png);
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-restore-btn {
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-tab.phpdebugbar-active {
|
||||
background: #3DB9EC;
|
||||
color: #fff;
|
||||
margin-top: -1px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar {
|
||||
border-top: 1px solid #ddd;
|
||||
padding-left: 5px;
|
||||
padding-right: 2px;
|
||||
padding-top: 2px;
|
||||
background-color: #fafafa !important;
|
||||
width: auto !important;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar input {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar .phpdebugbar-widgets-filter {
|
||||
|
||||
}
|
||||
|
||||
|
||||
.phpdebugbar input[type=text] {
|
||||
padding: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.phpdebugbar dl.phpdebugbar-widgets-varlist, ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.phpdebugbar pre, .phpdebugbar code {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
2
system/assets/debugger/clockwork.css
Normal file
2
system/assets/debugger/clockwork.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/** Clockwork Debugger CSS **/
|
||||
.clockwork-badge{position:fixed;z-index:10;bottom:0;left:0;padding:2px 4px;background-color:#eee;border:1px solid #ccc;border-bottom:0;border-left:0;display:flex;align-items:center}.clockwork-badge:hover{width:auto}.clockwork-badge:hover:after{content:'Grav Clockwork debugger enabled. Install Clockwork Browser extension (Chrome or Firefox), open your Developer tools and then select the Clockwork tab.'}.clockwork-badge:after{margin-left:10px;font-family:Monaco,Consolas,"Lucida Console",monospace;font-size:12px;line-height:1.5;color:#666}.clockwork-badge i{display:block;float:left;height:22px;width:22px;min-width:22px;background-size:contain;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAA/1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeHh4AAAD///8EBAT7+/sLCwv29vYVFRUvLy/t7e3m5ubCwsKxsbE/Pz+mpqZMTEwcHBzy8vLp6emfn5+AgIA2Njbi4uLf39+rq6tzc3NWVlYhISHa2trW1tbS0tLMzMy7u7uZmZmUlJSMjIxvb29kZGRHR0c7Ozt5eXkqKiq1tbWQkJBqampbW1tSUlLHx8eHh4ckJCRDQ0M3wD42AAAAI3RSTlMA/PibTbQ0x76TVAlw4LhZLOuEYCAN9Hjx0a2ppGZEGYw97djhXHwAAATZSURBVFjDlVcHW+MwDO1eFCjj2McNOzvdpXTTXVbL/P+/5SQ7QSSX5Di1X1onfi/Sk+Q4sTDbKqWK+YuznZ2zi3wxVdqK/Zf92M1nT9gnO8rmd398GX6Z3xaoOFoiAQcx3E5efgmeSuN8F6Xg1x3G06l/wjNpMR1B0uif4EhnIuFb+0diIoFXk3IVfokisR+h52GO4JKgyjmfaMhAFNlSaPR7DpwI+lzn/E4QKIqmKIJirxCMP4izBPPZPXhgXwMBYgULw0nfg/BF5scDbslb7QeJ08yqqTEmGYoB95d4H8ETL8+n9wBqrLu6ao3bBsMwAnxISf/9BHcqxNB8Y7cWl3Zz7TAUfPrvAT6AoNEFFXvsjutL01yOuMrtBxnFXsmT/1wQHmdWAFNnI3uI48Yj0FUcHbKf62GfUfr8eeQt7Uk3mQZpZNoVRPEui5vtEz5zFEpgWnyqVBZMc6oaGNriH2hGVZ0OxEvInPeMaZWJBA7vmPbCr5jjws5HBnAUxvDMH40aCIf4G5BjRQSs8E8HFFYf8bGxgDvD55bzGhwWkoBcuIyHR/AMdaCagxXDhtL6tSqoWpd4BMnlIR+Or+rYTK/a3EAGcc6e4AWHISnWv20iCCojsHoVlQdjrMexFF2C7UMg2A2WEGWbQhXN6l3eXC6XGp4b9qxbuEB2EBGBwtocrK90cVG5mbRXm6vmx/0phq1sIAGKDgLOBiN1MrO5a9aDl+D0W6x0Ar9BCTRuIIANa90Y7LrLVRXzwVtDInCqMRWcf2bUOEAsa4wJqFowQALL9EiAtVRk8QC4OW+1pOM9jIaVASwYagyNXDj+W0NcfuZNzjtXOiL0Zzg30Llj+ptfxQs4+vBPNiL5PawFCBkgXpUaVtqGl+A8dgZHL34BcBUQrwPptToW+o37Ku+UH9eYByJIx3YkAeFnMFuGO7S5gEp7YhXxa5OOAM39RXDPXb0qmpROsswZe+twXdU55oUIZAiEv3bD1UFwIYKkmGqytPCDCwKFQCKK0yL7qtSAPX54UAbtsLuBHkb9zyLmPQSNjsSgmQwKUOIfEY8F8t4B34DvndJY9BA8tNBJq1Nev9axmaStFcQLhgYoCTo0salkIaW8OUDdWjMTR2sHPhrAFZqx6cqcKE4pl2BJJ4K6hfwvqNgAnXfKX/HU6X3Zrhnu0k7tLNZtTBRv1hkwTDBY1NzFU6doDYjJbWdQkQhWwuU7/LvhTh3SDoco4ECL4i5dwURbc8NdDZz2IwKicE8d0KIqWetLE3+lL4hvUuGSeRfVWNLfj/gpOw4smBJBkKQHCzlHGwvAj4woB1gq5NGGLSXtORBPnUQPV5/MPVkDMxbpwG7w4x0xL6Ltxka0A/4NBvV09UVk4DoSn/jl2+JQS9q9KYawisAD4CfhsZ4TH3htylsdEHARIQBusqCKyUpymycgbbkkXEXjT3z7/oKQFTFVuZD2FMJHZIDsO5x2d4aAr2jR+GLwZhtAb028/0yJ9J8dE87jQyKObcjtTXT8dH+fDuKF4/eiPwzH44wTf/yUi6wrpRIOZ9lM1EtXAifFI+CJn9+iX/t2xMQwOMth/UZbASi8btAwR9FHWSpJr75g9Oqbin3VDg+SpwlP6k6TB4ex/7JvmcJx8jydy6XPk8eFTKhyfwCgX71MSvaBHgAAAABJRU5ErkJggg==)}
|
||||
2
system/assets/debugger/clockwork.js
Normal file
2
system/assets/debugger/clockwork.js
Normal file
@@ -0,0 +1,2 @@
|
||||
/** Clockwork Debugger JS **/
|
||||
document.addEventListener("DOMContentLoaded",function(){var e=document.createElement("div");e.appendChild(document.createElement("i")),e.className="clockwork-badge",document.body.appendChild(e)});
|
||||
70
system/assets/debugger/phpdebugbar.css
Normal file
70
system/assets/debugger/phpdebugbar.css
Normal file
@@ -0,0 +1,70 @@
|
||||
div.phpdebugbar {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.phpdebugbar pre {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div > * {
|
||||
padding: 5px 15px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header > div.phpdebugbar-header-right > * {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAA/1BMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeHh4AAAD///8EBAT7+/sLCwv29vYVFRUvLy/t7e3m5ubCwsKxsbE/Pz+mpqZMTEwcHBzy8vLp6emfn5+AgIA2Njbi4uLf39+rq6tzc3NWVlYhISHa2trW1tbS0tLMzMy7u7uZmZmUlJSMjIxvb29kZGRHR0c7Ozt5eXkqKiq1tbWQkJBqampbW1tSUlLHx8eHh4ckJCRDQ0M3wD42AAAAI3RSTlMA/PibTbQ0x76TVAlw4LhZLOuEYCAN9Hjx0a2ppGZEGYw97djhXHwAAATZSURBVFjDlVcHW+MwDO1eFCjj2McNOzvdpXTTXVbL/P+/5SQ7QSSX5Di1X1onfi/Sk+Q4sTDbKqWK+YuznZ2zi3wxVdqK/Zf92M1nT9gnO8rmd398GX6Z3xaoOFoiAQcx3E5efgmeSuN8F6Xg1x3G06l/wjNpMR1B0uif4EhnIuFb+0diIoFXk3IVfokisR+h52GO4JKgyjmfaMhAFNlSaPR7DpwI+lzn/E4QKIqmKIJirxCMP4izBPPZPXhgXwMBYgULw0nfg/BF5scDbslb7QeJ08yqqTEmGYoB95d4H8ETL8+n9wBqrLu6ao3bBsMwAnxISf/9BHcqxNB8Y7cWl3Zz7TAUfPrvAT6AoNEFFXvsjutL01yOuMrtBxnFXsmT/1wQHmdWAFNnI3uI48Yj0FUcHbKf62GfUfr8eeQt7Uk3mQZpZNoVRPEui5vtEz5zFEpgWnyqVBZMc6oaGNriH2hGVZ0OxEvInPeMaZWJBA7vmPbCr5jjws5HBnAUxvDMH40aCIf4G5BjRQSs8E8HFFYf8bGxgDvD55bzGhwWkoBcuIyHR/AMdaCagxXDhtL6tSqoWpd4BMnlIR+Or+rYTK/a3EAGcc6e4AWHISnWv20iCCojsHoVlQdjrMexFF2C7UMg2A2WEGWbQhXN6l3eXC6XGp4b9qxbuEB2EBGBwtocrK90cVG5mbRXm6vmx/0phq1sIAGKDgLOBiN1MrO5a9aDl+D0W6x0Ar9BCTRuIIANa90Y7LrLVRXzwVtDInCqMRWcf2bUOEAsa4wJqFowQALL9EiAtVRk8QC4OW+1pOM9jIaVASwYagyNXDj+W0NcfuZNzjtXOiL0Zzg30Llj+ptfxQs4+vBPNiL5PawFCBkgXpUaVtqGl+A8dgZHL34BcBUQrwPptToW+o37Ku+UH9eYByJIx3YkAeFnMFuGO7S5gEp7YhXxa5OOAM39RXDPXb0qmpROsswZe+twXdU55oUIZAiEv3bD1UFwIYKkmGqytPCDCwKFQCKK0yL7qtSAPX54UAbtsLuBHkb9zyLmPQSNjsSgmQwKUOIfEY8F8t4B34DvndJY9BA8tNBJq1Nev9axmaStFcQLhgYoCTo0salkIaW8OUDdWjMTR2sHPhrAFZqx6cqcKE4pl2BJJ4K6hfwvqNgAnXfKX/HU6X3Zrhnu0k7tLNZtTBRv1hkwTDBY1NzFU6doDYjJbWdQkQhWwuU7/LvhTh3SDoco4ECL4i5dwURbc8NdDZz2IwKicE8d0KIqWetLE3+lL4hvUuGSeRfVWNLfj/gpOw4smBJBkKQHCzlHGwvAj4woB1gq5NGGLSXtORBPnUQPV5/MPVkDMxbpwG7w4x0xL6Ltxka0A/4NBvV09UVk4DoSn/jl2+JQS9q9KYawisAD4CfhsZ4TH3htylsdEHARIQBusqCKyUpymycgbbkkXEXjT3z7/oKQFTFVuZD2FMJHZIDsO5x2d4aAr2jR+GLwZhtAb028/0yJ9J8dE87jQyKObcjtTXT8dH+fDuKF4/eiPwzH44wTf/yUi6wrpRIOZ9lM1EtXAifFI+CJn9+iX/t2xMQwOMth/UZbASi8btAwR9FHWSpJr75g9Oqbin3VDg+SpwlP6k6TB4ex/7JvmcJx8jydy6XPk8eFTKhyfwCgX71MSvaBHgAAAABJRU5ErkJggg==);
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-restore-btn {
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.phpdebugbar a.phpdebugbar-tab.phpdebugbar-active {
|
||||
background: #3DB9EC;
|
||||
color: #fff;
|
||||
margin-top: -1px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar {
|
||||
border-top: 1px solid #ddd;
|
||||
padding-left: 5px;
|
||||
padding-right: 2px;
|
||||
padding-top: 2px;
|
||||
background-color: #fafafa !important;
|
||||
width: auto !important;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar input {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.phpdebugbar .phpdebugbar-widgets-toolbar .phpdebugbar-widgets-filter {
|
||||
|
||||
}
|
||||
|
||||
|
||||
.phpdebugbar input[type=text] {
|
||||
padding: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.phpdebugbar dl.phpdebugbar-widgets-varlist, ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
|
||||
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.phpdebugbar pre, .phpdebugbar code {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -105,3 +105,15 @@ form:
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
|
||||
sanitize_svg:
|
||||
type: toggle
|
||||
label: PLUGIN_ADMIN.SANITIZE_SVG
|
||||
help: PLUGIN_ADMIN.SANITIZE_SVG_HELP
|
||||
highlight: 1
|
||||
options:
|
||||
1: PLUGIN_ADMIN.YES
|
||||
0: PLUGIN_ADMIN.NO
|
||||
default: true
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@ config:
|
||||
title: Accounts
|
||||
icon: fa-users
|
||||
authorize: ['admin.users', 'admin.accounts', 'admin.super']
|
||||
priority: 6
|
||||
|
||||
form:
|
||||
fields:
|
||||
@@ -36,4 +37,4 @@ form:
|
||||
flex-readonly@: exists
|
||||
readonly: false
|
||||
validate:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
@@ -36,3 +36,4 @@ uploads_dangerous_extensions:
|
||||
- htm
|
||||
- js
|
||||
- exe
|
||||
sanitize_svg: true
|
||||
|
||||
@@ -27,6 +27,7 @@ home:
|
||||
hide_in_urls: false # Hide the home route in URLs
|
||||
|
||||
pages:
|
||||
type: page # EXPERIMENTAL: Page type: page or flex
|
||||
theme: quark # Default theme (defaults to "quark" theme)
|
||||
order:
|
||||
by: default # Order pages by "default", "alpha" or "date"
|
||||
@@ -125,6 +126,8 @@ log:
|
||||
|
||||
debugger:
|
||||
enabled: false # Enable Grav debugger and following settings
|
||||
provider: clockwork # Debugger provider: debugbar | clockwork
|
||||
censored: false # Censor potentially sensitive information (POST parameters, cookies, files, configuration and most array/object data in log messages)
|
||||
shutdown:
|
||||
close_connection: true # Close the connection before calling onShutdown(). false for debugging
|
||||
|
||||
@@ -154,15 +157,15 @@ session:
|
||||
path:
|
||||
|
||||
gpm:
|
||||
releases: stable # Set to either 'stable' or 'testing'
|
||||
releases: testing # Set to either 'stable' or 'testing'
|
||||
proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
|
||||
method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
|
||||
verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
|
||||
official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
|
||||
|
||||
accounts:
|
||||
type: data # Account type: data or flex
|
||||
storage: file # Flex storage type: file or folder
|
||||
type: data # EXPERIMENTAL: Account type: data or flex
|
||||
storage: file # EXPERIMENTAL: Flex storage type: file or folder
|
||||
|
||||
strict_mode:
|
||||
yaml_compat: true # Grav 1.5+: Enables YAML backwards compatibility
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.6.14');
|
||||
define('GRAV_TESTING', false);
|
||||
define('GRAV_VERSION', '1.7.0-beta.10');
|
||||
define('GRAV_TESTING', true);
|
||||
define('DS', '/');
|
||||
|
||||
if (!defined('GRAV_PHP_MIN')) {
|
||||
|
||||
@@ -20,6 +20,7 @@ if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_N
|
||||
$grav_index = 'index.php';
|
||||
|
||||
/* Check the GRAV_BASEDIR environment variable and use if set */
|
||||
|
||||
$grav_basedir = getenv('GRAV_BASEDIR') ?: '';
|
||||
if ($grav_basedir) {
|
||||
$grav_index = ltrim($grav_basedir, '/') . DIRECTORY_SEPARATOR . $grav_index;
|
||||
|
||||
@@ -334,7 +334,7 @@ class Cache extends Getters
|
||||
* Stores a new cached entry.
|
||||
*
|
||||
* @param string $id the id of the cached entry
|
||||
* @param array|object $data the data for the cached entry to store
|
||||
* @param array|object|int $data the data for the cached entry to store
|
||||
* @param int $lifetime the lifetime to store the entry in seconds
|
||||
*/
|
||||
public function save($id, $data, $lifetime = null)
|
||||
|
||||
@@ -25,6 +25,9 @@ class Blueprint extends BlueprintForm
|
||||
/** @var BlueprintSchema */
|
||||
protected $blueprintSchema;
|
||||
|
||||
/** @var object */
|
||||
protected $object;
|
||||
|
||||
/** @var array */
|
||||
protected $defaults;
|
||||
|
||||
@@ -42,6 +45,11 @@ class Blueprint extends BlueprintForm
|
||||
$this->scope = $scope;
|
||||
}
|
||||
|
||||
public function setObject($object)
|
||||
{
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default values for field types.
|
||||
*
|
||||
@@ -111,6 +119,7 @@ class Blueprint extends BlueprintForm
|
||||
foreach ($data as $property => $call) {
|
||||
$action = $call['action'];
|
||||
$method = 'dynamic' . ucfirst($action);
|
||||
$call['object'] = $this->object;
|
||||
|
||||
if (isset($this->handlers[$action])) {
|
||||
$callable = $this->handlers[$action];
|
||||
|
||||
@@ -244,8 +244,8 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
|| !empty($field['disabled'])
|
||||
// Field validation is set to be ignored
|
||||
|| !empty($field['validate']['ignore'])
|
||||
// Field is toggleable and the toggle is turned off
|
||||
|| (!empty($field['toggleable']) && empty($toggles[$key]))
|
||||
// Field is overridable and the toggle is turned off
|
||||
|| (!empty($field['overridable']) && empty($toggles[$key]))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -279,6 +279,12 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip overridable fields without value.
|
||||
// TODO: We need better overridable support, which is not just ignoring required values but also looking if defaults are good.
|
||||
if (!empty($field['overridable']) && !isset($data[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if required.
|
||||
if (isset($field['validate']['required'])
|
||||
&& $field['validate']['required'] === true) {
|
||||
|
||||
@@ -9,6 +9,14 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use Clockwork\Clockwork;
|
||||
use Clockwork\DataSource\MonologDataSource;
|
||||
use Clockwork\DataSource\PhpDataSource;
|
||||
use Clockwork\DataSource\PsrMessageDataSource;
|
||||
use Clockwork\DataSource\XdebugDataSource;
|
||||
use Clockwork\Helpers\ServerTiming;
|
||||
use Clockwork\Request\UserData;
|
||||
use Clockwork\Storage\FileStorage;
|
||||
use DebugBar\DataCollector\ConfigCollector;
|
||||
use DebugBar\DataCollector\DataCollectorInterface;
|
||||
use DebugBar\DataCollector\ExceptionsCollector;
|
||||
@@ -22,11 +30,23 @@ use DebugBar\JavascriptRenderer;
|
||||
use DebugBar\StandardDebugBar;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Processors\ProcessorInterface;
|
||||
use Grav\Common\Twig\TwigClockworkDataSource;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Monolog\Logger;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Twig\Environment;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
class Debugger
|
||||
{
|
||||
/** @var static */
|
||||
protected static $instance;
|
||||
|
||||
/** @var Grav $grav */
|
||||
protected $grav;
|
||||
|
||||
@@ -39,8 +59,11 @@ class Debugger
|
||||
/** @var StandardDebugBar $debugbar */
|
||||
protected $debugbar;
|
||||
|
||||
/** @var Clockwork */
|
||||
protected $clockwork;
|
||||
|
||||
/** @var bool */
|
||||
protected $enabled;
|
||||
protected $enabled = false;
|
||||
|
||||
protected $initialized = false;
|
||||
|
||||
@@ -53,36 +76,38 @@ class Debugger
|
||||
/** @var callable */
|
||||
protected $errorHandler;
|
||||
|
||||
protected $requestTime;
|
||||
protected $currentTime;
|
||||
|
||||
/** @var int */
|
||||
protected $profiling = 0;
|
||||
|
||||
protected $censored = false;
|
||||
|
||||
/**
|
||||
* Debugger constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$currentTime = microtime(true);
|
||||
static::$instance = $this;
|
||||
|
||||
$this->currentTime = microtime(true);
|
||||
|
||||
if (!\defined('GRAV_REQUEST_TIME')) {
|
||||
\define('GRAV_REQUEST_TIME', $currentTime);
|
||||
\define('GRAV_REQUEST_TIME', $this->currentTime);
|
||||
}
|
||||
|
||||
// Enable debugger until $this->init() gets called.
|
||||
$this->enabled = true;
|
||||
|
||||
$debugbar = new DebugBar();
|
||||
$debugbar->addCollector(new PhpInfoCollector());
|
||||
$debugbar->addCollector(new MessagesCollector());
|
||||
$debugbar->addCollector(new RequestDataCollector());
|
||||
$debugbar->addCollector(new TimeDataCollector($_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME));
|
||||
|
||||
$debugbar['time']->addMeasure('Server', $debugbar['time']->getRequestStartTime(), GRAV_REQUEST_TIME);
|
||||
$debugbar['time']->addMeasure('Loading', GRAV_REQUEST_TIME, $currentTime);
|
||||
$debugbar['time']->addMeasure('Debugger', $currentTime, microtime(true));
|
||||
|
||||
$this->debugbar = $debugbar;
|
||||
$this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME;
|
||||
|
||||
// Set deprecation collector.
|
||||
$this->setErrorHandler();
|
||||
}
|
||||
|
||||
public function getClockwork(): ?Clockwork
|
||||
{
|
||||
return $this->enabled ? $this->clockwork : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the debugger
|
||||
*
|
||||
@@ -100,25 +125,234 @@ class Debugger
|
||||
|
||||
// Enable/disable debugger based on configuration.
|
||||
$this->enabled = (bool)$this->config->get('system.debugger.enabled');
|
||||
$this->censored = (bool)$this->config->get('system.debugger.censored', false);
|
||||
|
||||
if ($this->enabled()) {
|
||||
if ($this->enabled) {
|
||||
$this->initialized = true;
|
||||
|
||||
$plugins_config = (array)$this->config->get('plugins');
|
||||
$clockwork = $debugbar = null;
|
||||
|
||||
switch ($this->config->get('system.debugger.provider', 'debugbar')) {
|
||||
case 'clockwork':
|
||||
$this->clockwork = $clockwork = new Clockwork();
|
||||
break;
|
||||
default:
|
||||
$this->debugbar = $debugbar = new DebugBar();
|
||||
}
|
||||
|
||||
$plugins_config = (array)$this->config->get('plugins');
|
||||
ksort($plugins_config);
|
||||
|
||||
$debugbar = $this->debugbar;
|
||||
$debugbar->addCollector(new MemoryCollector());
|
||||
$debugbar->addCollector(new ExceptionsCollector());
|
||||
$debugbar->addCollector(new ConfigCollector((array)$this->config->get('system'), 'Config'));
|
||||
$debugbar->addCollector(new ConfigCollector($plugins_config, 'Plugins'));
|
||||
$this->addMessage('Grav v' . GRAV_VERSION);
|
||||
if ($clockwork) {
|
||||
$log = $this->grav['log'];
|
||||
$clockwork->setStorage(new FileStorage('cache://clockwork'));
|
||||
if (extension_loaded('xdebug')) {
|
||||
$clockwork->addDataSource(new XdebugDataSource());
|
||||
}
|
||||
if ($log instanceof Logger) {
|
||||
$clockwork->addDataSource(new MonologDataSource($log));
|
||||
}
|
||||
|
||||
$clockwork->addDataSource(new TwigClockworkDataSource());
|
||||
|
||||
$timeLine = $clockwork->getTimeline();
|
||||
if ($this->requestTime !== GRAV_REQUEST_TIME) {
|
||||
$timeLine->addEvent('server', 'Server', $this->requestTime, GRAV_REQUEST_TIME);
|
||||
}
|
||||
if ($this->currentTime !== GRAV_REQUEST_TIME) {
|
||||
$timeLine->addEvent('loading', 'Loading', GRAV_REQUEST_TIME, $this->currentTime);
|
||||
}
|
||||
$timeLine->addEvent('setup', 'Site Setup', $this->currentTime, microtime(true));
|
||||
}
|
||||
|
||||
if ($this->censored) {
|
||||
$censored = ['CENSORED' => true];
|
||||
}
|
||||
|
||||
if ($debugbar) {
|
||||
$debugbar->addCollector(new PhpInfoCollector());
|
||||
$debugbar->addCollector(new MessagesCollector());
|
||||
if (!$this->censored) {
|
||||
$debugbar->addCollector(new RequestDataCollector());
|
||||
}
|
||||
$debugbar->addCollector(new TimeDataCollector($this->requestTime));
|
||||
$debugbar->addCollector(new MemoryCollector());
|
||||
$debugbar->addCollector(new ExceptionsCollector());
|
||||
$debugbar->addCollector(new ConfigCollector($censored ?? (array)$this->config->get('system'), 'Config'));
|
||||
$debugbar->addCollector(new ConfigCollector($censored ?? $plugins_config, 'Plugins'));
|
||||
$debugbar->addCollector(new ConfigCollector($this->config->get('streams.schemes'), 'Streams'));
|
||||
|
||||
if ($this->requestTime !== GRAV_REQUEST_TIME) {
|
||||
$debugbar['time']->addMeasure('Server', $debugbar['time']->getRequestStartTime(), GRAV_REQUEST_TIME);
|
||||
}
|
||||
if ($this->currentTime !== GRAV_REQUEST_TIME) {
|
||||
$debugbar['time']->addMeasure('Loading', GRAV_REQUEST_TIME, $this->currentTime);
|
||||
}
|
||||
$debugbar['time']->addMeasure('Site Setup', $this->currentTime, microtime(true));
|
||||
}
|
||||
|
||||
$this->addMessage('Grav v' . GRAV_VERSION . ' - PHP ' . PHP_VERSION);
|
||||
$this->config->debug();
|
||||
|
||||
if ($clockwork) {
|
||||
$clockwork->info('System Configuration', $censored ?? $this->config->get('system'));
|
||||
$clockwork->info('Plugins Configuration', $censored ?? $plugins_config);
|
||||
$clockwork->info('Streams', $this->config->get('streams.schemes'));
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function finalize(): void
|
||||
{
|
||||
if ($this->clockwork && $this->enabled) {
|
||||
$this->stopProfiling('Profiler Analysis');
|
||||
$this->addMeasures();
|
||||
|
||||
$deprecations = $this->getDeprecations();
|
||||
$count = count($deprecations);
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var UserData $userData */
|
||||
$userData = $this->clockwork->userData('Deprecated');
|
||||
$userData->counters([
|
||||
'Deprecated' => count($deprecations)
|
||||
]);
|
||||
foreach ($deprecations as &$deprecation) {
|
||||
if (0) {
|
||||
$d = $deprecation;
|
||||
unset($d['message']);
|
||||
$this->clockwork->log('deprecated', $deprecation['message'], $d);
|
||||
}
|
||||
}
|
||||
unset($deprecation);
|
||||
|
||||
$userData->table('Your site is using following deprecated features', $deprecations);
|
||||
}
|
||||
}
|
||||
|
||||
public function logRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
if (!$this->enabled || !$this->clockwork) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$clockwork = $this->clockwork;
|
||||
|
||||
$this->finalize();
|
||||
|
||||
$clockwork->getTimeline()->finalize($request->getAttribute('request_time'));
|
||||
|
||||
if ($this->censored) {
|
||||
$censored = 'CENSORED';
|
||||
$request = $request
|
||||
->withCookieParams([$censored => ''])
|
||||
->withUploadedFiles([])
|
||||
->withHeader('cookie', $censored);
|
||||
if ($request->getBody()) {
|
||||
$request = $request->withParsedBody([$censored => '']);
|
||||
}
|
||||
}
|
||||
|
||||
$clockwork->addDataSource(new PsrMessageDataSource($request, $response));
|
||||
|
||||
$clockwork->resolveRequest();
|
||||
$clockwork->storeRequest();
|
||||
|
||||
$clockworkRequest = $clockwork->getRequest();
|
||||
|
||||
$response = $response
|
||||
->withHeader('X-Clockwork-Id', $clockworkRequest->id)
|
||||
->withHeader('X-Clockwork-Version', $clockwork::VERSION);
|
||||
|
||||
$basePath = Grav::instance()['uri']->rootUrl();
|
||||
if ($basePath) {
|
||||
$response = $response->withHeader('X-Clockwork-Path', $basePath . '/__clockwork/');
|
||||
}
|
||||
|
||||
return $response->withHeader('Server-Timing', ServerTiming::fromRequest($clockworkRequest)->value());
|
||||
}
|
||||
|
||||
|
||||
public function debuggerRequest(RequestInterface $request): Response
|
||||
{
|
||||
$clockwork = $this->clockwork;
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Grav-Internal-SkipShutdown' => 1
|
||||
];
|
||||
|
||||
$path = $request->getUri()->getPath();
|
||||
$clockworkDataUri = '#/__clockwork(?:/(?<id>[0-9-]+))?(?:/(?<direction>(?:previous|next)))?(?:/(?<count>\d+))?#';
|
||||
if (preg_match($clockworkDataUri, $path, $matches) === false) {
|
||||
$response = ['message' => 'Bad Input'];
|
||||
|
||||
return new Response(400, $headers, json_encode($response));
|
||||
}
|
||||
|
||||
$id = $matches['id'] ?? null;
|
||||
$direction = $matches['direction'] ?? null;
|
||||
$count = $matches['count'] ?? null;
|
||||
|
||||
$storage = $clockwork->getStorage();
|
||||
|
||||
if ($direction === 'previous') {
|
||||
$data = $storage->previous($id, $count);
|
||||
} elseif ($direction === 'next') {
|
||||
$data = $storage->next($id, $count);
|
||||
} elseif ($id === 'latest') {
|
||||
$data = $storage->latest();
|
||||
} else {
|
||||
$data = $storage->find($id);
|
||||
}
|
||||
|
||||
if (preg_match('#(?<id>[0-9-]+|latest)/extended#', $path)) {
|
||||
$clockwork->extendRequest($data);
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
$response = ['message' => 'Not Found'];
|
||||
|
||||
return new Response(404, $headers, json_encode($response));
|
||||
}
|
||||
|
||||
$data = is_array($data) ? array_map(function ($item) { return $item->toArray(); }, $data) : $data->toArray();
|
||||
|
||||
return new Response(200, $headers, json_encode($data));
|
||||
}
|
||||
|
||||
protected function addMeasures()
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nowTime = microtime(true);
|
||||
$clkTimeLine = $this->clockwork ? $this->clockwork->getTimeline() : null;
|
||||
$debTimeLine = $this->debugbar ? $this->debugbar['time'] : null;
|
||||
foreach ($this->timers as $name => $data) {
|
||||
$description = $data[0];
|
||||
$startTime = $data[1] ?? null;
|
||||
$endTime = $data[2] ?? $nowTime;
|
||||
if ($endTime - $startTime < 0.001) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($clkTimeLine) {
|
||||
$clkTimeLine->addEvent($name, $description ?? $name, $startTime, $endTime);
|
||||
}
|
||||
|
||||
if ($debTimeLine) {
|
||||
$debTimeLine->addMeasure($description ?? $name, $startTime, $endTime);
|
||||
}
|
||||
}
|
||||
$this->timers = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/get the enabled state of the debugger
|
||||
*
|
||||
@@ -142,7 +376,8 @@ class Debugger
|
||||
*/
|
||||
public function addAssets()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
if ($this->enabled) {
|
||||
|
||||
|
||||
// Only add assets if Page is HTML
|
||||
$page = $this->grav['page'];
|
||||
@@ -153,23 +388,35 @@ class Debugger
|
||||
/** @var Assets $assets */
|
||||
$assets = $this->grav['assets'];
|
||||
|
||||
// Add jquery library
|
||||
$assets->add('jquery', 101);
|
||||
|
||||
$this->renderer = $this->debugbar->getJavascriptRenderer();
|
||||
$this->renderer->setIncludeVendors(false);
|
||||
|
||||
// Get the required CSS files
|
||||
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
|
||||
foreach ((array)$css_files as $css) {
|
||||
$assets->addCss($css);
|
||||
// Clockwork specific assets
|
||||
if ($this->clockwork) {
|
||||
$assets->addCss('/system/assets/debugger/clockwork.css', ['loading' => 'inline']);
|
||||
$assets->addJs('/system/assets/debugger/clockwork.js', ['loading' => 'inline']);
|
||||
}
|
||||
|
||||
$assets->addCss('/system/assets/debugger.css');
|
||||
|
||||
foreach ((array)$js_files as $js) {
|
||||
$assets->addJs($js);
|
||||
// Debugbar specific assets
|
||||
if ($this->debugbar) {
|
||||
|
||||
// Add jquery library
|
||||
$assets->add('jquery', 101);
|
||||
|
||||
$this->renderer = $this->debugbar->getJavascriptRenderer();
|
||||
$this->renderer->setIncludeVendors(false);
|
||||
|
||||
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
|
||||
|
||||
foreach ((array)$css_files as $css) {
|
||||
$assets->addCss($css);
|
||||
}
|
||||
|
||||
$assets->addCss('/system/assets/debugger/phpdebugbar.css', ['loading' => 'inline']);
|
||||
|
||||
foreach ((array)$js_files as $js) {
|
||||
$assets->addJs($js);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -192,7 +439,9 @@ class Debugger
|
||||
*/
|
||||
public function addCollector($collector)
|
||||
{
|
||||
$this->debugbar->addCollector($collector);
|
||||
if ($this->debugbar && !$this->debugbar->hasCollector($collector->getName())) {
|
||||
$this->debugbar->addCollector($collector);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -200,14 +449,18 @@ class Debugger
|
||||
/**
|
||||
* Returns a data collector
|
||||
*
|
||||
* @param DataCollectorInterface $collector
|
||||
* @param string $name
|
||||
*
|
||||
* @return DataCollectorInterface
|
||||
* @throws \DebugBar\DebugBarException
|
||||
*/
|
||||
public function getCollector($collector)
|
||||
public function getCollector($name)
|
||||
{
|
||||
return $this->debugbar->getCollector($collector);
|
||||
if ($this->debugbar && $this->debugbar->hasCollector($name)) {
|
||||
return $this->debugbar->getCollector($name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,13 +470,14 @@ class Debugger
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
if ($this->enabled && $this->debugbar) {
|
||||
// Only add assets if Page is HTML
|
||||
$page = $this->grav['page'];
|
||||
if (!$this->renderer || $page->templateFormat() !== 'html') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->addMeasures();
|
||||
$this->addDeprecations();
|
||||
|
||||
echo $this->renderer->render();
|
||||
@@ -239,7 +493,8 @@ class Debugger
|
||||
*/
|
||||
public function sendDataInHeaders()
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
if ($this->enabled && $this->debugbar) {
|
||||
$this->addMeasures();
|
||||
$this->addDeprecations();
|
||||
$this->debugbar->sendDataInHeaders();
|
||||
}
|
||||
@@ -254,16 +509,146 @@ class Debugger
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
if (!$this->enabled()) {
|
||||
if (!$this->enabled || !$this->debugbar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->addMeasures();
|
||||
$this->addDeprecations();
|
||||
$this->timers = [];
|
||||
|
||||
return $this->debugbar->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hierarchical Profiler support.
|
||||
*
|
||||
* @param callable $callable
|
||||
* @param string $message
|
||||
* @return mixed
|
||||
*/
|
||||
public function profile(callable $callable, string $message = null)
|
||||
{
|
||||
$this->startProfiling();
|
||||
$response = $callable();
|
||||
$this->stopProfiling($message);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start profiling code.
|
||||
*/
|
||||
public function startProfiling(): void
|
||||
{
|
||||
if ($this->enabled && extension_loaded('tideways_xhprof')) {
|
||||
$this->profiling++;
|
||||
if ($this->profiling === 1) {
|
||||
\tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_NO_BUILTINS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop profiling code. Returns profiling array or null if profiling couldn't be done.
|
||||
*
|
||||
* @param string $message
|
||||
* @return array|null
|
||||
*/
|
||||
public function stopProfiling(string $message = null): ?array
|
||||
{
|
||||
$timings = null;
|
||||
if ($this->enabled && extension_loaded('tideways_xhprof')) {
|
||||
$profiling = $this->profiling - 1;
|
||||
if ($profiling === 0) {
|
||||
$timings = \tideways_xhprof_disable();
|
||||
$timings = $this->buildProfilerTimings($timings);
|
||||
|
||||
if ($this->clockwork) {
|
||||
/** @var UserData $userData */
|
||||
$userData = $this->clockwork->userData('Profiler');
|
||||
$userData->counters([
|
||||
'Calls' => count($timings)
|
||||
]);
|
||||
$userData->table('Profiler', $timings);
|
||||
} else {
|
||||
$this->addMessage($message ?? 'Profiler Analysis', 'debug', $timings);
|
||||
}
|
||||
}
|
||||
$this->profiling = max(0, $profiling);
|
||||
}
|
||||
|
||||
return $timings;
|
||||
}
|
||||
|
||||
protected function buildProfilerTimings(array $timings): array
|
||||
{
|
||||
// Filter method calls which take almost no time.
|
||||
$timings = array_filter($timings, function ($value) {
|
||||
return $value['wt'] > 50;
|
||||
});
|
||||
|
||||
uasort($timings, function (array $a, array $b) {
|
||||
return $b['wt'] <=> $a['wt'];
|
||||
});
|
||||
|
||||
$table = [];
|
||||
foreach ($timings as $key => $timing) {
|
||||
$parts = explode('==>', $key);
|
||||
$method = $this->parseProfilerCall(array_pop($parts));
|
||||
$context = $this->parseProfilerCall(array_pop($parts));
|
||||
|
||||
// Skip redundant method calls.
|
||||
if ($context === 'Grav\Framework\RequestHandler\RequestHandler::handle()') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not profile library calls.
|
||||
if (strpos($context, 'Grav\\') !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$table[] = [
|
||||
'Context' => $context,
|
||||
'Method' => $method,
|
||||
'Calls' => $timing['ct'],
|
||||
'Time (ms)' => $timing['wt'] / 1000,
|
||||
];
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
protected function parseProfilerCall(?string $call)
|
||||
{
|
||||
if (null === $call) {
|
||||
return '';
|
||||
}
|
||||
if (strpos($call, '@')) {
|
||||
[$call,] = explode('@', $call);
|
||||
}
|
||||
if (strpos($call, '::')) {
|
||||
[$class, $call] = explode('::', $call);
|
||||
}
|
||||
|
||||
if (!isset($class)) {
|
||||
return $call;
|
||||
}
|
||||
|
||||
// It is also possible to display twig files, but they are being logged in views.
|
||||
/*
|
||||
if (strpos($class, '__TwigTemplate_') === 0 && class_exists($class)) {
|
||||
$env = new Environment();
|
||||
/ ** @var Template $template * /
|
||||
$template = new $class($env);
|
||||
|
||||
return $template->getTemplateName();
|
||||
}
|
||||
*/
|
||||
|
||||
return "{$class}::{$call}()";
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a timer with an associated name and description
|
||||
*
|
||||
@@ -274,10 +659,7 @@ class Debugger
|
||||
*/
|
||||
public function startTimer($name, $description = null)
|
||||
{
|
||||
if (strpos($name, '_') === 0 || $this->enabled()) {
|
||||
$this->debugbar['time']->startMeasure($name, $description);
|
||||
$this->timers[] = $name;
|
||||
}
|
||||
$this->timers[$name] = [$description, microtime(true)];
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -291,8 +673,9 @@ class Debugger
|
||||
*/
|
||||
public function stopTimer($name)
|
||||
{
|
||||
if (\in_array($name, $this->timers, true) && (strpos($name, '_') === 0 || $this->enabled())) {
|
||||
$this->debugbar['time']->stopMeasure($name);
|
||||
if (isset($this->timers[$name])) {
|
||||
$endTime = microtime(true);
|
||||
$this->timers[$name][] = $endTime;
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -303,14 +686,60 @@ class Debugger
|
||||
*
|
||||
* @param mixed $message
|
||||
* @param string $label
|
||||
* @param bool $isString
|
||||
* @param mixed|bool $isString
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addMessage($message, $label = 'info', $isString = true)
|
||||
{
|
||||
if ($this->enabled()) {
|
||||
$this->debugbar['messages']->addMessage($message, $label, $isString);
|
||||
if ($this->enabled) {
|
||||
if ($this->censored) {
|
||||
if (!is_scalar($message)) {
|
||||
$message = 'CENSORED';
|
||||
}
|
||||
if (!is_scalar($isString)) {
|
||||
$isString = ['CENSORED'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->debugbar) {
|
||||
if (is_array($isString)) {
|
||||
$message = $isString;
|
||||
$isString = false;
|
||||
} elseif (is_string($isString)) {
|
||||
$message = $isString;
|
||||
$isString = true;
|
||||
}
|
||||
$this->debugbar['messages']->addMessage($message, $label, $isString);
|
||||
}
|
||||
|
||||
if ($this->clockwork) {
|
||||
if (!is_scalar($message)) {
|
||||
$isString = $message;
|
||||
$message = '';
|
||||
} elseif (is_bool($isString)) {
|
||||
$isString = [];
|
||||
}
|
||||
if (!is_array($isString)) {
|
||||
$isString = [gettype($isString) => $isString];
|
||||
}
|
||||
$this->clockwork->log($label, $message, $isString);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addEvent(string $name, ?Event $event, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
if ($this->enabled) {
|
||||
if ($this->clockwork) {
|
||||
$listeners = [];
|
||||
foreach ($dispatcher->getListeners($name) as $listener) {
|
||||
$listeners[] = $this->resolveCallable($listener);
|
||||
}
|
||||
$this->clockwork->addEvent($name, null, microtime(true), ['listeners' => $listeners]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -319,13 +748,23 @@ class Debugger
|
||||
/**
|
||||
* Dump exception into the Messages tab of the Debug Bar
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @param \Throwable $e
|
||||
* @return Debugger
|
||||
*/
|
||||
public function addException(\Exception $e)
|
||||
public function addException(\Throwable $e)
|
||||
{
|
||||
if ($this->initialized && $this->enabled()) {
|
||||
$this->debugbar['exceptions']->addException($e);
|
||||
if ($this->initialized && $this->enabled) {
|
||||
if ($this->debugbar) {
|
||||
$this->debugbar['exceptions']->addException($e);
|
||||
}
|
||||
|
||||
if ($this->clockwork) {
|
||||
/** @var UserData $exceptions */
|
||||
$exceptions = $this->clockwork->userData('Exceptions');
|
||||
$exceptions->data(['message' => $e->getMessage()]);
|
||||
|
||||
$this->clockwork->alert($e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -355,7 +794,7 @@ class Debugger
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$this->enabled()) {
|
||||
if (!$this->enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -540,6 +979,21 @@ class Debugger
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getDeprecations(): array
|
||||
{
|
||||
if (!$this->deprecations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$list = [];
|
||||
/** @var array $deprecated */
|
||||
foreach ($this->deprecations as $deprecated) {
|
||||
$list[] = $this->getDepracatedMessage($deprecated)[0];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
protected function addDeprecations()
|
||||
{
|
||||
if (!$this->deprecations) {
|
||||
@@ -603,4 +1057,13 @@ class Debugger
|
||||
|
||||
return $trace['function'] . '(' . implode(', ', $trace['args'] ?? []) . ')';
|
||||
}
|
||||
|
||||
protected function resolveCallable(callable $callable)
|
||||
{
|
||||
if (is_array($callable)) {
|
||||
return get_class($callable[0]) . '->' . $callable[1] . '()';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
/**
|
||||
* @property string $name
|
||||
*/
|
||||
class Package
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -16,12 +16,8 @@ use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Processors\AssetsProcessor;
|
||||
use Grav\Common\Processors\BackupsProcessor;
|
||||
use Grav\Common\Processors\ConfigurationProcessor;
|
||||
use Grav\Common\Processors\DebuggerAssetsProcessor;
|
||||
use Grav\Common\Processors\DebuggerProcessor;
|
||||
use Grav\Common\Processors\ErrorsProcessor;
|
||||
use Grav\Common\Processors\InitializeProcessor;
|
||||
use Grav\Common\Processors\LoggerProcessor;
|
||||
use Grav\Common\Processors\PagesProcessor;
|
||||
use Grav\Common\Processors\PluginsProcessor;
|
||||
use Grav\Common\Processors\RenderProcessor;
|
||||
@@ -90,10 +86,6 @@ class Grav extends Container
|
||||
* @var array All middleware processors that are processed in $this->process()
|
||||
*/
|
||||
protected $middleware = [
|
||||
'configurationProcessor',
|
||||
'loggerProcessor',
|
||||
'errorsProcessor',
|
||||
'debuggerProcessor',
|
||||
'initializeProcessor',
|
||||
'pluginsProcessor',
|
||||
'themesProcessor',
|
||||
@@ -157,15 +149,13 @@ class Grav extends Container
|
||||
|
||||
$this->initialized['setup'] = true;
|
||||
|
||||
$this->measureTime('_setup', 'Site Setup', function () use ($environment) {
|
||||
// Force environment if passed to the method.
|
||||
if ($environment) {
|
||||
Setup::$environment = $environment;
|
||||
}
|
||||
// Force environment if passed to the method.
|
||||
if ($environment) {
|
||||
Setup::$environment = $environment;
|
||||
}
|
||||
|
||||
$this['setup'];
|
||||
$this['streams'];
|
||||
});
|
||||
$this['setup'];
|
||||
$this['streams'];
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -186,18 +176,6 @@ class Grav extends Container
|
||||
|
||||
$container = new Container(
|
||||
[
|
||||
'configurationProcessor' => function () {
|
||||
return new ConfigurationProcessor($this);
|
||||
},
|
||||
'loggerProcessor' => function () {
|
||||
return new LoggerProcessor($this);
|
||||
},
|
||||
'errorsProcessor' => function () {
|
||||
return new ErrorsProcessor($this);
|
||||
},
|
||||
'debuggerProcessor' => function () {
|
||||
return new DebuggerProcessor($this);
|
||||
},
|
||||
'initializeProcessor' => function () {
|
||||
return new InitializeProcessor($this);
|
||||
},
|
||||
@@ -237,13 +215,10 @@ class Grav extends Container
|
||||
]
|
||||
);
|
||||
|
||||
$default = function (ServerRequestInterface $request) {
|
||||
$default = static function () {
|
||||
return new Response(404);
|
||||
};
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
|
||||
$collection = new RequestHandler($this->middleware, $default, $container);
|
||||
|
||||
$response = $collection->handle($this['request']);
|
||||
@@ -251,32 +226,65 @@ class Grav extends Container
|
||||
$this->header($response);
|
||||
echo $response->getBody();
|
||||
|
||||
$debugger->render();
|
||||
$this['debugger']->render();
|
||||
|
||||
register_shutdown_function([$this, 'shutdown']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the system locale based on the language and configuration
|
||||
*/
|
||||
public function setLocale()
|
||||
{
|
||||
// Initialize Locale if set and configured.
|
||||
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
|
||||
$language = $this['language']->getLanguage();
|
||||
setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
|
||||
} elseif ($this['config']->get('system.default_locale')) {
|
||||
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
|
||||
// Response object can turn off all shutdown processing. This can be used for example to speed up AJAX responses.
|
||||
// Note that using this feature will also turn off response compression.
|
||||
if ($response->getHeaderLine('Grav-Internal-SkipShutdown') !== '1') {
|
||||
register_shutdown_function([$this, 'shutdown']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect browser to another location.
|
||||
* Terminates Grav request with a response.
|
||||
*
|
||||
* Please use this method instead of calling `die();` or `exit();`. Note that you need to create a response object.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
public function close(ResponseInterface $response): void
|
||||
{
|
||||
// Make sure nothing extra gets written to the response.
|
||||
while (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// Close the session.
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = $this['request'];
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
$response = $debugger->logRequest($request, $response);
|
||||
|
||||
// Send the response and terminate.
|
||||
$this->header($response);
|
||||
echo $response->getBody();
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @deprecated 1.7 Do not use
|
||||
*/
|
||||
public function exit(ResponseInterface $response): void
|
||||
{
|
||||
$this->close($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates Grav request and redirects browser to another location.
|
||||
*
|
||||
* Please use this method instead of calling `header("Location: {$url}", true, 302); exit();`.
|
||||
*
|
||||
* @param string $route Internal route.
|
||||
* @param int $code Redirection code (30x)
|
||||
*/
|
||||
public function redirect($route, $code = null)
|
||||
public function redirect($route, $code = null): void
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = $this['uri'];
|
||||
@@ -293,11 +301,7 @@ class Grav extends Container
|
||||
$code = $this['config']->get('system.pages.redirect_default_code', 302);
|
||||
}
|
||||
|
||||
if (isset($this['session'])) {
|
||||
$this['session']->close();
|
||||
}
|
||||
|
||||
if ($uri->isExternal($route)) {
|
||||
if ($uri::isExternal($route)) {
|
||||
$url = $route;
|
||||
} else {
|
||||
$url = rtrim($uri->rootUrl(), '/') . '/';
|
||||
@@ -309,8 +313,9 @@ class Grav extends Container
|
||||
}
|
||||
}
|
||||
|
||||
header("Location: {$url}", true, $code);
|
||||
exit();
|
||||
$response = new Response($code, ['Location' => $url]);
|
||||
|
||||
$this->close($response);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,12 +348,30 @@ class Grav extends Container
|
||||
|
||||
header("HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}");
|
||||
foreach ($response->getHeaders() as $key => $values) {
|
||||
// Skip internal Grav headers.
|
||||
if (strpos($key, 'Grav-Internal-') === 0) {
|
||||
continue;
|
||||
}
|
||||
foreach ($values as $i => $value) {
|
||||
header($key . ': ' . $value, $i === 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the system locale based on the language and configuration
|
||||
*/
|
||||
public function setLocale()
|
||||
{
|
||||
// Initialize Locale if set and configured.
|
||||
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
|
||||
$language = $this['language']->getLanguage();
|
||||
setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
|
||||
} elseif ($this['config']->get('system.default_locale')) {
|
||||
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires an event with optional parameters.
|
||||
*
|
||||
@@ -362,6 +385,10 @@ class Grav extends Container
|
||||
/** @var EventDispatcher $events */
|
||||
$events = $this['events'];
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
$debugger->addEvent($eventName, $event, $events);
|
||||
|
||||
return $events->dispatch($eventName, $event);
|
||||
}
|
||||
|
||||
@@ -470,9 +497,7 @@ class Grav extends Container
|
||||
return $container;
|
||||
};
|
||||
|
||||
$container->measureTime('_services', 'Services', function () use ($container) {
|
||||
$container->registerServices();
|
||||
});
|
||||
$container->registerServices();
|
||||
|
||||
return $container;
|
||||
}
|
||||
@@ -528,7 +553,7 @@ class Grav extends Container
|
||||
$path_parts = pathinfo($path);
|
||||
|
||||
/** @var PageInterface $page */
|
||||
$page = $this['pages']->dispatch($path_parts['dirname'], true);
|
||||
$page = $this['pages']->find($path_parts['dirname'], true);
|
||||
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
|
||||
@@ -190,7 +190,7 @@ class Truncator {
|
||||
* Clean extra code
|
||||
*
|
||||
* @param DOMDocument $doc
|
||||
* @param $container
|
||||
* @param DOMDocument $container
|
||||
* @return string
|
||||
*/
|
||||
private static function getCleanedHTML(DOMDocument $doc, $container)
|
||||
@@ -203,8 +203,7 @@ class Truncator {
|
||||
$doc->appendChild($container->firstChild);
|
||||
}
|
||||
|
||||
$html = trim($doc->saveHTML());
|
||||
return $html;
|
||||
return trim($doc->saveHTML());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,17 +241,20 @@ class Truncator {
|
||||
$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 = '';
|
||||
$open_tags = [];
|
||||
|
||||
foreach ($lines as $line_matchings) {
|
||||
// if there is any html-tag in this line, handle it and add it (uncounted) to the output
|
||||
if (!empty($line_matchings[1])) {
|
||||
@@ -308,22 +310,22 @@ class Truncator {
|
||||
} else {
|
||||
if (strlen($text) <= $length) {
|
||||
return $text;
|
||||
} else {
|
||||
$truncate = substr($text, 0, $length - strlen($ending));
|
||||
}
|
||||
|
||||
$truncate = substr($text, 0, $length - strlen($ending));
|
||||
}
|
||||
// if the words shouldn't be cut in the middle...
|
||||
if (!$exact) {
|
||||
// ...search the last occurance of a space...
|
||||
$spacepos = strrpos($truncate, ' ');
|
||||
if (isset($spacepos)) {
|
||||
if (false !== $spacepos) {
|
||||
// ...and cut the text in this position
|
||||
$truncate = substr($truncate, 0, $spacepos);
|
||||
}
|
||||
}
|
||||
// add the defined ending to the text
|
||||
$truncate .= $ending;
|
||||
if($considerHtml) {
|
||||
if (isset($open_tags)) {
|
||||
// close all unclosed html-tags
|
||||
foreach ($open_tags as $tag) {
|
||||
$truncate .= '</' . $tag . '>';
|
||||
|
||||
@@ -16,13 +16,15 @@ use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class YamlLinter
|
||||
{
|
||||
public static function lint()
|
||||
public static function lint(string $folder = null)
|
||||
{
|
||||
$errors = static::lintConfig();
|
||||
$errors = $errors + static::lintPages();
|
||||
$errors = $errors + static::lintBlueprints();
|
||||
|
||||
return $errors;
|
||||
if (null !== $folder) {
|
||||
$folder = $folder ?: GRAV_ROOT;
|
||||
|
||||
return static::recurseFolder($folder);
|
||||
}
|
||||
|
||||
return array_merge(static::lintConfig(), static::lintPages(), static::lintBlueprints());
|
||||
}
|
||||
|
||||
public static function lintPages()
|
||||
@@ -47,7 +49,7 @@ class YamlLinter
|
||||
return static::recurseFolder('blueprints://');
|
||||
}
|
||||
|
||||
public static function recurseFolder($path, $extensions = 'md|yaml')
|
||||
public static function recurseFolder($path, $extensions = '(md|yaml)')
|
||||
{
|
||||
$lint_errors = [];
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Language\Language;
|
||||
|
||||
/**
|
||||
* This file was originally part of the Akelos Framework
|
||||
*/
|
||||
@@ -24,6 +26,7 @@ class Inflector
|
||||
public static function init()
|
||||
{
|
||||
if (empty(static::$plural)) {
|
||||
/** @var Language $language */
|
||||
$language = Grav::instance()['language'];
|
||||
static::$plural = $language->translate('GRAV.INFLECTOR_PLURALS', null, true) ?: [];
|
||||
static::$singular = $language->translate('GRAV.INFLECTOR_SINGULAR', null, true) ?: [];
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Grav\Common\Language;
|
||||
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Config\Config;
|
||||
use Negotiation\AcceptLanguage;
|
||||
@@ -16,20 +17,22 @@ use Negotiation\LanguageNegotiator;
|
||||
|
||||
class Language
|
||||
{
|
||||
/** @var Grav */
|
||||
protected $grav;
|
||||
protected $enabled = true;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $languages = [];
|
||||
protected $page_extensions = [];
|
||||
protected $fallback_languages = [];
|
||||
protected $default;
|
||||
protected $active = null;
|
||||
|
||||
/** @var Config $config */
|
||||
/** @var Config */
|
||||
protected $config;
|
||||
|
||||
protected $enabled = true;
|
||||
|
||||
/** @var array */
|
||||
protected $languages = [];
|
||||
protected $fallback_languages = [];
|
||||
protected $fallback_extensions = [];
|
||||
protected $page_extesions = [];
|
||||
protected $default;
|
||||
protected $active;
|
||||
|
||||
protected $http_accept_language;
|
||||
protected $lang_in_url = false;
|
||||
|
||||
@@ -42,7 +45,12 @@ class Language
|
||||
{
|
||||
$this->grav = $grav;
|
||||
$this->config = $grav['config'];
|
||||
$this->languages = $this->config->get('system.languages.supported', []);
|
||||
$languages = $this->config->get('system.languages.supported', []);
|
||||
foreach ($languages as &$language) {
|
||||
$language = (string)$language;
|
||||
}
|
||||
$this->languages = $languages;
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
@@ -58,7 +66,7 @@ class Language
|
||||
$this->default = reset($this->languages);
|
||||
}
|
||||
|
||||
$this->page_extensions = null;
|
||||
$this->resetFallbackPageExtensions();
|
||||
|
||||
if (empty($this->languages)) {
|
||||
$this->enabled = false;
|
||||
@@ -93,20 +101,22 @@ class Language
|
||||
public function setLanguages($langs)
|
||||
{
|
||||
$this->languages = $langs;
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pipe-separated string of available languages
|
||||
*
|
||||
* @param string|null $delimiter Delimiter to be quoted.
|
||||
* @return string
|
||||
*/
|
||||
public function getAvailable()
|
||||
public function getAvailable($delimiter = null)
|
||||
{
|
||||
$languagesArray = $this->languages; //Make local copy
|
||||
|
||||
$languagesArray = array_map(function($value) {
|
||||
return preg_quote($value);
|
||||
$languagesArray = array_map(function($value) use ($delimiter) {
|
||||
return preg_quote($value, $delimiter);
|
||||
}, $languagesArray);
|
||||
|
||||
sort($languagesArray);
|
||||
@@ -143,6 +153,7 @@ class Language
|
||||
*/
|
||||
public function setDefault($lang)
|
||||
{
|
||||
$lang = (string)$lang;
|
||||
if ($this->validate($lang)) {
|
||||
$this->default = $lang;
|
||||
|
||||
@@ -171,7 +182,12 @@ class Language
|
||||
*/
|
||||
public function setActive($lang)
|
||||
{
|
||||
$lang = (string)$lang;
|
||||
if ($this->validate($lang)) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addMessage('Active language set to ' . $lang, 'debug');
|
||||
|
||||
$this->active = $lang;
|
||||
|
||||
return $lang;
|
||||
@@ -196,7 +212,7 @@ class Language
|
||||
// Try setting language from prefix of URL (/en/blah/blah).
|
||||
if (preg_match($regex, $uri, $matches)) {
|
||||
$this->lang_in_url = true;
|
||||
$this->active = $matches[2];
|
||||
$this->setActive($matches[2]);
|
||||
$uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1);
|
||||
|
||||
// Store in session if language is different.
|
||||
@@ -210,7 +226,7 @@ class Language
|
||||
// Try getting language from the session, else no active.
|
||||
if (isset($this->grav['session']) && $this->grav['session']->isStarted() &&
|
||||
$this->config->get('system.languages.session_store_active', true)) {
|
||||
$this->active = $this->grav['session']->active_language ?: null;
|
||||
$this->setActive($this->grav['session']->active_language ?: null);
|
||||
}
|
||||
// if still null, try from http_accept_language header
|
||||
if ($this->active === null &&
|
||||
@@ -221,9 +237,9 @@ class Language
|
||||
$best_language = $negotiator->getBest($accept, $this->languages);
|
||||
|
||||
if ($best_language instanceof AcceptLanguage) {
|
||||
$this->active = $best_language->getType();
|
||||
$this->setActive($best_language->getType());
|
||||
} else {
|
||||
$this->active = $this->getDefault();
|
||||
$this->setActive($this->getDefault());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -275,52 +291,67 @@ class Language
|
||||
return (bool) $this->lang_in_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full list of used language page extensions: [''=>'.md', 'en'=>'.en.md', ...]
|
||||
*
|
||||
* @param string|null $fileExtension
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPageExtensions($fileExtension = null)
|
||||
{
|
||||
$fileExtension = $fileExtension ?: CONTENT_EXT;
|
||||
|
||||
if (!isset($this->fallback_extensions[$fileExtension])) {
|
||||
$extensions[''] = $fileExtension;
|
||||
foreach ($this->languages as $code) {
|
||||
$extensions[$code] = ".{$code}{$fileExtension}";
|
||||
}
|
||||
|
||||
$this->fallback_extensions[$fileExtension] = $extensions;
|
||||
}
|
||||
|
||||
return $this->fallback_extensions[$fileExtension];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of valid extensions with active first, then fallback extensions
|
||||
*
|
||||
* @param string|null $file_ext
|
||||
*
|
||||
* @return array
|
||||
* @param string|null $fileExtension
|
||||
* @param string|null $languageCode
|
||||
* @param bool $assoc Return values in ['en' => '.en.md', ...] format.
|
||||
* @return array Key is the language code, value is the file extension to be used.
|
||||
*/
|
||||
public function getFallbackPageExtensions($file_ext = null)
|
||||
public function getFallbackPageExtensions(string $fileExtension = null, string $languageCode = null, bool $assoc = false)
|
||||
{
|
||||
if (empty($this->page_extensions)) {
|
||||
if (!$file_ext) {
|
||||
$file_ext = CONTENT_EXT;
|
||||
$fileExtension = $fileExtension ?: CONTENT_EXT;
|
||||
$key = $fileExtension . '-' . ($languageCode ?? 'default') . '-' . (int)$assoc;
|
||||
|
||||
if (!isset($this->fallback_extensions[$key])) {
|
||||
$all = $this->getPageExtensions($fileExtension);
|
||||
$list = [];
|
||||
$fallback = $this->getFallbackLanguages($languageCode, true);
|
||||
foreach ($fallback as $code) {
|
||||
$ext = $all[$code] ?? null;
|
||||
if (null !== $ext) {
|
||||
$list[$code] = $ext;
|
||||
}
|
||||
}
|
||||
if (!$assoc) {
|
||||
$list = array_values($list);
|
||||
}
|
||||
|
||||
if ($this->enabled()) {
|
||||
$valid_lang_extensions = [];
|
||||
foreach ($this->languages as $lang) {
|
||||
$valid_lang_extensions[] = '.' . $lang . $file_ext;
|
||||
}
|
||||
$this->fallback_extensions[$key] = $list;
|
||||
|
||||
if ($this->active) {
|
||||
$active_extension = '.' . $this->active . $file_ext;
|
||||
$key = \array_search($active_extension, $valid_lang_extensions, true);
|
||||
|
||||
// Default behavior is to find any language other than active
|
||||
if ($this->config->get('system.languages.pages_fallback_only')) {
|
||||
$slice = \array_slice($valid_lang_extensions, 0, $key+1);
|
||||
$valid_lang_extensions = array_reverse($slice);
|
||||
} else {
|
||||
unset($valid_lang_extensions[$key]);
|
||||
array_unshift($valid_lang_extensions, $active_extension);
|
||||
}
|
||||
}
|
||||
$valid_lang_extensions[] = $file_ext;
|
||||
$this->page_extensions = $valid_lang_extensions;
|
||||
} else {
|
||||
$this->page_extensions = (array)$file_ext;
|
||||
}
|
||||
/** @var Debugger $debugger */
|
||||
//$debugger = $this->grav['debugger'];
|
||||
//$debugger->addMessage("Language fallback extensions for {$languageCode}", 'debug', $list);
|
||||
}
|
||||
|
||||
return $this->page_extensions;
|
||||
return $this->fallback_extensions[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the page_extensions value.
|
||||
* Resets the fallback_languages value.
|
||||
*
|
||||
* Useful to re-initialize the pages and change site language at runtime, example:
|
||||
*
|
||||
@@ -332,33 +363,77 @@ class Language
|
||||
*/
|
||||
public function resetFallbackPageExtensions()
|
||||
{
|
||||
$this->page_extensions = null;
|
||||
$this->fallback_languages = [];
|
||||
$this->fallback_extensions = [];
|
||||
$this->page_extesions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of languages with active first, then fallback languages
|
||||
* Gets an array of languages with active first, then fallback languages.
|
||||
*
|
||||
*
|
||||
* @param string|null $languageCode
|
||||
* @param bool $includeDefault If true, list contains '', which can be used for default
|
||||
* @return array
|
||||
*/
|
||||
public function getFallbackLanguages()
|
||||
public function getFallbackLanguages(string $languageCode = null, bool $includeDefault = false)
|
||||
{
|
||||
if (empty($this->fallback_languages)) {
|
||||
if ($this->enabled()) {
|
||||
$fallback_languages = $this->languages;
|
||||
|
||||
if ($this->active) {
|
||||
$active_extension = $this->active;
|
||||
$key = \array_search($active_extension, $fallback_languages, true);
|
||||
unset($fallback_languages[$key]);
|
||||
array_unshift($fallback_languages, $active_extension);
|
||||
}
|
||||
$this->fallback_languages = $fallback_languages;
|
||||
}
|
||||
// always add english in case a translation doesn't exist
|
||||
$this->fallback_languages[] = 'en';
|
||||
// Handle default.
|
||||
if ($languageCode === '' || !$this->enabled()) {
|
||||
return [''];
|
||||
}
|
||||
|
||||
return $this->fallback_languages;
|
||||
$default = $this->getDefault() ?? 'en';
|
||||
$active = $languageCode ?? $this->getActive() ?? $default;
|
||||
$key = $active . '-' . (int)$includeDefault;
|
||||
|
||||
if (!isset($this->fallback_languages[$key])) {
|
||||
$fallback = $this->config->get('system.languages.content_fallback.' . $active);
|
||||
$fallback_languages = [];
|
||||
|
||||
if (null === $fallback && $this->config->get('system.languages.pages_fallback_only', false)) {
|
||||
// Special fallback list returns itself and all the previous items in reverse order:
|
||||
// active: 'v2', languages: ['v1', 'v2', 'v3', 'v4'] => ['v2', 'v1', '']
|
||||
if ($includeDefault) {
|
||||
$fallback_languages[''] = '';
|
||||
}
|
||||
foreach ($this->languages as $code) {
|
||||
$fallback_languages[$code] = $code;
|
||||
if ($code === $active) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$fallback_languages = array_reverse($fallback_languages);
|
||||
|
||||
} else {
|
||||
if (null === $fallback) {
|
||||
$fallback = [$default];
|
||||
} elseif (!is_array($fallback)) {
|
||||
$fallback = is_string($fallback) && $fallback !== '' ? explode(',', $fallback) : [];
|
||||
}
|
||||
array_unshift($fallback, $active);
|
||||
$fallback = array_unique($fallback);
|
||||
|
||||
foreach ($fallback as $code) {
|
||||
// Default fallback list has active language followed by default language and extensionless file:
|
||||
// active: 'fi', default: 'en', languages: ['sv', 'en', 'de', 'fi'] => ['fi', 'en', '']
|
||||
$fallback_languages[$code] = $code;
|
||||
if ($includeDefault && $code === $default) {
|
||||
$fallback_languages[''] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fallback_languages = array_values($fallback_languages);
|
||||
|
||||
$this->fallback_languages[$key] = $fallback_languages;
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
//$debugger = $this->grav['debugger'];
|
||||
//$debugger->addMessage("Language fallback for {$active}", 'debug', $fallback_languages);
|
||||
}
|
||||
|
||||
return $this->fallback_languages[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,18 +469,12 @@ class Language
|
||||
}
|
||||
|
||||
if ($this->config->get('system.languages.translations', true)) {
|
||||
if ($this->enabled() && $lookup) {
|
||||
if (empty($languages)) {
|
||||
if ($this->config->get('system.languages.translations_fallback', true)) {
|
||||
$languages = $this->getFallbackLanguages();
|
||||
} else {
|
||||
$languages = (array)$this->getLanguage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$languages = ['en'];
|
||||
if ($this->enabled() && $lookup && empty($languages)) {
|
||||
$languages = $this->getTranslatedLanguages();
|
||||
}
|
||||
|
||||
$languages = $languages ?: ['en'];
|
||||
|
||||
foreach ((array)$languages as $lang) {
|
||||
$translation = $this->getTranslation($lang, $lookup, $array_support);
|
||||
|
||||
@@ -439,18 +508,12 @@ class Language
|
||||
public function translateArray($key, $index, $languages = null, $html_out = false)
|
||||
{
|
||||
if ($this->config->get('system.languages.translations', true)) {
|
||||
if ($this->enabled() && $key) {
|
||||
if (empty($languages)) {
|
||||
if ($this->config->get('system.languages.translations_fallback', true)) {
|
||||
$languages = $this->getFallbackLanguages();
|
||||
} else {
|
||||
$languages = (array)$this->getDefault();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$languages = ['en'];
|
||||
if ($this->enabled() && $key && empty($languages)) {
|
||||
$languages = $this->getTranslatedLanguages();
|
||||
}
|
||||
|
||||
$languages = $languages ?: ['en'];
|
||||
|
||||
foreach ((array)$languages as $lang) {
|
||||
$translation_array = (array)Grav::instance()['languages']->get($lang . '.' . $key, null);
|
||||
if ($translation_array && array_key_exists($index, $translation_array)) {
|
||||
@@ -534,4 +597,27 @@ class Language
|
||||
return LanguageCodes::get($code, $type);
|
||||
}
|
||||
|
||||
public function __debugInfo()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
unset($vars['grav'], $vars['config']);
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getTranslatedLanguages(): array
|
||||
{
|
||||
if ($this->config->get('system.languages.translations_fallback', true)) {
|
||||
$languages = $this->getFallbackLanguages();
|
||||
} else {
|
||||
$languages = [$this->getLanguage()];
|
||||
}
|
||||
|
||||
$languages[] = 'en';
|
||||
|
||||
return array_values(array_unique($languages));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,4 +202,13 @@ class LanguageCodes
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getList($native = true)
|
||||
{
|
||||
$list = [];
|
||||
foreach (static::$codes as $key => $names) {
|
||||
$list[$key] = $native ? $names['nativeName'] : $names['name'];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
trait MediaTrait
|
||||
{
|
||||
protected $media;
|
||||
protected $_loadMedia = true;
|
||||
|
||||
/**
|
||||
* Get filesystem path to the associated media.
|
||||
@@ -40,11 +41,14 @@ trait MediaTrait
|
||||
/**
|
||||
* Get URI ot the associated media. Method will return null if path isn't URI.
|
||||
*
|
||||
* @return null|string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMediaUri()
|
||||
{
|
||||
$folder = $this->getMediaFolder();
|
||||
if (!$folder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (strpos($folder, '://')) {
|
||||
return $folder;
|
||||
@@ -73,7 +77,7 @@ trait MediaTrait
|
||||
// Use cached media if possible.
|
||||
$cacheKey = md5('media' . $this->getCacheKey());
|
||||
if (!$media = $cache->get($cacheKey)) {
|
||||
$media = new Media($this->getMediaFolder(), $this->getMediaOrder());
|
||||
$media = new Media($this->getMediaFolder(), $this->getMediaOrder(), $this->_loadMedia);
|
||||
$cache->set($cacheKey, $media);
|
||||
}
|
||||
$this->media = $media;
|
||||
|
||||
@@ -11,10 +11,11 @@ namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
class Collection extends Iterator
|
||||
class Collection extends Iterator implements PageCollectionInterface
|
||||
{
|
||||
/**
|
||||
* @var Pages
|
||||
@@ -51,6 +52,20 @@ class Collection extends Iterator
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->params = array_merge($this->params, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single page to a collection
|
||||
*
|
||||
@@ -94,10 +109,10 @@ class Collection extends Iterator
|
||||
*
|
||||
* Merge another collection with the current collection
|
||||
*
|
||||
* @param Collection $collection
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(Collection $collection)
|
||||
public function merge(PageCollectionInterface $collection)
|
||||
{
|
||||
foreach($collection as $page) {
|
||||
$this->addPage($page);
|
||||
@@ -109,10 +124,10 @@ class Collection extends Iterator
|
||||
/**
|
||||
* Intersect another collection with the current collection
|
||||
*
|
||||
* @param Collection $collection
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function intersect(Collection $collection)
|
||||
public function intersect(PageCollectionInterface $collection)
|
||||
{
|
||||
$array1 = $this->items;
|
||||
$array2 = $collection->toArray();
|
||||
@@ -124,20 +139,6 @@ class Collection extends Iterator
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->params = array_merge($this->params, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current page.
|
||||
*
|
||||
@@ -240,7 +241,7 @@ class Collection extends Iterator
|
||||
*
|
||||
* @return bool True if item is first.
|
||||
*/
|
||||
public function isFirst($path)
|
||||
public function isFirst($path): bool
|
||||
{
|
||||
return $this->items && $path === array_keys($this->items)[0];
|
||||
}
|
||||
@@ -252,7 +253,7 @@ class Collection extends Iterator
|
||||
*
|
||||
* @return bool True if item is last.
|
||||
*/
|
||||
public function isLast($path)
|
||||
public function isLast($path): bool
|
||||
{
|
||||
return $this->items && $path === array_keys($this->items)[\count($this->items) - 1];
|
||||
}
|
||||
@@ -301,7 +302,6 @@ class Collection extends Iterator
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,11 +309,13 @@ class Collection extends Iterator
|
||||
*
|
||||
* @param string $path the path the item
|
||||
*
|
||||
* @return int the index of the current page.
|
||||
* @return int|null The index of the current page, null if not found.
|
||||
*/
|
||||
public function currentPosition($path)
|
||||
public function currentPosition($path): ?int
|
||||
{
|
||||
return \array_search($path, \array_keys($this->items), true);
|
||||
$pos = \array_search($path, \array_keys($this->items), true);
|
||||
|
||||
return $pos !== false ? $pos : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,9 +10,18 @@
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use RocketTheme\Toolbox\ArrayTraits\Constructor;
|
||||
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccess;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
|
||||
use RocketTheme\Toolbox\ArrayTraits\NestedArrayAccessWithGetters;
|
||||
|
||||
class Header implements \ArrayAccess
|
||||
class Header implements \ArrayAccess, ExportInterface, \JsonSerializable
|
||||
{
|
||||
use NestedArrayAccess, Constructor;
|
||||
use NestedArrayAccessWithGetters, Constructor, Export;
|
||||
|
||||
protected $items;
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Page
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
interface PageCollectionInterface extends \Traversable, \ArrayAccess, \Countable, \Serializable
|
||||
{
|
||||
/**
|
||||
* Get the collection params
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function params();
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params);
|
||||
|
||||
/**
|
||||
* Add a single page to a collection
|
||||
*
|
||||
* @param PageInterface $page
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addPage(PageInterface $page);
|
||||
|
||||
/**
|
||||
* Add a page with path and slug
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $slug
|
||||
* @return $this
|
||||
*/
|
||||
//public function add($path, $slug);
|
||||
|
||||
/**
|
||||
*
|
||||
* Create a copy of this collection
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy();
|
||||
|
||||
/**
|
||||
*
|
||||
* Merge another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function merge(PageCollectionInterface $collection);
|
||||
|
||||
/**
|
||||
* Intersect another collection with the current collection
|
||||
*
|
||||
* @param PageCollectionInterface $collection
|
||||
* @return $this
|
||||
*/
|
||||
public function intersect(PageCollectionInterface $collection);
|
||||
|
||||
/**
|
||||
* Split collection into array of smaller collections.
|
||||
*
|
||||
* @param int $size
|
||||
* @return PageCollectionInterface[]
|
||||
*/
|
||||
public function batch($size);
|
||||
|
||||
/**
|
||||
* Remove item from the list.
|
||||
*
|
||||
* @param PageInterface|string|null $key
|
||||
*
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
//public function remove($key = null);
|
||||
|
||||
/**
|
||||
* Reorder collection.
|
||||
*
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array $manual
|
||||
* @param string $sort_flags
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function order($by, $dir = 'asc', $manual = null, $sort_flags = null);
|
||||
|
||||
/**
|
||||
* Check to see if this item is the first in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool True if item is first.
|
||||
*/
|
||||
public function isFirst($path): bool;
|
||||
|
||||
/**
|
||||
* Check to see if this item is the last in the collection.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return bool True if item is last.
|
||||
*/
|
||||
public function isLast($path): bool;
|
||||
|
||||
/**
|
||||
* Gets the previous sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return PageInterface The previous item.
|
||||
*/
|
||||
public function prevSibling($path);
|
||||
|
||||
/**
|
||||
* Gets the next sibling based on current position.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return PageInterface The next item.
|
||||
*/
|
||||
public function nextSibling($path);
|
||||
|
||||
/**
|
||||
* Returns the adjacent sibling based on a direction.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $direction either -1 or +1
|
||||
*
|
||||
* @return PageInterface|PageCollectionInterface The sibling item.
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1);
|
||||
|
||||
/**
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @param string $path the path the item
|
||||
*
|
||||
* @return int|null The index of the current page, null if not found.
|
||||
*/
|
||||
public function currentPosition($path): ?int;
|
||||
|
||||
/**
|
||||
* Returns the items between a set of date ranges of either the page date field (default) or
|
||||
* an arbitrary datetime page field where end date is optional
|
||||
* Dates can be passed in as text that strtotime() can process
|
||||
* http://php.net/manual/en/function.strtotime.php
|
||||
*
|
||||
* @param string $startDate
|
||||
* @param bool $endDate
|
||||
* @param string|null $field
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function dateRange($startDate, $endDate = false, $field = null);
|
||||
|
||||
/**
|
||||
* Creates new collection with only visible pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only visible pages
|
||||
*/
|
||||
public function visible();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-visible pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-visible pages
|
||||
*/
|
||||
public function nonVisible();
|
||||
|
||||
/**
|
||||
* Creates new collection with only modular pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only modular pages
|
||||
*/
|
||||
public function modular();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-modular pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-modular pages
|
||||
*/
|
||||
public function nonModular();
|
||||
|
||||
/**
|
||||
* Creates new collection with only published pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only published pages
|
||||
*/
|
||||
public function published();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-published pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-published pages
|
||||
*/
|
||||
public function nonPublished();
|
||||
|
||||
/**
|
||||
* Creates new collection with only routable pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only routable pages
|
||||
*/
|
||||
public function routable();
|
||||
|
||||
/**
|
||||
* Creates new collection with only non-routable pages
|
||||
*
|
||||
* @return PageCollectionInterface The collection with only non-routable pages
|
||||
*/
|
||||
public function nonRoutable();
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of the specified type
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofType($type);
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified types
|
||||
*
|
||||
* @param string[] $types
|
||||
*
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofOneOfTheseTypes($types);
|
||||
|
||||
/**
|
||||
* Creates new collection with only pages of one of the specified access levels
|
||||
*
|
||||
* @param array $accessLevels
|
||||
*
|
||||
* @return PageCollectionInterface The collection
|
||||
*/
|
||||
public function ofOneOfTheseAccessLevels($accessLevels);
|
||||
|
||||
/**
|
||||
* Get the extended version of this Collection with each page keyed by route
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function toExtendedArray();
|
||||
}
|
||||
@@ -36,6 +36,13 @@ interface PageContentInterface
|
||||
*/
|
||||
public function summary($size = null, $textOnly = false);
|
||||
|
||||
/**
|
||||
* Sets the summary of the page
|
||||
*
|
||||
* @param string $summary Summary
|
||||
*/
|
||||
public function setSummary($summary);
|
||||
|
||||
/**
|
||||
* Gets and Sets the content based on content portion of the .md file
|
||||
*
|
||||
@@ -64,7 +71,7 @@ interface PageContentInterface
|
||||
*
|
||||
* @param string|null $var
|
||||
*
|
||||
* @return null
|
||||
* @return string
|
||||
*/
|
||||
public function rawMarkdown($var = null);
|
||||
|
||||
@@ -167,7 +174,7 @@ interface PageContentInterface
|
||||
*
|
||||
* @param int $var
|
||||
*
|
||||
* @return int|bool
|
||||
* @return string|bool
|
||||
*/
|
||||
public function order($var = null);
|
||||
|
||||
|
||||
30
system/src/Grav/Common/Page/Interfaces/PageFormInterface.php
Normal file
30
system/src/Grav/Common/Page/Interfaces/PageFormInterface.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page\Interfaces;
|
||||
|
||||
interface PageFormInterface
|
||||
{
|
||||
/**
|
||||
* Return all the forms which are associated to this page.
|
||||
*
|
||||
* Forms are returned as [name => blueprint, ...], where blueprint follows the regular form blueprint format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
//public function getForms(): array;
|
||||
|
||||
/**
|
||||
* Add forms to this page.
|
||||
*
|
||||
* @param array $new
|
||||
* @param bool $override
|
||||
* @return $this
|
||||
*/
|
||||
public function addForms(array $new/*, $override = true*/);
|
||||
|
||||
/**
|
||||
* Alias of $this->getForms();
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function forms();//: array;
|
||||
}
|
||||
@@ -14,6 +14,12 @@ use Grav\Common\Media\Interfaces\MediaInterface;
|
||||
/**
|
||||
* Class implements page interface.
|
||||
*/
|
||||
interface PageInterface extends PageContentInterface, PageRoutableInterface, PageTranslateInterface, MediaInterface, PageLegacyInterface
|
||||
interface PageInterface extends
|
||||
PageContentInterface,
|
||||
PageFormInterface,
|
||||
PageRoutableInterface,
|
||||
PageTranslateInterface,
|
||||
MediaInterface,
|
||||
PageLegacyInterface
|
||||
{
|
||||
}
|
||||
|
||||
@@ -51,13 +51,6 @@ interface PageLegacyInterface
|
||||
|
||||
public function httpHeaders();
|
||||
|
||||
/**
|
||||
* Sets the summary of the page
|
||||
*
|
||||
* @param string $summary Summary
|
||||
*/
|
||||
public function setSummary($summary);
|
||||
|
||||
/**
|
||||
* Get the contentMeta array and initialize content first if it's not already
|
||||
*
|
||||
@@ -69,7 +62,7 @@ interface PageLegacyInterface
|
||||
* Add an entry to the page's contentMeta array
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function addContentMeta($name, $value);
|
||||
|
||||
@@ -229,9 +222,9 @@ interface PageLegacyInterface
|
||||
* Allows a page to override the output render format, usually the extension provided
|
||||
* in the URL. (e.g. `html`, `json`, `xml`, etc).
|
||||
*
|
||||
* @param null $var
|
||||
* @param string|null $var
|
||||
*
|
||||
* @return null
|
||||
* @return string
|
||||
*/
|
||||
public function templateFormat($var = null);
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ interface PageRoutableInterface
|
||||
/**
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @return int the index of the current page.
|
||||
* @return int|null The index of the current page.
|
||||
*/
|
||||
public function currentPosition();
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ class Excerpts
|
||||
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
|
||||
|
||||
/** @var PageInterface $ext_page */
|
||||
$ext_page = $grav['pages']->dispatch($page_route, true);
|
||||
$ext_page = $grav['pages']->find($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->getMedia();
|
||||
} else {
|
||||
|
||||
@@ -78,21 +78,21 @@ class Media extends AbstractMedia
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$config = Grav::instance()['config'];
|
||||
$locator = Grav::instance()['locator'];
|
||||
$exif_reader = isset(Grav::instance()['exif']) ? Grav::instance()['exif']->getReader() : false;
|
||||
$media_types = array_keys(Grav::instance()['config']->get('media.types'));
|
||||
$path = $this->getPath();
|
||||
|
||||
// Handle special cases where page doesn't exist in filesystem.
|
||||
if (!is_dir($this->getPath())) {
|
||||
if (!$path || !is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$iterator = new \FilesystemIterator($this->getPath(), \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
|
||||
$iterator = new \FilesystemIterator($path, \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::SKIP_DOTS);
|
||||
|
||||
$media = [];
|
||||
|
||||
/** @var \DirectoryIterator $info */
|
||||
foreach ($iterator as $path => $info) {
|
||||
foreach ($iterator as $file => $info) {
|
||||
// Ignore folders and Markdown files.
|
||||
if (!$info->isFile() || $info->getExtension() === 'md' || strpos($info->getFilename(), '.') === 0) {
|
||||
continue;
|
||||
@@ -106,9 +106,9 @@ class Media extends AbstractMedia
|
||||
}
|
||||
|
||||
if ($type === 'alternative') {
|
||||
$media["{$basename}.{$ext}"][$type][$extra] = ['file' => $path, 'size' => $info->getSize()];
|
||||
$media["{$basename}.{$ext}"][$type][$extra] = ['file' => $file, 'size' => $info->getSize()];
|
||||
} else {
|
||||
$media["{$basename}.{$ext}"][$type] = ['file' => $path, 'size' => $info->getSize()];
|
||||
$media["{$basename}.{$ext}"][$type] = ['file' => $file, 'size' => $info->getSize()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ class Media extends AbstractMedia
|
||||
$alt['file']->set('size', $alt['size']);
|
||||
}
|
||||
}
|
||||
unset($alt);
|
||||
}
|
||||
|
||||
$file_path = null;
|
||||
|
||||
@@ -109,7 +109,7 @@ class ImageFile extends Image
|
||||
* Gets the hash.
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @param [] $extras
|
||||
* @param array $extras
|
||||
* @return null
|
||||
*/
|
||||
public function getHash($type = 'guess', $quality = 80, $extras = [])
|
||||
|
||||
@@ -15,17 +15,17 @@ use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Markdown\ParsedownExtra;
|
||||
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Media\Traits\MediaTrait;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use Grav\Common\Taxonomy;
|
||||
use Grav\Common\Page\Traits\PageFormTrait;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Yaml;
|
||||
use Negotiation\Accept;
|
||||
use Negotiation\Negotiator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\MarkdownFile;
|
||||
|
||||
@@ -33,6 +33,7 @@ define('PAGE_ORDER_PREFIX_REGEX', '/^[0-9]+\./u');
|
||||
|
||||
class Page implements PageInterface
|
||||
{
|
||||
use PageFormTrait;
|
||||
use MediaTrait;
|
||||
|
||||
/**
|
||||
@@ -93,11 +94,9 @@ class Page implements PageInterface
|
||||
protected $ssl;
|
||||
protected $template_format;
|
||||
protected $debugger;
|
||||
/** @var array */
|
||||
protected $forms;
|
||||
|
||||
/**
|
||||
* @var PageInterface Unmodified (original) version of the page. Used for copying and moving the page.
|
||||
* @var PageInterface|null Unmodified (original) version of the page. Used for copying and moving the page.
|
||||
*/
|
||||
private $_original;
|
||||
|
||||
@@ -188,16 +187,32 @@ class Page implements PageInterface
|
||||
*/
|
||||
public function translatedLanguages($onlyPublished = false)
|
||||
{
|
||||
$filename = substr($this->name, 0, -(strlen($this->extension())));
|
||||
$config = Grav::instance()['config'];
|
||||
$languages = $config->get('system.languages.supported', []);
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
$languages = $language->getLanguages();
|
||||
$defaultCode = $language->getDefault();
|
||||
|
||||
$name = substr($this->name, 0, -strlen($this->extension()));
|
||||
$translatedLanguages = [];
|
||||
|
||||
foreach ($languages as $language) {
|
||||
$path = $this->path . DS . $this->folder . DS . $filename . '.' . $language . '.md';
|
||||
if (file_exists($path)) {
|
||||
foreach ($languages as $languageCode) {
|
||||
$languageExtension = ".{$languageCode}.md";
|
||||
$path = $this->path . DS . $this->folder . DS . $name . $languageExtension;
|
||||
$exists = file_exists($path);
|
||||
|
||||
// Default language may be saved without language file location.
|
||||
if (!$exists && $languageCode === $defaultCode) {
|
||||
$languageExtension = '.md';
|
||||
$path = $this->path . DS . $this->folder . DS . $name . $languageExtension;
|
||||
$exists = file_exists($path);
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
$aPage = new Page();
|
||||
$aPage->init(new \SplFileInfo($path), $language . '.md');
|
||||
$aPage->init(new \SplFileInfo($path), $languageExtension);
|
||||
|
||||
$route = $aPage->header()->routes['default'] ?? $aPage->rawRoute();
|
||||
if (!$route) {
|
||||
@@ -208,7 +223,7 @@ class Page implements PageInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$translatedLanguages[$language] = $route;
|
||||
$translatedLanguages[$languageCode] = $route;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,22 +239,41 @@ class Page implements PageInterface
|
||||
*/
|
||||
public function untranslatedLanguages($includeUnpublished = false)
|
||||
{
|
||||
$filename = substr($this->name, 0, -strlen($this->extension()));
|
||||
$config = Grav::instance()['config'];
|
||||
$languages = $config->get('system.languages.supported', []);
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $grav['language'];
|
||||
|
||||
$languages = $language->getLanguages();
|
||||
$defaultCode = $language->getDefault();
|
||||
|
||||
$name = substr($this->name, 0, -strlen($this->extension()));
|
||||
$untranslatedLanguages = [];
|
||||
|
||||
foreach ($languages as $language) {
|
||||
$path = $this->path . DS . $this->folder . DS . $filename . '.' . $language . '.md';
|
||||
if (file_exists($path)) {
|
||||
$aPage = new Page();
|
||||
$aPage->init(new \SplFileInfo($path), $language . '.md');
|
||||
if ($includeUnpublished && !$aPage->published()) {
|
||||
$untranslatedLanguages[] = $language;
|
||||
}
|
||||
} else {
|
||||
$untranslatedLanguages[] = $language;
|
||||
foreach ($languages as $languageCode) {
|
||||
$path = $this->path . DS . $this->folder . DS . $name . '.' . $languageCode . '.md';
|
||||
$exists = file_exists($path);
|
||||
|
||||
// Default language may be saved without language file location.
|
||||
if (!$exists && $languageCode === $defaultCode) {
|
||||
$path = $this->path . DS . $this->folder . DS . $name . '.md';
|
||||
$exists = file_exists($path);
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
if ($includeUnpublished) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$aPage = new Page();
|
||||
$aPage->init(new \SplFileInfo($path), $languageCode . '.md');
|
||||
|
||||
if (!$aPage->published()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$untranslatedLanguages[] = $languageCode;
|
||||
}
|
||||
|
||||
return $untranslatedLanguages;
|
||||
@@ -568,8 +602,7 @@ class Page implements PageInterface
|
||||
$content = $textOnly ? strip_tags($this->content()) : $this->content();
|
||||
$summary_size = $this->summary_size;
|
||||
} else {
|
||||
$content = strip_tags($this->summary);
|
||||
// Use mb_strwidth to deal with the 2 character widths characters
|
||||
$content = $textOnly ? strip_tags($this->summary) : $this->summary;
|
||||
$summary_size = mb_strwidth($content, 'utf-8');
|
||||
}
|
||||
|
||||
@@ -772,7 +805,7 @@ class Page implements PageInterface
|
||||
* Add an entry to the page's contentMeta array
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function addContentMeta($name, $value)
|
||||
{
|
||||
@@ -784,16 +817,12 @@ class Page implements PageInterface
|
||||
*
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return string
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getContentMeta($name = null)
|
||||
{
|
||||
if ($name) {
|
||||
if (isset($this->content_meta[$name])) {
|
||||
return $this->content_meta[$name];
|
||||
}
|
||||
|
||||
return null;
|
||||
return $this->content_meta[$name] ?? null;
|
||||
}
|
||||
|
||||
return $this->content_meta;
|
||||
@@ -922,13 +951,16 @@ class Page implements PageInterface
|
||||
return $this->slug();
|
||||
}
|
||||
if ($name === 'name') {
|
||||
$name = $this->name();
|
||||
$language = $this->language() ? '.' . $this->language() : '';
|
||||
$name_val = str_replace($language . '.md', '', $this->name());
|
||||
$pattern = '%(' . preg_quote($language, '%') . ')?\.md$%';
|
||||
$name = preg_replace($pattern, '', $name);
|
||||
|
||||
if ($this->modular()) {
|
||||
return 'modular/' . $name_val;
|
||||
return 'modular/' . $name;
|
||||
}
|
||||
|
||||
return $name_val;
|
||||
return $name;
|
||||
}
|
||||
if ($name === 'media') {
|
||||
return $this->media()->all();
|
||||
@@ -1205,93 +1237,12 @@ class Page implements PageInterface
|
||||
return $this->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns normalized list of name => form pairs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function forms()
|
||||
{
|
||||
if (null === $this->forms) {
|
||||
$header = $this->header();
|
||||
|
||||
// Call event to allow filling the page header form dynamically (e.g. use case: Comments plugin)
|
||||
$grav = Grav::instance();
|
||||
$grav->fireEvent('onFormPageHeaderProcessed', new Event(['page' => $this, 'header' => $header]));
|
||||
|
||||
$rules = $header->rules ?? null;
|
||||
if (!\is_array($rules)) {
|
||||
$rules = [];
|
||||
}
|
||||
|
||||
$forms = [];
|
||||
|
||||
// First grab page.header.form
|
||||
$form = $this->normalizeForm($header->form ?? null, null, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
|
||||
// Append page.header.forms (override singular form if it clashes)
|
||||
$headerForms = $header->forms ?? null;
|
||||
if (\is_array($headerForms)) {
|
||||
foreach ($headerForms as $name => $form) {
|
||||
$form = $this->normalizeForm($form, $name, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->forms = $forms;
|
||||
}
|
||||
|
||||
return $this->forms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $new
|
||||
*/
|
||||
public function addForms(array $new)
|
||||
{
|
||||
// Initialize forms.
|
||||
$this->forms();
|
||||
|
||||
foreach ($new as $form) {
|
||||
$form = $this->normalizeForm($form);
|
||||
if ($form) {
|
||||
$this->forms[$form['name']] = $form;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function normalizeForm($form, $name = null, array $rules = [])
|
||||
{
|
||||
if (!\is_array($form)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore numeric indexes on name.
|
||||
if (!$name || (string)(int)$name === (string)$name) {
|
||||
$name = null;
|
||||
}
|
||||
|
||||
$name = $name ?? $form['name'] ?? $this->slug();
|
||||
|
||||
$formRules = $form['rules'] ?? null;
|
||||
if (!\is_array($formRules)) {
|
||||
$formRules = [];
|
||||
}
|
||||
|
||||
return ['name' => $name, 'rules' => $rules + $formRules] + $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the associated media as found in the page folder.
|
||||
*
|
||||
* @param Media $var Representation of associated media.
|
||||
*
|
||||
* @return Media Representation of associated media.
|
||||
* @return MediaCollectionInterface|Media Representation of associated media.
|
||||
*/
|
||||
public function media($var = null)
|
||||
{
|
||||
@@ -1371,67 +1322,32 @@ class Page implements PageInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a page to override the output render format, usually the extension provided
|
||||
* in the URL. (e.g. `html`, `json`, `xml`, etc).
|
||||
* Allows a page to override the output render format, usually the extension provided in the URL.
|
||||
* (e.g. `html`, `json`, `xml`, etc).
|
||||
*
|
||||
* @param null $var
|
||||
* @param string|null $var
|
||||
*
|
||||
* @return null
|
||||
* @return string
|
||||
*/
|
||||
public function templateFormat($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->template_format = $var;
|
||||
return $this->template_format;
|
||||
if (null !== $var) {
|
||||
$this->template_format = is_string($var) ? $var : null;
|
||||
}
|
||||
|
||||
if (isset($this->template_format)) {
|
||||
return $this->template_format;
|
||||
if (!isset($this->template_format)) {
|
||||
$this->template_format = ltrim($this->header->append_url_extension ?? Utils::getPageFormat(), '.');
|
||||
}
|
||||
|
||||
// Set from URL extension set on page
|
||||
$page_extension = trim($this->header->append_url_extension ?? '' , '.');
|
||||
if (!empty($page_extension)) {
|
||||
$this->template_format = $page_extension;
|
||||
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
// Set from uri extension
|
||||
$uri_extension = Grav::instance()['uri']->extension();
|
||||
if (is_string($uri_extension)) {
|
||||
$this->template_format = $uri_extension;
|
||||
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
// Use content negotiation via the `accept:` header
|
||||
$http_accept = $_SERVER['HTTP_ACCEPT'] ?? null;
|
||||
if (is_string($http_accept)) {
|
||||
$negotiator = new Negotiator();
|
||||
|
||||
$supported_types = Utils::getSupportPageTypes(['html', 'json']);
|
||||
$priorities = Utils::getMimeTypes($supported_types);
|
||||
|
||||
$media_type = $negotiator->getBest($http_accept, $priorities);
|
||||
$mimetype = $media_type instanceof Accept ? $media_type->getValue() : '';
|
||||
|
||||
$this->template_format = Utils::getExtensionByMime($mimetype);
|
||||
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
// Last chance set a default type
|
||||
$this->template_format = 'html';
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and sets the extension field.
|
||||
*
|
||||
* @param null $var
|
||||
* @param string|null $var
|
||||
*
|
||||
* @return null|string
|
||||
* @return string
|
||||
*/
|
||||
public function extension($var = null)
|
||||
{
|
||||
@@ -1660,7 +1576,7 @@ class Page implements PageInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state of the debugger override etting for this page
|
||||
* Returns the state of the debugger override setting for this page
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -1689,9 +1605,10 @@ class Page implements PageInterface
|
||||
|
||||
$this->metadata = [];
|
||||
|
||||
$metadata = [];
|
||||
// Set the Generator tag
|
||||
$metadata['generator'] = 'GravCMS';
|
||||
$metadata = [
|
||||
'generator' => 'GravCMS'
|
||||
];
|
||||
|
||||
// Get initial metadata for the page
|
||||
$metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata'));
|
||||
@@ -1784,7 +1701,7 @@ class Page implements PageInterface
|
||||
*
|
||||
* @param int $var
|
||||
*
|
||||
* @return int|bool
|
||||
* @return string|bool
|
||||
*/
|
||||
public function order($var = null)
|
||||
{
|
||||
@@ -2040,7 +1957,7 @@ class Page implements PageInterface
|
||||
*
|
||||
* @param string $var redirect url
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function redirect($var = null)
|
||||
{
|
||||
@@ -2048,7 +1965,7 @@ class Page implements PageInterface
|
||||
$this->redirect = $var;
|
||||
}
|
||||
|
||||
return $this->redirect;
|
||||
return $this->redirect ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2107,7 +2024,7 @@ class Page implements PageInterface
|
||||
$this->path = dirname($var, 2);
|
||||
}
|
||||
|
||||
return $this->path . '/' . $this->folder . '/' . ($this->name ?: '');
|
||||
return rtrim($this->path . '/' . $this->folder . '/' . ($this->name() ?: ''), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2349,7 +2266,7 @@ class Page implements PageInterface
|
||||
}
|
||||
}
|
||||
|
||||
return $this->modular_twig;
|
||||
return $this->modular_twig ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2497,7 +2414,7 @@ class Page implements PageInterface
|
||||
/**
|
||||
* Returns the item in the current position.
|
||||
*
|
||||
* @return int the index of the current page.
|
||||
* @return int|null The index of the current page.
|
||||
*/
|
||||
public function currentPosition()
|
||||
{
|
||||
@@ -2538,7 +2455,7 @@ class Page implements PageInterface
|
||||
|
||||
if (isset($routes[$uri_path])) {
|
||||
/** @var PageInterface $child_page */
|
||||
$child_page = $pages->dispatch($uri->route())->parent();
|
||||
$child_page = $pages->find($uri->route())->parent();
|
||||
if ($child_page) {
|
||||
while (!$child_page->root()) {
|
||||
if ($this->path() === $child_page->path()) {
|
||||
@@ -2671,321 +2588,48 @@ class Page implements PageInterface
|
||||
public function collection($params = 'content', $pagination = true)
|
||||
{
|
||||
if (is_string($params)) {
|
||||
// Look into a page header field.
|
||||
$params = (array)$this->value('header.' . $params);
|
||||
} elseif (!is_array($params)) {
|
||||
throw new \InvalidArgumentException('Argument should be either header variable name or array of parameters');
|
||||
}
|
||||
|
||||
if (!isset($params['items'])) {
|
||||
return new Collection();
|
||||
if (!$pagination) {
|
||||
$params['pagination'] = false;
|
||||
}
|
||||
$context = [
|
||||
'pagination' => $pagination,
|
||||
'self' => $this
|
||||
];
|
||||
|
||||
// See if require published filter is set and use that, if assume published=true
|
||||
$only_published = true;
|
||||
if (isset($params['filter']['published']) && $params['filter']['published']) {
|
||||
$only_published = false;
|
||||
} elseif (isset($params['filter']['non-published']) && $params['filter']['non-published']) {
|
||||
$only_published = false;
|
||||
}
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
$collection = $this->evaluate($params['items'], $only_published);
|
||||
if (!$collection instanceof Collection) {
|
||||
$collection = new Collection();
|
||||
}
|
||||
$collection->setParams($params);
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = Grav::instance()['uri'];
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$process_taxonomy = $params['url_taxonomy_filters'] ?? $config->get('system.pages.url_taxonomy_filters');
|
||||
|
||||
if ($process_taxonomy) {
|
||||
foreach ((array)$config->get('site.taxonomies') as $taxonomy) {
|
||||
if ($uri->param(rawurlencode($taxonomy))) {
|
||||
$items = explode(',', $uri->param($taxonomy));
|
||||
$collection->setParams(['taxonomies' => [$taxonomy => $items]]);
|
||||
|
||||
foreach ($collection as $page) {
|
||||
// Don't filter modular pages
|
||||
if ($page->modular()) {
|
||||
continue;
|
||||
}
|
||||
foreach ($items as $item) {
|
||||
$item = rawurldecode($item);
|
||||
if (empty($page->taxonomy[$taxonomy]) || !\in_array(htmlspecialchars_decode($item, ENT_QUOTES), $page->taxonomy[$taxonomy], true)
|
||||
) {
|
||||
$collection->remove($page->path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a filter or filters are set, filter the collection...
|
||||
if (isset($params['filter'])) {
|
||||
|
||||
// remove any inclusive sets from filer:
|
||||
$sets = ['published', 'visible', 'modular', 'routable'];
|
||||
foreach ($sets as $type) {
|
||||
$var = "non-{$type}";
|
||||
if (isset($params['filter'][$type], $params['filter'][$var]) && $params['filter'][$type] && $params['filter'][$var]) {
|
||||
unset ($params['filter'][$type], $params['filter'][$var]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((array)$params['filter'] as $type => $filter) {
|
||||
switch ($type) {
|
||||
case 'published':
|
||||
if ((bool) $filter) {
|
||||
$collection->published();
|
||||
}
|
||||
break;
|
||||
case 'non-published':
|
||||
if ((bool) $filter) {
|
||||
$collection->nonPublished();
|
||||
}
|
||||
break;
|
||||
case 'visible':
|
||||
if ((bool) $filter) {
|
||||
$collection->visible();
|
||||
}
|
||||
break;
|
||||
case 'non-visible':
|
||||
if ((bool) $filter) {
|
||||
$collection->nonVisible();
|
||||
}
|
||||
break;
|
||||
case 'modular':
|
||||
if ((bool) $filter) {
|
||||
$collection->modular();
|
||||
}
|
||||
break;
|
||||
case 'non-modular':
|
||||
if ((bool) $filter) {
|
||||
$collection->nonModular();
|
||||
}
|
||||
break;
|
||||
case 'routable':
|
||||
if ((bool) $filter) {
|
||||
$collection->routable();
|
||||
}
|
||||
break;
|
||||
case 'non-routable':
|
||||
if ((bool) $filter) {
|
||||
$collection->nonRoutable();
|
||||
}
|
||||
break;
|
||||
case 'type':
|
||||
$collection->ofType($filter);
|
||||
break;
|
||||
case 'types':
|
||||
$collection->ofOneOfTheseTypes($filter);
|
||||
break;
|
||||
case 'access':
|
||||
$collection->ofOneOfTheseAccessLevels($filter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['dateRange'])) {
|
||||
$start = $params['dateRange']['start'] ?? 0;
|
||||
$end = $params['dateRange']['end'] ?? false;
|
||||
$field = $params['dateRange']['field'] ?? false;
|
||||
$collection->dateRange($start, $end, $field);
|
||||
}
|
||||
|
||||
if (isset($params['order'])) {
|
||||
$by = $params['order']['by'] ?? 'default';
|
||||
$dir = $params['order']['dir'] ?? 'asc';
|
||||
$custom = $params['order']['custom'] ?? null;
|
||||
$sort_flags = $params['order']['sort_flags'] ?? null;
|
||||
|
||||
if (is_array($sort_flags)) {
|
||||
$sort_flags = array_map('constant', $sort_flags); //transform strings to constant value
|
||||
$sort_flags = array_reduce($sort_flags, function ($a, $b) {
|
||||
return $a | $b;
|
||||
}, 0); //merge constant values using bit or
|
||||
}
|
||||
|
||||
$collection->order($by, $dir, $custom, $sort_flags);
|
||||
}
|
||||
|
||||
/** @var Grav $grav */
|
||||
$grav = Grav::instance();
|
||||
|
||||
// New Custom event to handle things like pagination.
|
||||
$grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection]));
|
||||
|
||||
// Slice and dice the collection if pagination is required
|
||||
if ($pagination) {
|
||||
$params = $collection->params();
|
||||
|
||||
$limit = $params['limit'] ?? 0;
|
||||
$start = !empty($params['pagination']) ? ($uri->currentPage() - 1) * $limit : 0;
|
||||
|
||||
if ($limit && $collection->count() > $limit) {
|
||||
$collection->slice($start, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
return $pages->getCollection($params, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $value
|
||||
* @param bool $only_published
|
||||
* @return mixed
|
||||
* @return Collection
|
||||
*/
|
||||
public function evaluate($value, $only_published = true)
|
||||
{
|
||||
// Parse command.
|
||||
if (is_string($value)) {
|
||||
// Format: @command.param
|
||||
$cmd = $value;
|
||||
$params = [];
|
||||
} elseif (is_array($value) && count($value) == 1 && !is_int(key($value))) {
|
||||
// Format: @command.param: { attr1: value1, attr2: value2 }
|
||||
$cmd = (string)key($value);
|
||||
$params = (array)current($value);
|
||||
} else {
|
||||
$result = [];
|
||||
foreach ((array)$value as $key => $val) {
|
||||
if (is_int($key)) {
|
||||
$result = $result + $this->evaluate($val)->toArray();
|
||||
} else {
|
||||
$result = $result + $this->evaluate([$key => $val])->toArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new Collection($result);
|
||||
}
|
||||
$params = [
|
||||
'items' => $value,
|
||||
'published' => $only_published
|
||||
];
|
||||
$context = [
|
||||
'event' => false,
|
||||
'pagination' => false,
|
||||
'url_taxonomy_filters' => false,
|
||||
'self' => $this
|
||||
];
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = Grav::instance()['pages'];
|
||||
|
||||
$parts = explode('.', $cmd);
|
||||
$current = array_shift($parts);
|
||||
|
||||
/** @var Collection $results */
|
||||
$results = new Collection();
|
||||
|
||||
switch ($current) {
|
||||
case 'self@':
|
||||
case '@self':
|
||||
if (!empty($parts)) {
|
||||
switch ($parts[0]) {
|
||||
case 'modular':
|
||||
// @self.modular: false (alternative to @self.children)
|
||||
if (!empty($params) && $params[0] === false) {
|
||||
$results = $this->children()->nonModular();
|
||||
break;
|
||||
}
|
||||
$results = $this->children()->modular();
|
||||
break;
|
||||
case 'children':
|
||||
$results = $this->children()->nonModular();
|
||||
break;
|
||||
case 'all':
|
||||
$results = $this->children();
|
||||
break;
|
||||
case 'parent':
|
||||
$collection = new Collection();
|
||||
$results = $collection->addPage($this->parent());
|
||||
break;
|
||||
case 'siblings':
|
||||
if (!$this->parent()) {
|
||||
return new Collection();
|
||||
}
|
||||
$results = $this->parent()->children()->remove($this->path());
|
||||
break;
|
||||
case 'descendants':
|
||||
$results = $pages->all($this)->remove($this->path())->nonModular();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
case 'page@':
|
||||
case '@page':
|
||||
$page = null;
|
||||
|
||||
if (!empty($params)) {
|
||||
$page = $this->find($params[0]);
|
||||
}
|
||||
|
||||
// safety check in case page is not found
|
||||
if (!isset($page)) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
// Handle a @page.descendants
|
||||
if (!empty($parts)) {
|
||||
switch ($parts[0]) {
|
||||
case 'modular':
|
||||
$results = new Collection();
|
||||
foreach ($page->children() as $child) {
|
||||
$results = $results->addPage($child);
|
||||
}
|
||||
$results->modular();
|
||||
break;
|
||||
case 'page':
|
||||
case 'self':
|
||||
$results = new Collection();
|
||||
$results = $results->addPage($page);
|
||||
break;
|
||||
|
||||
case 'descendants':
|
||||
$results = $pages->all($page)->remove($page->path())->nonModular();
|
||||
break;
|
||||
|
||||
case 'children':
|
||||
$results = $page->children()->nonModular();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$results = $page->children()->nonModular();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'root@':
|
||||
case '@root':
|
||||
if (!empty($parts) && $parts[0] === 'descendants') {
|
||||
$results = $pages->all($pages->root())->nonModular();
|
||||
} else {
|
||||
$results = $pages->root()->children()->nonModular();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'taxonomy@':
|
||||
case '@taxonomy':
|
||||
// Gets a collection of pages by using one of the following formats:
|
||||
// @taxonomy.category: blog
|
||||
// @taxonomy.category: [ blog, featured ]
|
||||
// @taxonomy: { category: [ blog, featured ], level: 1 }
|
||||
|
||||
/** @var Taxonomy $taxonomy_map */
|
||||
$taxonomy_map = Grav::instance()['taxonomy'];
|
||||
|
||||
if (!empty($parts)) {
|
||||
$params = [implode('.', $parts) => $params];
|
||||
}
|
||||
$results = $taxonomy_map->findTaxonomy($params);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($only_published) {
|
||||
$results = $results->published();
|
||||
}
|
||||
|
||||
return $results;
|
||||
return $pages->getCollection($params, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3170,7 +2814,7 @@ class Page implements PageInterface
|
||||
/**
|
||||
* Gets the action.
|
||||
*
|
||||
* @return string The Action string.
|
||||
* @return string|null The Action string.
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
|
||||
@@ -13,13 +13,18 @@ use Grav\Common\Cache;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\Blueprints;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Taxonomy;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\Flex;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Plugin\Admin;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
@@ -28,64 +33,46 @@ use Collator;
|
||||
|
||||
class Pages
|
||||
{
|
||||
/**
|
||||
* @var Grav
|
||||
*/
|
||||
/** @var Grav */
|
||||
protected $grav;
|
||||
|
||||
/**
|
||||
* @var array|PageInterface[]
|
||||
*/
|
||||
/** @var Flex */
|
||||
protected $flex;
|
||||
|
||||
/** @var array|PageInterface[] */
|
||||
protected $instances;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var array */
|
||||
protected $children;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
protected $base = '';
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var string[] */
|
||||
protected $baseRoute = [];
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var string[] */
|
||||
protected $routes = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var array */
|
||||
protected $sort;
|
||||
|
||||
/**
|
||||
* @var Blueprints
|
||||
*/
|
||||
/** @var Blueprints */
|
||||
protected $blueprints;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var bool */
|
||||
protected $enable_pages = true;
|
||||
|
||||
/** @var int */
|
||||
protected $last_modified;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var string[] */
|
||||
protected $ignore_files;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
/** @var string[] */
|
||||
protected $ignore_folders;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
/** @var bool */
|
||||
protected $ignore_hidden;
|
||||
|
||||
/** @var string */
|
||||
@@ -93,16 +80,13 @@ class Pages
|
||||
|
||||
protected $pages_cache_id;
|
||||
|
||||
/** @var bool */
|
||||
protected $initialized = false;
|
||||
|
||||
/**
|
||||
* @var Types
|
||||
*/
|
||||
/** @var Types */
|
||||
static protected $types;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string|null */
|
||||
static protected $home_route;
|
||||
|
||||
/**
|
||||
@@ -115,6 +99,26 @@ class Pages
|
||||
$this->grav = $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used in admin to disable frontend pages from being initialized.
|
||||
*/
|
||||
public function disablePages(): void
|
||||
{
|
||||
$this->enable_pages = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used in admin to later load frontend pages.
|
||||
*/
|
||||
public function enablePages(): void
|
||||
{
|
||||
if (!$this->enable_pages) {
|
||||
$this->enable_pages = true;
|
||||
|
||||
$this->buildPages();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set base path for the pages.
|
||||
*
|
||||
@@ -249,6 +253,9 @@ class Pages
|
||||
$this->ignore_files = $config->get('system.pages.ignore_files');
|
||||
$this->ignore_folders = $config->get('system.pages.ignore_folders');
|
||||
$this->ignore_hidden = $config->get('system.pages.ignore_hidden');
|
||||
if ($config->get('system.pages.type') === 'flex') {
|
||||
$this->flex = $this->grav['flex_objects'] ?? null;
|
||||
}
|
||||
|
||||
$this->instances = [];
|
||||
$this->children = [];
|
||||
@@ -280,11 +287,23 @@ class Pages
|
||||
/**
|
||||
* Returns a list of all pages.
|
||||
*
|
||||
* @return array|PageInterface[]
|
||||
* @return PageInterface[]
|
||||
*/
|
||||
public function instances()
|
||||
{
|
||||
return $this->instances;
|
||||
if (!$this->flex) {
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
$list = [];
|
||||
foreach ($this->instances as $path => $instance) {
|
||||
if (!$instance instanceof PageInterface) {
|
||||
$instance = $this->flex->getObject($instance);
|
||||
}
|
||||
$list[$path] = $instance;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,18 +324,301 @@ class Pages
|
||||
*/
|
||||
public function addPage(PageInterface $page, $route = null)
|
||||
{
|
||||
if (!isset($this->instances[$page->path()])) {
|
||||
$this->instances[$page->path()] = $page;
|
||||
$path = $page->path() ?? '';
|
||||
if (!isset($this->instances[$path])) {
|
||||
$this->instances[$path] = $page;
|
||||
}
|
||||
$route = $page->route($route);
|
||||
if ($page->parent()) {
|
||||
$this->children[$page->parent()->path()][$page->path()] = ['slug' => $page->slug()];
|
||||
$parentPath = $page->parent()->path() ?? '';
|
||||
$this->children[$parentPath][$path] = ['slug' => $page->slug()];
|
||||
}
|
||||
$this->routes[$route] = $page->path();
|
||||
$this->routes[$route] = $path;
|
||||
|
||||
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of pages in the given context.
|
||||
*
|
||||
* @param array $params
|
||||
* @param array $context
|
||||
* @return PageCollectionInterface|Collection
|
||||
*/
|
||||
public function getCollection(array $params = [], array $context = [])
|
||||
{
|
||||
if (!isset($params['items'])) {
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
$context += [
|
||||
'event' => true,
|
||||
'pagination' => true,
|
||||
'url_taxonomy_filters' => $config->get('system.pages.url_taxonomy_filters'),
|
||||
'taxonomies' => (array)$config->get('site.taxonomies'),
|
||||
'pagination_page' => 1,
|
||||
'self' => null,
|
||||
];
|
||||
|
||||
// Include taxonomies from the URL if requested.
|
||||
$process_taxonomy = $params['url_taxonomy_filters'] ?? $context['url_taxonomy_filters'];
|
||||
if ($process_taxonomy) {
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
foreach ($context['taxonomies'] as $taxonomy) {
|
||||
$param = $uri->param(rawurlencode($taxonomy));
|
||||
$items = $param ? explode(',', $param) : [];
|
||||
foreach ($items as $item) {
|
||||
$params['taxonomies'][$taxonomy][] = htmlspecialchars_decode(rawurldecode($item), ENT_QUOTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pagination = $params['pagination'] ?? $context['pagination'];
|
||||
if ($pagination && !isset($params['page'])) {
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->grav['uri'];
|
||||
$context['pagination_page'] = $uri->currentPage();
|
||||
}
|
||||
|
||||
$collection = $this->evaluate($params['items'], $context['self']);
|
||||
$collection->setParams($params);
|
||||
|
||||
// Filter by taxonomies.
|
||||
foreach ($params['taxonomies'] ?? [] as $taxonomy => $items) {
|
||||
foreach ($collection as $page) {
|
||||
// Don't filter modular pages
|
||||
if ($page->modular()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$test = $page->taxonomy()[$taxonomy] ?? [];
|
||||
foreach ($items as $item) {
|
||||
if (!$test || !\in_array($item, $test, true)) {
|
||||
$collection->remove($page->path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any inclusive sets from filter.
|
||||
$filters = $params['filter'] ?? [];
|
||||
|
||||
// Assume published=true if not set.
|
||||
if (!isset($filters['published']) && !isset($filters['non-published'])) {
|
||||
$filters['published'] = true;
|
||||
}
|
||||
foreach (['published', 'visible', 'modular', 'routable'] as $type) {
|
||||
$var = "non-{$type}";
|
||||
if (isset($filters[$type], $filters[$var]) && $filters[$type] && $filters[$var]) {
|
||||
unset($filters[$type], $filters[$var]);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter the collection
|
||||
foreach ($filters as $type => $filter) {
|
||||
switch ($type) {
|
||||
case 'published':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->published();
|
||||
}
|
||||
break;
|
||||
case 'non-published':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->nonPublished();
|
||||
}
|
||||
break;
|
||||
case 'visible':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->visible();
|
||||
}
|
||||
break;
|
||||
case 'non-visible':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->nonVisible();
|
||||
}
|
||||
break;
|
||||
case 'modular':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->modular();
|
||||
}
|
||||
break;
|
||||
case 'non-modular':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->nonModular();
|
||||
}
|
||||
break;
|
||||
case 'routable':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->routable();
|
||||
}
|
||||
break;
|
||||
case 'non-routable':
|
||||
if ((bool)$filter) {
|
||||
$collection = $collection->nonRoutable();
|
||||
}
|
||||
break;
|
||||
case 'type':
|
||||
$collection = $collection->ofType($filter);
|
||||
break;
|
||||
case 'types':
|
||||
$collection = $collection->ofOneOfTheseTypes($filter);
|
||||
break;
|
||||
case 'access':
|
||||
$collection = $collection->ofOneOfTheseAccessLevels($filter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['dateRange'])) {
|
||||
$start = $params['dateRange']['start'] ?? 0;
|
||||
$end = $params['dateRange']['end'] ?? false;
|
||||
$field = $params['dateRange']['field'] ?? false;
|
||||
$collection = $collection->dateRange($start, $end, $field);
|
||||
}
|
||||
|
||||
if (isset($params['order'])) {
|
||||
$by = $params['order']['by'] ?? 'default';
|
||||
$dir = $params['order']['dir'] ?? 'asc';
|
||||
$custom = $params['order']['custom'] ?? null;
|
||||
$sort_flags = $params['order']['sort_flags'] ?? null;
|
||||
|
||||
if (is_array($sort_flags)) {
|
||||
$sort_flags = array_map('constant', $sort_flags); //transform strings to constant value
|
||||
$sort_flags = array_reduce($sort_flags, function ($a, $b) {
|
||||
return $a | $b;
|
||||
}, 0); //merge constant values using bit or
|
||||
}
|
||||
|
||||
$collection = $collection->order($by, $dir, $custom, $sort_flags);
|
||||
}
|
||||
|
||||
// New Custom event to handle things like pagination.
|
||||
if ($context['event']) {
|
||||
$this->grav->fireEvent('onCollectionProcessed', new Event(['collection' => $collection]));
|
||||
}
|
||||
|
||||
// Slice and dice the collection if pagination is required
|
||||
if ($pagination) {
|
||||
$params = $collection->params();
|
||||
|
||||
$limit = $params['limit'] ?? 0;
|
||||
$start = !empty($params['pagination']) ? (($params['page'] ?? $context['pagination_page']) - 1) * $limit : 0;
|
||||
|
||||
if ($limit && $collection->count() > $limit) {
|
||||
$collection->slice($start, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @param PageInterface|null $self
|
||||
* @return Collection
|
||||
*/
|
||||
protected function evaluate($value, PageInterface $self = null)
|
||||
{
|
||||
// Parse command.
|
||||
if (is_string($value)) {
|
||||
// Format: @command.param
|
||||
$cmd = $value;
|
||||
$params = [];
|
||||
} elseif (is_array($value) && count($value) === 1 && !is_int(key($value))) {
|
||||
// Format: @command.param: { attr1: value1, attr2: value2 }
|
||||
$cmd = (string)key($value);
|
||||
$params = (array)current($value);
|
||||
} else {
|
||||
$result = [];
|
||||
foreach ((array)$value as $key => $val) {
|
||||
if (is_int($key)) {
|
||||
$result = $result + $this->evaluate($val, $self)->toArray();
|
||||
} else {
|
||||
$result = $result + $this->evaluate([$key => $val], $self)->toArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new Collection($result);
|
||||
}
|
||||
|
||||
$parts = explode('.', $cmd);
|
||||
$scope = array_shift($parts);
|
||||
$type = $parts[0] ?? null;
|
||||
|
||||
/** @var PageInterface|null $page */
|
||||
$page = null;
|
||||
switch ($scope) {
|
||||
case 'self@':
|
||||
case '@self':
|
||||
$page = $self;
|
||||
break;
|
||||
|
||||
case 'page@':
|
||||
case '@page':
|
||||
$page = isset($params[0]) ? $this->find($params[0]) : null;
|
||||
break;
|
||||
|
||||
case 'root@':
|
||||
case '@root':
|
||||
$page = $this->root();
|
||||
break;
|
||||
|
||||
case 'taxonomy@':
|
||||
case '@taxonomy':
|
||||
// Gets a collection of pages by using one of the following formats:
|
||||
// @taxonomy.category: blog
|
||||
// @taxonomy.category: [ blog, featured ]
|
||||
// @taxonomy: { category: [ blog, featured ], level: 1 }
|
||||
|
||||
/** @var Taxonomy $taxonomy_map */
|
||||
$taxonomy_map = Grav::instance()['taxonomy'];
|
||||
|
||||
if (!empty($parts)) {
|
||||
$params = [implode('.', $parts) => $params];
|
||||
}
|
||||
|
||||
return $taxonomy_map->findTaxonomy($params);
|
||||
}
|
||||
|
||||
if (!$page) {
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
// Handle '@page', '@page.modular: false', '@self' and '@self.modular: false'.
|
||||
if (null === $type || ($type === 'modular' && ($params[0] ?? null) === false)) {
|
||||
$type = 'children';
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'all':
|
||||
return $page->children();
|
||||
case 'modular':
|
||||
return $page->children()->modular();
|
||||
case 'children':
|
||||
return $page->children()->nonModular();
|
||||
case 'page':
|
||||
case 'self':
|
||||
return (new Collection())->addPage($page);
|
||||
case 'parent':
|
||||
$parent = $page->parent();
|
||||
$collection = new Collection();
|
||||
return $parent ? $collection->addPage($parent) : $collection;
|
||||
case 'siblings':
|
||||
$parent = $page->parent();
|
||||
return $parent ? $parent->children()->remove($page->path()) : new Collection();
|
||||
case 'descendants':
|
||||
return $this->all($page)->remove($page->path())->nonModular();
|
||||
default:
|
||||
// Unknown type; return empty collection.
|
||||
return new Collection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort sub-pages in a page.
|
||||
*
|
||||
@@ -397,7 +699,16 @@ class Pages
|
||||
*/
|
||||
public function get($path)
|
||||
{
|
||||
return $this->instances[(string)$path] ?? null;
|
||||
$instance = $this->instances[(string)$path] ?? null;
|
||||
if (\is_string($instance)) {
|
||||
$instance = $this->flex ? $this->flex->getObject($instance) : null;
|
||||
$instance = $instance->hasTranslation() ? $instance->getTranslation() : $instance;
|
||||
}
|
||||
if ($instance && !$instance instanceof PageInterface) {
|
||||
throw new \RuntimeException('Routing failed on unknown type', 500);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -425,7 +736,7 @@ class Pages
|
||||
public function ancestor($route, $path = null)
|
||||
{
|
||||
if ($path !== null) {
|
||||
$page = $this->dispatch($route, true);
|
||||
$page = $this->find($route, true);
|
||||
|
||||
if ($page && $page->path() === $path) {
|
||||
return $page;
|
||||
@@ -452,7 +763,7 @@ class Pages
|
||||
{
|
||||
if ($field !== null) {
|
||||
|
||||
$page = $this->dispatch($route, true);
|
||||
$page = $this->find($route, true);
|
||||
|
||||
$parent = $page ? $page->parent() : null;
|
||||
if ($parent && $parent->value('header.' . $field) !== null) {
|
||||
@@ -513,13 +824,18 @@ class Pages
|
||||
|
||||
// fall back and check site based redirects
|
||||
if (!$page || ($page && !$page->routable())) {
|
||||
// Redirect to the first child (placeholder page)
|
||||
if ($redirect && $page && count($children = $page->children()->visible()) > 0) {
|
||||
$this->grav->redirectLangSafe($children->first()->route());
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
// See if route matches one in the site configuration
|
||||
$site_route = $config->get("site.routes.{$route}");
|
||||
if ($site_route) {
|
||||
$page = $this->dispatch($site_route, $all);
|
||||
$page = $this->dispatch($site_route, $all, $redirect);
|
||||
} else {
|
||||
|
||||
/** @var Uri $uri */
|
||||
@@ -551,7 +867,7 @@ class Pages
|
||||
try {
|
||||
$found = preg_replace($pattern, $replace, $source_url);
|
||||
if ($found !== $source_url) {
|
||||
$page = $this->dispatch($found, $all);
|
||||
$page = $this->dispatch($found, $all, $redirect);
|
||||
}
|
||||
} catch (ErrorException $e) {
|
||||
$this->grav['log']->error('site.routes: ' . $pattern . '-> ' . $e->getMessage());
|
||||
@@ -575,7 +891,7 @@ class Pages
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
return $this->instances[rtrim($locator->findResource('page://'), DS)];
|
||||
return $this->get(rtrim($locator->findResource('page://'), '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -713,13 +1029,9 @@ class Pages
|
||||
} else {
|
||||
$extra = $showSlug ? '(' . $current->slug() . ') ' : '';
|
||||
$option = str_repeat('—-', $level). '▸ ' . $extra . $current->title();
|
||||
|
||||
|
||||
}
|
||||
|
||||
$list[$route] = $option;
|
||||
|
||||
|
||||
}
|
||||
|
||||
if ($limitLevels === false || ($level+1 < $limitLevels)) {
|
||||
@@ -824,20 +1136,23 @@ class Pages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function pageTypes()
|
||||
public static function pageTypes($type = null)
|
||||
{
|
||||
if (isset(Grav::instance()['admin'])) {
|
||||
if (null === $type && isset(Grav::instance()['admin'])) {
|
||||
/** @var Admin $admin */
|
||||
$admin = Grav::instance()['admin'];
|
||||
|
||||
/** @var PageInterface $page */
|
||||
$page = $admin->getPage($admin->route);
|
||||
$page = $admin->page();
|
||||
|
||||
if ($page && $page->modular()) {
|
||||
$type = $page && $page->modular() ? 'modular' : 'standard';
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'standard':
|
||||
return static::types();
|
||||
case 'modular':
|
||||
return static::modularTypes();
|
||||
}
|
||||
|
||||
return static::types();
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -943,26 +1258,156 @@ class Pages
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function buildPages()
|
||||
protected function buildPages(): void
|
||||
{
|
||||
$this->sort = [];
|
||||
if ($this->enable_pages === false) {
|
||||
$page = $this->buildRootPage();
|
||||
$this->instances[$page->path()] = $page;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->startTimer('build-pages', 'Init frontend routes');
|
||||
|
||||
$directory = $this->flex ? $this->flex->getDirectory('grav-pages') : null;
|
||||
|
||||
if ($directory) {
|
||||
$this->buildFlexPages($directory);
|
||||
} else {
|
||||
$this->buildRegularPages();
|
||||
}
|
||||
$debugger->stopTimer('build-pages');
|
||||
}
|
||||
|
||||
protected function buildFlexPages(FlexDirectory $directory)
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
// TODO: right now we are just emulating normal pages, it is inefficient and bad... but works!
|
||||
$collection = $directory->getIndex();
|
||||
$cache = $directory->getCache('index');
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
$this->pages_cache_id = 'pages-' . md5($collection->getCacheChecksum() . $language->getActive() . $config->checksum());
|
||||
|
||||
$cached = $cache->get($this->pages_cache_id);
|
||||
|
||||
if ($cached && $this->getVersion() === $cached[0]) {
|
||||
[, $this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort] = $cached;
|
||||
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
$taxonomy->taxonomy($taxonomy_map);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding Flex Pages..');
|
||||
|
||||
$root = $this->buildRootPage();
|
||||
$root_path = $root->path();
|
||||
$this->instances = [$root_path => $root];
|
||||
$this->children = [];
|
||||
$this->sort = [];
|
||||
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onBuildPagesInitialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string $key
|
||||
* @var PageInterface|FlexObjectInterface $page
|
||||
*/
|
||||
foreach ($collection as $key => $page) {
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onPageProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
|
||||
$path = $page->path();
|
||||
|
||||
// FIXME: We really need to do better than this.
|
||||
$translated = $page->hasTranslation() ? $page->getTranslation() : false;
|
||||
|
||||
if (!$translated || $path === $root_path) {
|
||||
continue;
|
||||
}
|
||||
$parent = dirname($path);
|
||||
|
||||
$this->instances[$path] = $page->getFlexKey();
|
||||
// FIXME: ... better...
|
||||
$this->children[$parent][$path] = ['slug' => $translated->slug()];
|
||||
if (!isset($this->children[$path])) {
|
||||
$this->children[$path] = [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->children as $path => $list) {
|
||||
$page = $this->get($path);
|
||||
if (null === $page) {
|
||||
continue;
|
||||
}
|
||||
// Call onFolderProcessed event.
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
// Sort the children.
|
||||
$this->children[$path] = $this->sort($page);
|
||||
}
|
||||
|
||||
$this->buildRoutes();
|
||||
|
||||
// cache if needed
|
||||
if (isset($cache)) {
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
$taxonomy_map = $taxonomy->taxonomy();
|
||||
|
||||
// save pages, routes, taxonomy, and sort to cache
|
||||
$cache->set($this->pages_cache_id, [$this->getVersion(), $this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function buildRootPage()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
$page = new Page();
|
||||
$page->path($locator->findResource('page://'));
|
||||
$page->orderDir($config->get('system.pages.order.dir'));
|
||||
$page->orderBy($config->get('system.pages.order.by'));
|
||||
$page->modified(0);
|
||||
$page->routable(false);
|
||||
$page->template('default');
|
||||
$page->extension('.md');
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
protected function buildRegularPages()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
|
||||
$pages_dir = $locator->findResource('page://');
|
||||
|
||||
if ($config->get('system.cache.enabled')) {
|
||||
/** @var Cache $cache */
|
||||
$cache = $this->grav['cache'];
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
// how should we check for last modified? Default is by file
|
||||
switch ($this->check_method) {
|
||||
@@ -982,22 +1427,25 @@ class Pages
|
||||
|
||||
$this->pages_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum());
|
||||
|
||||
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($this->pages_cache_id);
|
||||
if (!$this->instances) {
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
|
||||
/** @var Cache $cache */
|
||||
$cache = $this->grav['cache'];
|
||||
$cached = $cache->fetch($this->pages_cache_id);
|
||||
if ($cached && $this->getVersion() === $cached[0]) {
|
||||
[, $this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort] = $cached;
|
||||
|
||||
// recurse pages and cache result
|
||||
$this->resetPages($pages_dir);
|
||||
|
||||
} else {
|
||||
// If pages was found in cache, set the taxonomy
|
||||
$this->grav['debugger']->addMessage('Page cache hit.');
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
$taxonomy->taxonomy($taxonomy_map);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
|
||||
} else {
|
||||
$this->recurse($pages_dir);
|
||||
$this->buildRoutes();
|
||||
$this->grav['debugger']->addMessage('Page cache disabled, rebuilding pages..');
|
||||
}
|
||||
|
||||
$this->resetPages($pages_dir);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1007,6 +1455,7 @@ class Pages
|
||||
*/
|
||||
public function resetPages($pages_dir)
|
||||
{
|
||||
$this->sort = [];
|
||||
$this->recurse($pages_dir);
|
||||
$this->buildRoutes();
|
||||
|
||||
@@ -1018,7 +1467,7 @@ class Pages
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
|
||||
// save pages, routes, taxonomy, and sort to cache
|
||||
$cache->save($this->pages_cache_id, [$this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort]);
|
||||
$cache->save($this->pages_cache_id, [$this->getVersion(), $this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1063,7 +1512,7 @@ class Pages
|
||||
if ($parent && $page->path()) {
|
||||
$this->children[$parent->path()][$page->path()] = ['slug' => $page->slug()];
|
||||
}
|
||||
} else {
|
||||
} elseif ($parent !== null) {
|
||||
throw new \RuntimeException('Fatal error when creating page instances.');
|
||||
}
|
||||
|
||||
@@ -1161,7 +1610,6 @@ class Pages
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!$content_exists) {
|
||||
// Set routability to false if no page found
|
||||
$page->routable(false);
|
||||
@@ -1203,44 +1651,49 @@ class Pages
|
||||
|
||||
// Get the home route
|
||||
$home = self::resetHomeRoute();
|
||||
|
||||
// Build routes and taxonomy map.
|
||||
/** @var PageInterface $page */
|
||||
foreach ($this->instances as $page) {
|
||||
if (!$page->root()) {
|
||||
// process taxonomy
|
||||
$taxonomy->addTaxonomy($page);
|
||||
foreach ($this->instances as $path => $page) {
|
||||
if (\is_string($page)) {
|
||||
$page = $this->get($path);
|
||||
}
|
||||
|
||||
$route = $page->route();
|
||||
$raw_route = $page->rawRoute();
|
||||
$page_path = $page->path();
|
||||
if (!$page || $page->root()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add regular route
|
||||
$this->routes[$route] = $page_path;
|
||||
// process taxonomy
|
||||
$taxonomy->addTaxonomy($page);
|
||||
|
||||
// add raw route
|
||||
if ($raw_route !== $route) {
|
||||
$this->routes[$raw_route] = $page_path;
|
||||
}
|
||||
$route = $page->route();
|
||||
$raw_route = $page->rawRoute();
|
||||
$page_path = $page->path();
|
||||
|
||||
// add canonical route
|
||||
$route_canonical = $page->routeCanonical();
|
||||
if ($route_canonical && ($route !== $route_canonical)) {
|
||||
$this->routes[$route_canonical] = $page_path;
|
||||
}
|
||||
// add regular route
|
||||
$this->routes[$route] = $page_path;
|
||||
|
||||
// add aliases to routes list if they are provided
|
||||
$route_aliases = $page->routeAliases();
|
||||
if ($route_aliases) {
|
||||
foreach ($route_aliases as $alias) {
|
||||
$this->routes[$alias] = $page_path;
|
||||
}
|
||||
// add raw route
|
||||
if ($raw_route !== $route) {
|
||||
$this->routes[$raw_route] = $page_path;
|
||||
}
|
||||
|
||||
// add canonical route
|
||||
$route_canonical = $page->routeCanonical();
|
||||
if ($route_canonical && ($route !== $route_canonical)) {
|
||||
$this->routes[$route_canonical] = $page_path;
|
||||
}
|
||||
|
||||
// add aliases to routes list if they are provided
|
||||
$route_aliases = $page->routeAliases();
|
||||
if ($route_aliases) {
|
||||
foreach ($route_aliases as $alias) {
|
||||
$this->routes[$alias] = $page_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alias and set default route to home page.
|
||||
$homeRoute = '/' . $home;
|
||||
$homeRoute = "/{$home}";
|
||||
if ($home && isset($this->routes[$homeRoute])) {
|
||||
$this->routes['/'] = $this->routes[$homeRoute];
|
||||
$this->get($this->routes[$homeRoute])->route('/');
|
||||
@@ -1272,7 +1725,7 @@ class Pages
|
||||
}
|
||||
|
||||
foreach ($pages as $key => $info) {
|
||||
$child = $this->instances[$key] ?? null;
|
||||
$child = $this->get($key);
|
||||
if (!$child) {
|
||||
throw new \RuntimeException("Page does not exist: {$key}");
|
||||
}
|
||||
@@ -1307,7 +1760,10 @@ class Pages
|
||||
$list[$key] = $child->folder();
|
||||
break;
|
||||
case (is_string($header_query[0])):
|
||||
$child_header = new Header((array)$child->header());
|
||||
$child_header = $child->header();
|
||||
if (!$child_header instanceof Header) {
|
||||
$child_header = new Header((array)$child_header);
|
||||
}
|
||||
$header_value = $child_header->get($header_query[0]);
|
||||
if (is_array($header_value)) {
|
||||
$list[$key] = implode(',',$header_value);
|
||||
@@ -1408,6 +1864,11 @@ class Pages
|
||||
return $new;
|
||||
}
|
||||
|
||||
protected function getVersion()
|
||||
{
|
||||
return $this->flex ? 'flex' : 'page';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Pages cache ID
|
||||
*
|
||||
|
||||
120
system/src/Grav/Common/Page/Traits/PageFormTrait.php
Normal file
120
system/src/Grav/Common/Page/Traits/PageFormTrait.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Grav\Common\Page\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
trait PageFormTrait
|
||||
{
|
||||
private $_forms;
|
||||
|
||||
/**
|
||||
* Return all the forms which are associated to this page.
|
||||
*
|
||||
* Forms are returned as [name => blueprint, ...], where blueprint follows the regular form blueprint format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getForms(): array
|
||||
{
|
||||
if (null === $this->_forms) {
|
||||
$header = $this->header();
|
||||
|
||||
// Call event to allow filling the page header form dynamically (e.g. use case: Comments plugin)
|
||||
$grav = Grav::instance();
|
||||
$grav->fireEvent('onFormPageHeaderProcessed', new Event(['page' => $this, 'header' => $header]));
|
||||
|
||||
$rules = $header->rules ?? null;
|
||||
if (!\is_array($rules)) {
|
||||
$rules = [];
|
||||
}
|
||||
|
||||
$forms = [];
|
||||
|
||||
// First grab page.header.form
|
||||
$form = $this->normalizeForm($header->form ?? null, null, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
|
||||
// Append page.header.forms (override singular form if it clashes)
|
||||
$headerForms = $header->forms ?? null;
|
||||
if (\is_array($headerForms)) {
|
||||
foreach ($headerForms as $name => $form) {
|
||||
$form = $this->normalizeForm($form, $name, $rules);
|
||||
if ($form) {
|
||||
$forms[$form['name']] = $form;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_forms = $forms;
|
||||
}
|
||||
|
||||
return $this->_forms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add forms to this page.
|
||||
*
|
||||
* @param array $new
|
||||
* @param bool $override
|
||||
* @return $this
|
||||
*/
|
||||
public function addForms(array $new, $override = true)
|
||||
{
|
||||
// Initialize forms.
|
||||
$this->forms();
|
||||
|
||||
foreach ($new as $name => $form) {
|
||||
$form = $this->normalizeForm($form, $name);
|
||||
$name = $form['name'] ?? null;
|
||||
if ($name && ($override || !isset($this->_forms[$name]))) {
|
||||
$this->_forms[$name] = $form;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of $this->getForms();
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function forms(): array
|
||||
{
|
||||
return $this->getForms();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $form
|
||||
* @param string|null $name
|
||||
* @param array $rules
|
||||
* @return array|null
|
||||
*/
|
||||
protected function normalizeForm($form, $name = null, array $rules = []): ?array
|
||||
{
|
||||
if (!\is_array($form)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore numeric indexes on name.
|
||||
if (!$name || (string)(int)$name === (string)$name) {
|
||||
$name = null;
|
||||
}
|
||||
|
||||
$name = $name ?? $form['name'] ?? $this->slug();
|
||||
|
||||
$formRules = $form['rules'] ?? null;
|
||||
if (!\is_array($formRules)) {
|
||||
$formRules = [];
|
||||
}
|
||||
|
||||
return ['name' => $name, 'rules' => $rules + $formRules] + $form;
|
||||
}
|
||||
|
||||
abstract public function header($var = null);
|
||||
abstract public function slug($var = null);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class AssetsProcessor extends ProcessorBase
|
||||
public $id = '_assets';
|
||||
public $title = 'Assets';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['assets']->init();
|
||||
|
||||
@@ -18,7 +18,7 @@ class BackupsProcessor extends ProcessorBase
|
||||
public $id = '_backups';
|
||||
public $title = 'Backups';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$backups = $this->container['backups'];
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class ConfigurationProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = '_config';
|
||||
public $title = 'Configuration';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['config']->init();
|
||||
$this->container['plugins']->setup();
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ class DebuggerAssetsProcessor extends ProcessorBase
|
||||
public $id = 'debugger_assets';
|
||||
public $title = 'Debugger Assets';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['debugger']->addAssets();
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class DebuggerProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = '_debugger';
|
||||
public $title = 'Init Debugger';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['debugger']->init();
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class ErrorsProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = '_errors';
|
||||
public $title = 'Error Handlers Reset';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['errors']->resetHandlers();
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -10,25 +10,119 @@
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use Grav\Framework\Session\Exceptions\SessionException;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Monolog\Handler\SyslogHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class InitializeProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = 'init';
|
||||
public $id = '_init';
|
||||
public $title = 'Initialize';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$config = $this->initializeConfig();
|
||||
$this->initializeLogger($config);
|
||||
$this->initializeErrors();
|
||||
|
||||
$this->startTimer('_debugger', 'Init Debugger');
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->container['debugger']->init();
|
||||
// Clockwork integration.
|
||||
$clockwork = $debugger->getClockwork();
|
||||
if ($clockwork) {
|
||||
$server = $request->getServerParams();
|
||||
// $baseUri = str_replace('\\', '/', dirname(parse_url($server['SCRIPT_NAME'], PHP_URL_PATH)));
|
||||
// if ($baseUri === '/') {
|
||||
// $baseUri = '';
|
||||
// }
|
||||
$requestTime = $server['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME;
|
||||
|
||||
$request = $request->withAttribute('request_time', $requestTime);
|
||||
|
||||
// Handle clockwork API calls.
|
||||
$uri = $request->getUri();
|
||||
if (Utils::contains($uri->getPath(), '/__clockwork/')) {
|
||||
return $debugger->debuggerRequest($request);
|
||||
}
|
||||
|
||||
$this->container['clockwork'] = $clockwork;
|
||||
}
|
||||
$this->stopTimer('_debugger');
|
||||
|
||||
$this->initialize($config);
|
||||
$this->initializeSession($config);
|
||||
|
||||
// Wrap call to next handler so that debugger can profile it.
|
||||
/** @var Response $response */
|
||||
$response = $debugger->profile(function () use ($handler, $request) {
|
||||
return $handler->handle($request);
|
||||
});
|
||||
|
||||
// Log both request and response and return the response.
|
||||
return $debugger->logRequest($request, $response);
|
||||
}
|
||||
|
||||
protected function initializeConfig(): Config
|
||||
{
|
||||
$this->startTimer('_config', 'Configuration');
|
||||
|
||||
// Initialize Configuration
|
||||
$grav = $this->container;
|
||||
/** @var Config $config */
|
||||
$config = $this->container['config'];
|
||||
$config->debug();
|
||||
$config = $grav['config'];
|
||||
$config->init();
|
||||
$grav['plugins']->setup();
|
||||
|
||||
$this->stopTimer('_config');
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function initializeLogger(Config $config): void
|
||||
{
|
||||
$this->startTimer('_logger', 'Logger');
|
||||
|
||||
// Initialize Logging
|
||||
$grav = $this->container;
|
||||
|
||||
switch ($config->get('system.log.handler', 'file')) {
|
||||
case 'syslog':
|
||||
$log = $grav['log'];
|
||||
$log->popHandler();
|
||||
|
||||
$facility = $config->get('system.log.syslog.facility', 'local6');
|
||||
$logHandler = new SyslogHandler('grav', $facility);
|
||||
$formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
|
||||
$logHandler->setFormatter($formatter);
|
||||
|
||||
$log->pushHandler($logHandler);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->stopTimer('_logger');
|
||||
}
|
||||
|
||||
protected function initializeErrors(): void
|
||||
{
|
||||
$this->startTimer('_errors', 'Error Handlers Reset');
|
||||
|
||||
// Initialize Error Handlers
|
||||
$this->container['errors']->resetHandlers();
|
||||
|
||||
$this->stopTimer('_errors');
|
||||
}
|
||||
|
||||
protected function initialize(Config $config): void
|
||||
{
|
||||
$this->startTimer('_init', 'Initialize');
|
||||
|
||||
// Use output buffering to prevent headers from being sent too early.
|
||||
ob_start();
|
||||
@@ -43,21 +137,6 @@ class InitializeProcessor extends ProcessorBase
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
|
||||
// FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
|
||||
if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
|
||||
// TODO: remove in 2.0.
|
||||
$this->container['accounts'];
|
||||
|
||||
try {
|
||||
$this->container['session']->init();
|
||||
} catch (SessionException $e) {
|
||||
$this->container['session']->init();
|
||||
$message = 'Session corruption detected, restarting session...';
|
||||
$this->addMessage($message);
|
||||
$this->container['messages']->add($message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = $this->container['uri'];
|
||||
$uri->init();
|
||||
@@ -73,8 +152,29 @@ class InitializeProcessor extends ProcessorBase
|
||||
}
|
||||
|
||||
$this->container->setLocale();
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
$this->stopTimer('_init');
|
||||
}
|
||||
|
||||
protected function initializeSession(Config $config): void
|
||||
{
|
||||
// FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
|
||||
if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
|
||||
$this->startTimer('_session', 'Start Session');
|
||||
|
||||
// TODO: remove in 2.0.
|
||||
$this->container['accounts'];
|
||||
|
||||
try {
|
||||
$this->container['session']->init();
|
||||
} catch (SessionException $e) {
|
||||
$this->container['session']->init();
|
||||
$message = 'Session corruption detected, restarting session...';
|
||||
$this->addMessage($message);
|
||||
$this->container['messages']->add($message, 'error');
|
||||
}
|
||||
|
||||
$this->stopTimer('_session');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Processors
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Processors;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Monolog\Handler\SyslogHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class LoggerProcessor extends ProcessorBase
|
||||
{
|
||||
public $id = '_logger';
|
||||
public $title = 'Logger';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
$grav = $this->container;
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
|
||||
switch ($config->get('system.log.handler', 'file')) {
|
||||
case 'syslog':
|
||||
$log = $grav['log'];
|
||||
$log->popHandler();
|
||||
|
||||
$facility = $config->get('system.log.syslog.facility', 'local6');
|
||||
$logHandler = new SyslogHandler('grav', $facility);
|
||||
$formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
|
||||
$logHandler->setFormatter($formatter);
|
||||
|
||||
$log->pushHandler($logHandler);
|
||||
break;
|
||||
}
|
||||
$this->stopTimer();
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class PagesProcessor extends ProcessorBase
|
||||
public $id = 'pages';
|
||||
public $title = 'Pages';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class PluginsProcessor extends ProcessorBase
|
||||
public $id = 'plugins';
|
||||
public $title = 'Plugins';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
// TODO: remove in 2.0.
|
||||
|
||||
@@ -25,21 +25,21 @@ abstract class ProcessorBase implements ProcessorInterface
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
protected function startTimer($id = null, $title = null)
|
||||
protected function startTimer($id = null, $title = null): void
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->container['debugger'];
|
||||
$debugger->startTimer($id ?? $this->id, $title ?? $this->title);
|
||||
}
|
||||
|
||||
protected function stopTimer($id = null)
|
||||
protected function stopTimer($id = null): void
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->container['debugger'];
|
||||
$debugger->stopTimer($id ?? $this->id);
|
||||
}
|
||||
|
||||
protected function addMessage($message, $label = 'info', $isString = true)
|
||||
protected function addMessage($message, $label = 'info', $isString = true): void
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->container['debugger'];
|
||||
|
||||
@@ -20,7 +20,7 @@ class RenderProcessor extends ProcessorBase
|
||||
public $id = 'render';
|
||||
public $title = 'Render';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class RequestProcessor extends ProcessorBase
|
||||
public $id = 'request';
|
||||
public $title = 'Request';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class SchedulerProcessor extends ProcessorBase
|
||||
public $id = '_scheduler';
|
||||
public $title = 'Scheduler';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$scheduler = $this->container['scheduler'];
|
||||
|
||||
@@ -19,7 +19,7 @@ class TasksProcessor extends ProcessorBase
|
||||
public $id = 'tasks';
|
||||
public $title = 'Tasks';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class ThemesProcessor extends ProcessorBase
|
||||
public $id = 'themes';
|
||||
public $title = 'Themes';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['themes']->init();
|
||||
|
||||
@@ -18,7 +18,7 @@ class TwigProcessor extends ProcessorBase
|
||||
public $id = 'twig';
|
||||
public $title = 'Twig';
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->startTimer();
|
||||
$this->container['twig']->init();
|
||||
|
||||
@@ -21,12 +21,20 @@ class Scheduler
|
||||
/**
|
||||
* The queued jobs.
|
||||
*
|
||||
* @var array
|
||||
* @var Job[]
|
||||
*/
|
||||
private $jobs = [];
|
||||
|
||||
/** @var Job[] */
|
||||
private $saved_jobs = [];
|
||||
|
||||
/** @var Job[] */
|
||||
private $executed_jobs = [];
|
||||
|
||||
/** @var Job[] */
|
||||
private $failed_jobs = [];
|
||||
|
||||
/** @var Job[] */
|
||||
private $jobs_run = [];
|
||||
private $output_schedule = [];
|
||||
private $config;
|
||||
@@ -49,6 +57,8 @@ class Scheduler
|
||||
|
||||
/**
|
||||
* Load saved jobs from config/scheduler.yaml file
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function loadSavedJobs()
|
||||
{
|
||||
@@ -65,7 +75,7 @@ class Scheduler
|
||||
}
|
||||
|
||||
if (isset($j['output'])) {
|
||||
$mode = isset($j['output_mode']) && $j['output_mode'] === 'append' ? true : false;
|
||||
$mode = isset($j['output_mode']) && $j['output_mode'] === 'append';
|
||||
$job->output($j['output'], $mode);
|
||||
}
|
||||
|
||||
@@ -106,7 +116,7 @@ class Scheduler
|
||||
/**
|
||||
* Get all jobs if they are disabled or not as one array
|
||||
*
|
||||
* @return array
|
||||
* @return Job[]
|
||||
*/
|
||||
public function getAllJobs()
|
||||
{
|
||||
@@ -184,6 +194,8 @@ class Scheduler
|
||||
* Reset all collected data of last run.
|
||||
*
|
||||
* Call before run() if you call run() multiple times.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function resetRun()
|
||||
{
|
||||
@@ -199,7 +211,7 @@ class Scheduler
|
||||
* Get the scheduler verbose output.
|
||||
*
|
||||
* @param string $type Allowed: text, html, array
|
||||
* @return mixed The return depends on the requested $type
|
||||
* @return string|array The return depends on the requested $type
|
||||
*/
|
||||
public function getVerboseOutput($type = 'text')
|
||||
{
|
||||
@@ -217,6 +229,8 @@ class Scheduler
|
||||
|
||||
/**
|
||||
* Remove all queued Jobs.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clearJobs()
|
||||
{
|
||||
@@ -263,7 +277,7 @@ class Scheduler
|
||||
/**
|
||||
* Get the Job states file
|
||||
*
|
||||
* @return \RocketTheme\Toolbox\File\FileInterface|YamlFile
|
||||
* @return YamlFile
|
||||
*/
|
||||
public function getJobStates()
|
||||
{
|
||||
@@ -296,7 +310,6 @@ class Scheduler
|
||||
* Queue a job for execution in the correct queue.
|
||||
*
|
||||
* @param Job $job
|
||||
* @return void
|
||||
*/
|
||||
private function queueJob(Job $job)
|
||||
{
|
||||
@@ -309,7 +322,6 @@ class Scheduler
|
||||
* Add an entry to the scheduler verbose output array.
|
||||
*
|
||||
* @param string $string
|
||||
* @return void
|
||||
*/
|
||||
private function addSchedulerVerboseOutput($string)
|
||||
{
|
||||
|
||||
@@ -9,11 +9,35 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
use enshrined\svgSanitize\Sanitizer;
|
||||
use Grav\Common\Page\Pages;
|
||||
|
||||
class Security
|
||||
{
|
||||
|
||||
/**
|
||||
* Sanitize SVG for XSS code
|
||||
*
|
||||
* @param $file
|
||||
*/
|
||||
public static function sanitizeSVG($file)
|
||||
{
|
||||
if (Grav::instance()['config']->get('security.sanitize_svg') && file_exists($file)) {
|
||||
$sanitizer = new Sanitizer();
|
||||
$original_svg = file_get_contents($file);
|
||||
$clean_svg = $sanitizer->sanitize($original_svg);
|
||||
file_put_contents($file, $clean_svg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect XSS code in Grav pages
|
||||
*
|
||||
* @param Pages $pages
|
||||
* @param bool $route
|
||||
* @param callable|null $status
|
||||
* @return array
|
||||
*/
|
||||
public static function detectXssFromPages(Pages $pages, $route = true, callable $status = null)
|
||||
{
|
||||
$routes = $pages->routes();
|
||||
@@ -51,7 +75,6 @@ class Security
|
||||
} else {
|
||||
$list[$page->filePathClean()] = $results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
@@ -63,6 +86,8 @@ class Security
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect XSS in an array or strings such as $_POST or $_GET
|
||||
*
|
||||
* @param array $array Array such as $_POST or $_GET
|
||||
* @param string $prefix Prefix for returned values.
|
||||
* @return array Returns flatten list of potentially dangerous input values, such as 'data.content'.
|
||||
@@ -89,6 +114,7 @@ class Security
|
||||
|
||||
/**
|
||||
* Determine if string potentially has a XSS attack. This simple function does not catch all XSS and it is likely to
|
||||
*
|
||||
* return false positives because of it tags all potentially dangerous HTML tags and attributes without looking into
|
||||
* their content.
|
||||
*
|
||||
|
||||
@@ -108,7 +108,8 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
'options' => [
|
||||
'formatter' => ['class' => YamlFormatter::class],
|
||||
'folder' => 'account://',
|
||||
'pattern' => '{FOLDER}/{KEY:2}/{KEY}/user.yaml',
|
||||
'file' => 'user',
|
||||
'pattern' => '{FOLDER}/{KEY:2}/{KEY}/{FILE}{EXT}',
|
||||
'key' => 'username',
|
||||
'indexed' => true
|
||||
],
|
||||
@@ -120,7 +121,7 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
'options' => [
|
||||
'formatter' => ['class' => YamlFormatter::class],
|
||||
'folder' => 'account://',
|
||||
'pattern' => '{FOLDER}/{KEY}.yaml',
|
||||
'pattern' => '{FOLDER}/{KEY}{EXT}',
|
||||
'key' => 'storage_key',
|
||||
'indexed' => true
|
||||
],
|
||||
|
||||
@@ -125,7 +125,7 @@ class Session extends \Grav\Framework\Session\Session
|
||||
|
||||
/** @var FormFlash $flash */
|
||||
$flash = $form ? $form->getFlash() : null;
|
||||
$object = $flash ? [$sessionField => $flash->getLegacyFiles()] : null;
|
||||
$object = $flash && method_exists($flash, 'getLegacyFiles') ? [$sessionField => $flash->getLegacyFiles()] : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,11 +30,11 @@ class Theme extends Plugin
|
||||
/**
|
||||
* Get configuration of the plugin.
|
||||
*
|
||||
* @return Config
|
||||
* @return array
|
||||
*/
|
||||
public function config()
|
||||
{
|
||||
return $this->config["themes.{$this->name}"];
|
||||
return $this->config["themes.{$this->name}"] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,7 +42,7 @@ class Theme extends Plugin
|
||||
*
|
||||
* @param string $theme_name The name of the theme whose config it should store.
|
||||
*
|
||||
* @return true
|
||||
* @return bool
|
||||
*/
|
||||
public static function saveConfig($theme_name)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ use Grav\Common\Config\Config;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Data\Blueprints;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
use RocketTheme\Toolbox\Event\EventSubscriberInterface;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
@@ -128,7 +129,7 @@ class Themes extends Iterator
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return Data
|
||||
* @return Data|null
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function get($name)
|
||||
@@ -214,7 +215,9 @@ class Themes extends Iterator
|
||||
}
|
||||
}
|
||||
} elseif (!$locator('theme://') && !defined('GRAV_CLI')) {
|
||||
exit("Theme '$name' does not exist, unable to display page.");
|
||||
$response = new Response(500, [], "Theme '$name' does not exist, unable to display page.");
|
||||
|
||||
$grav->close($response);
|
||||
}
|
||||
|
||||
$this->config->set('theme', $config->get('themes.' . $name));
|
||||
|
||||
61
system/src/Grav/Common/Twig/Node/TwigNodeCache.php
Normal file
61
system/src/Grav/Common/Twig/Node/TwigNodeCache.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Node;
|
||||
|
||||
|
||||
class TwigNodeCache extends Node
|
||||
{
|
||||
/**
|
||||
* @param AbstractExpression $key
|
||||
* @param AbstractExpression $lifetime
|
||||
* @param Node $body
|
||||
* @param integer $lineno
|
||||
* @param string $tag
|
||||
*/
|
||||
public function __construct(AbstractExpression $key, AbstractExpression $lifetime, Node $body, $lineno, $tag = null)
|
||||
{
|
||||
parent::__construct(array('key' => $key, 'body' => $body, 'lifetime' => $lifetime), array(), $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write("\$cache = \\Grav\\Common\\Grav::instance()['cache'];\n")
|
||||
->write("\$key = \"twigcache-\" . ")
|
||||
->subcompile($this->getNode('key'))
|
||||
->write(";\n")
|
||||
->write("\$lifetime = ")
|
||||
->subcompile($this->getNode('lifetime'))
|
||||
->write(";\n")
|
||||
->write("\$cache_body = \$cache->fetch(\$key);\n")
|
||||
->write("if (\$cache_body === false) {\n")
|
||||
->indent()
|
||||
->write("ob_start();\n")
|
||||
->indent()
|
||||
->subcompile($this->getNode('body'))
|
||||
->outdent()
|
||||
->write("\n")
|
||||
->write("\$cache_body = ob_get_clean();\n")
|
||||
->write("\$cache->save(\$key, \$cache_body, \$lifetime);\n")
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
->write("echo \$cache_body;\n")
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class TwigNodeMarkdown extends Node implements NodeOutputInterface
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @param Compiler $compiler A Twig Compiler instance
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
@@ -41,6 +41,6 @@ class TwigNodeMarkdown extends Node implements NodeOutputInterface
|
||||
->write('$lines = explode("\n", $content);' . PHP_EOL)
|
||||
->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
|
||||
->write('$content = join("\n", $content);' . PHP_EOL)
|
||||
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($content);' . PHP_EOL);
|
||||
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($context, $content);' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,15 @@ class TwigNodeRender extends Node implements NodeCaptureInterface
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(['object' => $object, 'layout' => $layout, 'context' => $context], [], $lineno, $tag);
|
||||
$nodes = ['object' => $object, 'layout' => $layout, 'context' => $context];
|
||||
$nodes = array_filter($nodes);
|
||||
|
||||
parent::__construct($nodes, [], $lineno, $tag);
|
||||
}
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @param Compiler $compiler A Twig Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
@@ -46,15 +49,15 @@ class TwigNodeRender extends Node implements NodeCaptureInterface
|
||||
$compiler->addDebugInfo($this);
|
||||
$compiler->write('$object = ')->subcompile($this->getNode('object'))->raw(';' . PHP_EOL);
|
||||
|
||||
$layout = $this->getNode('layout');
|
||||
if ($layout) {
|
||||
if ($this->hasNode('layout')) {
|
||||
$layout = $this->getNode('layout');
|
||||
$compiler->write('$layout = ')->subcompile($layout)->raw(';' . PHP_EOL);
|
||||
} else {
|
||||
$compiler->write('$layout = null;' . PHP_EOL);
|
||||
}
|
||||
|
||||
$context = $this->getNode('context');
|
||||
if ($context) {
|
||||
if ($this->hasNode('context')) {
|
||||
$context = $this->getNode('context');
|
||||
$compiler->write('$attributes = ')->subcompile($context)->raw(';' . PHP_EOL);
|
||||
} else {
|
||||
$compiler->write('$attributes = null;' . PHP_EOL);
|
||||
|
||||
@@ -29,21 +29,24 @@ class TwigNodeScript extends Node implements NodeCaptureInterface
|
||||
* @param string|null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
Node $body = null,
|
||||
AbstractExpression $file = null,
|
||||
AbstractExpression $group = null,
|
||||
AbstractExpression $priority = null,
|
||||
AbstractExpression $attributes = null,
|
||||
?Node $body,
|
||||
?AbstractExpression $file,
|
||||
?AbstractExpression $group,
|
||||
?AbstractExpression $priority,
|
||||
?AbstractExpression $attributes,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(['body' => $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes], [], $lineno, $tag);
|
||||
$nodes = ['body' => $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes];
|
||||
$nodes = array_filter($nodes);
|
||||
|
||||
parent::__construct($nodes, [], $lineno, $tag);
|
||||
}
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @param Compiler $compiler A Twig Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
@@ -52,7 +55,7 @@ class TwigNodeScript extends Node implements NodeCaptureInterface
|
||||
|
||||
$compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
|
||||
|
||||
if ($this->getNode('attributes') !== null) {
|
||||
if ($this->hasNode('attributes')) {
|
||||
$compiler
|
||||
->write('$attributes = ')
|
||||
->subcompile($this->getNode('attributes'))
|
||||
@@ -66,7 +69,7 @@ class TwigNodeScript extends Node implements NodeCaptureInterface
|
||||
$compiler->write('$attributes = [];' . "\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('group') !== null) {
|
||||
if ($this->hasNode('group')) {
|
||||
$compiler
|
||||
->write("\$attributes['group'] = ")
|
||||
->subcompile($this->getNode('group'))
|
||||
@@ -78,14 +81,14 @@ class TwigNodeScript extends Node implements NodeCaptureInterface
|
||||
->write("}\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('priority') !== null) {
|
||||
if ($this->hasNode('priority')) {
|
||||
$compiler
|
||||
->write("\$attributes['priority'] = (int)(")
|
||||
->subcompile($this->getNode('priority'))
|
||||
->raw(");\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('file') !== null) {
|
||||
if ($this->hasNode('file')) {
|
||||
$compiler
|
||||
->write('$assets->addJs(')
|
||||
->subcompile($this->getNode('file'))
|
||||
|
||||
@@ -29,21 +29,24 @@ class TwigNodeStyle extends Node implements NodeCaptureInterface
|
||||
* @param string|null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
Node $body = null,
|
||||
AbstractExpression $file = null,
|
||||
AbstractExpression $group = null,
|
||||
AbstractExpression $priority = null,
|
||||
AbstractExpression $attributes = null,
|
||||
?Node $body,
|
||||
?AbstractExpression $file,
|
||||
?AbstractExpression $group,
|
||||
?AbstractExpression $priority,
|
||||
?AbstractExpression $attributes,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(['body' => $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes], [], $lineno, $tag);
|
||||
$nodes = ['body' => $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes];
|
||||
$nodes = array_filter($nodes);
|
||||
|
||||
parent::__construct($nodes, [], $lineno, $tag);
|
||||
}
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @param Compiler $compiler A Twig Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
@@ -52,7 +55,7 @@ class TwigNodeStyle extends Node implements NodeCaptureInterface
|
||||
|
||||
$compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
|
||||
|
||||
if ($this->getNode('attributes') !== null) {
|
||||
if ($this->hasNode('attributes')) {
|
||||
$compiler
|
||||
->write('$attributes = ')
|
||||
->subcompile($this->getNode('attributes'))
|
||||
@@ -66,7 +69,7 @@ class TwigNodeStyle extends Node implements NodeCaptureInterface
|
||||
$compiler->write('$attributes = [];' . "\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('group') !== null) {
|
||||
if ($this->hasNode('group')) {
|
||||
$compiler
|
||||
->write("\$attributes['group'] = ")
|
||||
->subcompile($this->getNode('group'))
|
||||
@@ -78,14 +81,14 @@ class TwigNodeStyle extends Node implements NodeCaptureInterface
|
||||
->write("}\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('priority') !== null) {
|
||||
if ($this->hasNode('priority')) {
|
||||
$compiler
|
||||
->write("\$attributes['priority'] = (int)(")
|
||||
->subcompile($this->getNode('priority'))
|
||||
->raw(");\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('file') !== null) {
|
||||
if ($this->hasNode('file')) {
|
||||
$compiler
|
||||
->write('$assets->addCss(')
|
||||
->subcompile($this->getNode('file'))
|
||||
|
||||
@@ -30,13 +30,16 @@ class TwigNodeSwitch extends Node
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(array('value' => $value, 'cases' => $cases, 'default' => $default), array(), $lineno, $tag);
|
||||
$nodes = ['value' => $value, 'cases' => $cases, 'default' => $default];
|
||||
$nodes = array_filter($nodes);
|
||||
|
||||
parent::__construct($nodes, [], $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @param Compiler $compiler A Twig Compiler instance
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
@@ -68,7 +71,7 @@ class TwigNodeSwitch extends Node
|
||||
->write("}\n");
|
||||
}
|
||||
|
||||
if ($this->hasNode('default') && $this->getNode('default') !== null) {
|
||||
if ($this->hasNode('default')) {
|
||||
$compiler
|
||||
->write("default:\n")
|
||||
->write("{\n")
|
||||
|
||||
@@ -34,7 +34,7 @@ class TwigNodeThrow extends Node
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @param Compiler $compiler A Twig Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
|
||||
@@ -28,13 +28,16 @@ class TwigNodeTryCatch extends Node
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(['try' => $try, 'catch' => $catch], [], $lineno, $tag);
|
||||
$nodes = ['try' => $try, 'catch' => $catch];
|
||||
$nodes = array_filter($nodes);
|
||||
|
||||
parent::__construct($nodes, [], $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @param Compiler $compiler A Twig Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
@@ -50,7 +53,7 @@ class TwigNodeTryCatch extends Node
|
||||
->subcompile($this->getNode('try'))
|
||||
;
|
||||
|
||||
if ($this->hasNode('catch') && null !== $this->getNode('catch')) {
|
||||
if ($this->hasNode('catch')) {
|
||||
$compiler
|
||||
->outdent()
|
||||
->write('} catch (\Exception $e) {' . "\n")
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeCache;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
/**
|
||||
* Adds ability to cache Twig between tags.
|
||||
*
|
||||
* {% cache 'unique-key' 600 %}
|
||||
* {{ some_complex_work() }}
|
||||
* {% endcache %}
|
||||
*/
|
||||
class TwigTokenParserCache extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
$key = $this->parser->getExpressionParser()->parseExpression();
|
||||
$lifetime =$this->parser->getExpressionParser()->parseExpression();
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideCacheEnd'), true);
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new TwigNodeCache($key, $lifetime, $body, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide if current token marks end of cache block.
|
||||
*
|
||||
* @param Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideCacheEnd(Token $token)
|
||||
{
|
||||
return $token->test('endcache');
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'cache';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -24,9 +24,9 @@ class TwigTokenParserRender extends AbstractTokenParser
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
* @param Token $token A Twig Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
* @return Node A Twig Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
|
||||
@@ -29,9 +29,9 @@ class TwigTokenParserScript extends AbstractTokenParser
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
* @param Token $token A Twig Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
* @return Node A Twig Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
|
||||
@@ -28,9 +28,9 @@ class TwigTokenParserStyle extends AbstractTokenParser
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
* @param Token $token A Twig Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
* @return Node A Twig Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
|
||||
@@ -26,9 +26,9 @@ class TwigTokenParserThrow extends AbstractTokenParser
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
* @param Token $token A Twig Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
* @return Node A Twig Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
|
||||
@@ -30,9 +30,9 @@ class TwigTokenParserTryCatch extends AbstractTokenParser
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
* @param Token $token A Twig Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
* @return Node A Twig Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
|
||||
@@ -18,11 +18,21 @@ use Grav\Common\Page\Pages;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Phive\Twig\Extensions\Deferred\DeferredExtension;
|
||||
use Twig\Cache\FilesystemCache;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Extension\CoreExtension;
|
||||
use Twig\Extension\DebugExtension;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use Twig\Loader\ChainLoader;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class Twig
|
||||
{
|
||||
/**
|
||||
* @var \Twig_Environment
|
||||
* @var Environment
|
||||
*/
|
||||
public $twig;
|
||||
|
||||
@@ -47,18 +57,20 @@ class Twig
|
||||
protected $grav;
|
||||
|
||||
/**
|
||||
* @var \Twig_Loader_Filesystem
|
||||
* @var FilesystemLoader
|
||||
*/
|
||||
protected $loader;
|
||||
|
||||
/**
|
||||
* @var \Twig_Loader_Array
|
||||
* @var ArrayLoader
|
||||
*/
|
||||
protected $loaderArray;
|
||||
|
||||
|
||||
protected $autoescape;
|
||||
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -102,7 +114,7 @@ class Twig
|
||||
$core_templates = array_merge($locator->findResources('system://templates'), $locator->findResources('system://templates/testing'));
|
||||
$this->twig_paths = array_merge($this->twig_paths, $core_templates);
|
||||
|
||||
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
|
||||
$this->loader = new FilesystemLoader($this->twig_paths);
|
||||
|
||||
// Register all other prefixes as namespaces in twig
|
||||
foreach ($locator->getPaths('theme') as $prefix => $_) {
|
||||
@@ -128,13 +140,13 @@ class Twig
|
||||
|
||||
$this->grav->fireEvent('onTwigLoader');
|
||||
|
||||
$this->loaderArray = new \Twig_Loader_Array([]);
|
||||
$loader_chain = new \Twig_Loader_Chain([$this->loaderArray, $this->loader]);
|
||||
$this->loaderArray = new ArrayLoader([]);
|
||||
$loader_chain = new ChainLoader([$this->loaderArray, $this->loader]);
|
||||
|
||||
$params = $config->get('system.twig');
|
||||
if (!empty($params['cache'])) {
|
||||
$cachePath = $locator->findResource('cache://twig', true, true);
|
||||
$params['cache'] = new \Twig_Cache_Filesystem($cachePath, \Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION);
|
||||
$params['cache'] = new FilesystemCache($cachePath, FilesystemCache::FORCE_BYTECODE_INVALIDATION);
|
||||
}
|
||||
|
||||
if (!$config->get('system.strict_mode.twig_compat', true)) {
|
||||
@@ -153,10 +165,10 @@ class Twig
|
||||
if ($config->get('system.twig.undefined_functions')) {
|
||||
$this->twig->registerUndefinedFunctionCallback(function ($name) {
|
||||
if (function_exists($name)) {
|
||||
return new \Twig_SimpleFunction($name, $name);
|
||||
return new TwigFunction($name, $name);
|
||||
}
|
||||
|
||||
return new \Twig_SimpleFunction($name, function () {
|
||||
return new TwigFunction($name, function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -164,10 +176,10 @@ class Twig
|
||||
if ($config->get('system.twig.undefined_filters')) {
|
||||
$this->twig->registerUndefinedFilterCallback(function ($name) {
|
||||
if (function_exists($name)) {
|
||||
return new \Twig_SimpleFilter($name, $name);
|
||||
return new TwigFilter($name, $name);
|
||||
}
|
||||
|
||||
return new \Twig_SimpleFilter($name, function () {
|
||||
return new TwigFilter($name, function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -176,17 +188,21 @@ class Twig
|
||||
|
||||
// set default date format if set in config
|
||||
if ($config->get('system.pages.dateformat.long')) {
|
||||
/** @var \Twig_Extension_Core $extension */
|
||||
$extension = $this->twig->getExtension('Twig_Extension_Core');
|
||||
/** @var CoreExtension $extension */
|
||||
$extension = $this->twig->getExtension(CoreExtension::class);
|
||||
$extension->setDateFormat($config->get('system.pages.dateformat.long'));
|
||||
}
|
||||
// enable the debug extension if required
|
||||
if ($config->get('system.twig.debug')) {
|
||||
$this->twig->addExtension(new \Twig_Extension_Debug());
|
||||
$this->twig->addExtension(new DebugExtension());
|
||||
}
|
||||
$this->twig->addExtension(new TwigExtension());
|
||||
$this->twig->addExtension(new DeferredExtension());
|
||||
|
||||
$this->profile = new \Twig\Profiler\Profile();
|
||||
$this->twig->addExtension(new \Twig\Extension\ProfilerExtension($this->profile));
|
||||
|
||||
|
||||
$this->grav->fireEvent('onTwigExtensions');
|
||||
|
||||
/** @var Pages $pages */
|
||||
@@ -219,7 +235,7 @@ class Twig
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Twig_Environment
|
||||
* @return Environment
|
||||
*/
|
||||
public function twig()
|
||||
{
|
||||
@@ -227,13 +243,19 @@ class Twig
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Twig_Loader_Filesystem
|
||||
* @return FilesystemLoader
|
||||
*/
|
||||
public function loader()
|
||||
{
|
||||
return $this->loader;
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->profile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds or overrides a template.
|
||||
*
|
||||
@@ -254,7 +276,6 @@ class Twig
|
||||
* @param string $content Optional content override
|
||||
*
|
||||
* @return string The rendered output
|
||||
* @throws \Twig_Error_Loader
|
||||
*/
|
||||
public function processPage(PageInterface $item, $content = null)
|
||||
{
|
||||
@@ -288,7 +309,7 @@ class Twig
|
||||
$output = $local_twig->render($name, $twig_vars);
|
||||
}
|
||||
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
} catch (LoaderError $e) {
|
||||
throw new \RuntimeException($e->getRawMessage(), 404, $e);
|
||||
}
|
||||
|
||||
@@ -312,7 +333,7 @@ class Twig
|
||||
|
||||
try {
|
||||
$output = $this->twig->render($template, $vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
} catch (LoaderError $e) {
|
||||
throw new \RuntimeException($e->getRawMessage(), 404, $e);
|
||||
}
|
||||
|
||||
@@ -341,7 +362,7 @@ class Twig
|
||||
|
||||
try {
|
||||
$output = $this->twig->render($name, $vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
} catch (LoaderError $e) {
|
||||
throw new \RuntimeException($e->getRawMessage(), 404, $e);
|
||||
}
|
||||
|
||||
@@ -386,14 +407,14 @@ class Twig
|
||||
|
||||
try {
|
||||
$output = $this->twig->render($template, $vars + $twig_vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
} catch (LoaderError $e) {
|
||||
$error_msg = $e->getMessage();
|
||||
// Try html version of this template if initial template was NOT html
|
||||
if ($ext !== '.html' . TWIG_EXT) {
|
||||
try {
|
||||
$page->templateFormat('html');
|
||||
$output = $this->twig->render($page->template() . '.html' . TWIG_EXT, $vars + $twig_vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
} catch (LoaderError $e) {
|
||||
throw new \RuntimeException($error_msg, 400, $e);
|
||||
}
|
||||
} else {
|
||||
@@ -405,9 +426,10 @@ class Twig
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the Twig_Loader_Filesystem addPath method (should be used only in `onTwigLoader()` event
|
||||
* Wraps the FilesystemLoader addPath method (should be used only in `onTwigLoader()` event
|
||||
* @param string $template_path
|
||||
* @param string $namespace
|
||||
* @throws LoaderError
|
||||
*/
|
||||
public function addPath($template_path, $namespace = '__main__')
|
||||
{
|
||||
@@ -415,9 +437,10 @@ class Twig
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the Twig_Loader_Filesystem prependPath method (should be used only in `onTwigLoader()` event
|
||||
* Wraps the FilesystemLoader prependPath method (should be used only in `onTwigLoader()` event
|
||||
* @param string $template_path
|
||||
* @param string $namespace
|
||||
* @throws LoaderError
|
||||
*/
|
||||
public function prependPath($template_path, $namespace = '__main__')
|
||||
{
|
||||
|
||||
53
system/src/Grav/Common/Twig/TwigClockworkDataSource.php
Normal file
53
system/src/Grav/Common/Twig/TwigClockworkDataSource.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
use Clockwork\DataSource\DataSource;
|
||||
use Clockwork\Request\Request;
|
||||
use Clockwork\Request\Timeline;
|
||||
use Grav\Common\Grav;
|
||||
|
||||
class TwigClockworkDataSource extends DataSource
|
||||
{
|
||||
/**
|
||||
* Views data structure
|
||||
*/
|
||||
protected $views;
|
||||
|
||||
protected $root;
|
||||
|
||||
/**
|
||||
* TwigClockworkDataSource constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->views = new Timeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves and adds the Twig profiler data to the request
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Request
|
||||
*/
|
||||
public function resolve(Request $request)
|
||||
{
|
||||
$profile = Grav::instance()['twig']->profile();
|
||||
|
||||
if ($profile) {
|
||||
$processor = new TwigProfileProcessor();
|
||||
|
||||
$processor->process($profile, $this->views);
|
||||
$request->viewsData = $this->views->finalize();
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,9 @@
|
||||
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
class TwigEnvironment extends \Twig_Environment
|
||||
use Twig\Environment;
|
||||
|
||||
class TwigEnvironment extends Environment
|
||||
{
|
||||
use WriteCacheFileTrait;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use Grav\Common\Page\Collection;
|
||||
use Grav\Common\Page\Media;
|
||||
use Grav\Common\Scheduler\Cron;
|
||||
use Grav\Common\Security;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserCache;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserRender;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserScript;
|
||||
use Grav\Common\Twig\TokenParser\TwigTokenParserStyle;
|
||||
@@ -29,9 +30,16 @@ use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Yaml;
|
||||
use Grav\Common\Helpers\Base32;
|
||||
use Grav\Framework\Psr7\Response;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\Extension\GlobalsInterface;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
use Twig\TwigFilter;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface
|
||||
class TwigExtension extends AbstractExtension implements GlobalsInterface
|
||||
{
|
||||
/** @var Grav */
|
||||
protected $grav;
|
||||
@@ -72,60 +80,60 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
public function getFilters()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFilter('*ize', [$this, 'inflectorFilter']),
|
||||
new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']),
|
||||
new \Twig_SimpleFilter('contains', [$this, 'containsFilter']),
|
||||
new \Twig_SimpleFilter('chunk_split', [$this, 'chunkSplitFilter']),
|
||||
new \Twig_SimpleFilter('nicenumber', [$this, 'niceNumberFunc']),
|
||||
new \Twig_SimpleFilter('nicefilesize', [$this, 'niceFilesizeFunc']),
|
||||
new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFunc']),
|
||||
new \Twig_SimpleFilter('defined', [$this, 'definedDefaultFilter']),
|
||||
new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']),
|
||||
new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
|
||||
new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
|
||||
new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
|
||||
new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['needs_context' => true, 'is_safe' => ['html']]),
|
||||
new \Twig_SimpleFilter('md5', [$this, 'md5Filter']),
|
||||
new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']),
|
||||
new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']),
|
||||
new \Twig_SimpleFilter('base64_encode', [$this, 'base64EncodeFilter']),
|
||||
new \Twig_SimpleFilter('base64_decode', [$this, 'base64DecodeFilter']),
|
||||
new \Twig_SimpleFilter('randomize', [$this, 'randomizeFilter']),
|
||||
new \Twig_SimpleFilter('modulus', [$this, 'modulusFilter']),
|
||||
new \Twig_SimpleFilter('rtrim', [$this, 'rtrimFilter']),
|
||||
new \Twig_SimpleFilter('pad', [$this, 'padFilter']),
|
||||
new \Twig_SimpleFilter('regex_replace', [$this, 'regexReplace']),
|
||||
new \Twig_SimpleFilter('safe_email', [$this, 'safeEmailFilter']),
|
||||
new \Twig_SimpleFilter('safe_truncate', ['\Grav\Common\Utils', 'safeTruncate']),
|
||||
new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']),
|
||||
new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']),
|
||||
new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
|
||||
new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
|
||||
new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
|
||||
new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']),
|
||||
new \Twig_SimpleFilter('array_unique', 'array_unique'),
|
||||
new \Twig_SimpleFilter('basename', 'basename'),
|
||||
new \Twig_SimpleFilter('dirname', 'dirname'),
|
||||
new \Twig_SimpleFilter('print_r', 'print_r'),
|
||||
new \Twig_SimpleFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
|
||||
new \Twig_SimpleFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
|
||||
new \Twig_SimpleFilter('nicecron', [$this, 'niceCronFilter']),
|
||||
new TwigFilter('*ize', [$this, 'inflectorFilter']),
|
||||
new TwigFilter('absolute_url', [$this, 'absoluteUrlFilter']),
|
||||
new TwigFilter('contains', [$this, 'containsFilter']),
|
||||
new TwigFilter('chunk_split', [$this, 'chunkSplitFilter']),
|
||||
new TwigFilter('nicenumber', [$this, 'niceNumberFunc']),
|
||||
new TwigFilter('nicefilesize', [$this, 'niceFilesizeFunc']),
|
||||
new TwigFilter('nicetime', [$this, 'nicetimeFunc']),
|
||||
new TwigFilter('defined', [$this, 'definedDefaultFilter']),
|
||||
new TwigFilter('ends_with', [$this, 'endsWithFilter']),
|
||||
new TwigFilter('fieldName', [$this, 'fieldNameFilter']),
|
||||
new TwigFilter('ksort', [$this, 'ksortFilter']),
|
||||
new TwigFilter('ltrim', [$this, 'ltrimFilter']),
|
||||
new TwigFilter('markdown', [$this, 'markdownFunction'], ['needs_context' => true, 'is_safe' => ['html']]),
|
||||
new TwigFilter('md5', [$this, 'md5Filter']),
|
||||
new TwigFilter('base32_encode', [$this, 'base32EncodeFilter']),
|
||||
new TwigFilter('base32_decode', [$this, 'base32DecodeFilter']),
|
||||
new TwigFilter('base64_encode', [$this, 'base64EncodeFilter']),
|
||||
new TwigFilter('base64_decode', [$this, 'base64DecodeFilter']),
|
||||
new TwigFilter('randomize', [$this, 'randomizeFilter']),
|
||||
new TwigFilter('modulus', [$this, 'modulusFilter']),
|
||||
new TwigFilter('rtrim', [$this, 'rtrimFilter']),
|
||||
new TwigFilter('pad', [$this, 'padFilter']),
|
||||
new TwigFilter('regex_replace', [$this, 'regexReplace']),
|
||||
new TwigFilter('safe_email', [$this, 'safeEmailFilter']),
|
||||
new TwigFilter('safe_truncate', ['\Grav\Common\Utils', 'safeTruncate']),
|
||||
new TwigFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']),
|
||||
new TwigFilter('sort_by_key', [$this, 'sortByKeyFilter']),
|
||||
new TwigFilter('starts_with', [$this, 'startsWithFilter']),
|
||||
new TwigFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
|
||||
new TwigFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
|
||||
new TwigFilter('json_decode', [$this, 'jsonDecodeFilter']),
|
||||
new TwigFilter('array_unique', 'array_unique'),
|
||||
new TwigFilter('basename', 'basename'),
|
||||
new TwigFilter('dirname', 'dirname'),
|
||||
new TwigFilter('print_r', 'print_r'),
|
||||
new TwigFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
|
||||
new TwigFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
|
||||
new TwigFilter('nicecron', [$this, 'niceCronFilter']),
|
||||
|
||||
// Translations
|
||||
new \Twig_SimpleFilter('t', [$this, 'translate'], ['needs_environment' => true]),
|
||||
new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
|
||||
new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
|
||||
new TwigFilter('t', [$this, 'translate'], ['needs_environment' => true]),
|
||||
new TwigFilter('tl', [$this, 'translateLanguage']),
|
||||
new TwigFilter('ta', [$this, 'translateArray']),
|
||||
|
||||
// Casting values
|
||||
new \Twig_SimpleFilter('string', [$this, 'stringFilter']),
|
||||
new \Twig_SimpleFilter('int', [$this, 'intFilter'], ['is_safe' => ['all']]),
|
||||
new \Twig_SimpleFilter('bool', [$this, 'boolFilter']),
|
||||
new \Twig_SimpleFilter('float', [$this, 'floatFilter'], ['is_safe' => ['all']]),
|
||||
new \Twig_SimpleFilter('array', [$this, 'arrayFilter']),
|
||||
new TwigFilter('string', [$this, 'stringFilter']),
|
||||
new TwigFilter('int', [$this, 'intFilter'], ['is_safe' => ['all']]),
|
||||
new TwigFilter('bool', [$this, 'boolFilter']),
|
||||
new TwigFilter('float', [$this, 'floatFilter'], ['is_safe' => ['all']]),
|
||||
new TwigFilter('array', [$this, 'arrayFilter']),
|
||||
|
||||
// Object Types
|
||||
new \Twig_SimpleFilter('get_type', [$this, 'getTypeFunc']),
|
||||
new \Twig_SimpleFilter('of_type', [$this, 'ofTypeFunc'])
|
||||
new TwigFilter('get_type', [$this, 'getTypeFunc']),
|
||||
new TwigFilter('of_type', [$this, 'ofTypeFunc'])
|
||||
];
|
||||
}
|
||||
|
||||
@@ -137,54 +145,54 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFunction('array', [$this, 'arrayFilter']),
|
||||
new \Twig_SimpleFunction('array_key_value', [$this, 'arrayKeyValueFunc']),
|
||||
new \Twig_SimpleFunction('array_key_exists', 'array_key_exists'),
|
||||
new \Twig_SimpleFunction('array_unique', 'array_unique'),
|
||||
new \Twig_SimpleFunction('array_intersect', [$this, 'arrayIntersectFunc']),
|
||||
new \Twig_SimpleFunction('authorize', [$this, 'authorize']),
|
||||
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
|
||||
new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
|
||||
new \Twig_SimpleFunction('vardump', [$this, 'vardumpFunc']),
|
||||
new \Twig_SimpleFunction('print_r', 'print_r'),
|
||||
new \Twig_SimpleFunction('http_response_code', 'http_response_code'),
|
||||
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true]),
|
||||
new \Twig_SimpleFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true]),
|
||||
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
|
||||
new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
|
||||
new \Twig_SimpleFunction('pathinfo', 'pathinfo'),
|
||||
new \Twig_SimpleFunction('random_string', [$this, 'randomStringFunc']),
|
||||
new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']),
|
||||
new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']),
|
||||
new \Twig_SimpleFunction('regex_filter', [$this, 'regexFilter']),
|
||||
new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
|
||||
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
|
||||
new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']),
|
||||
new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
|
||||
new \Twig_SimpleFunction('redirect_me', [$this, 'redirectFunc']),
|
||||
new \Twig_SimpleFunction('range', [$this, 'rangeFunc']),
|
||||
new \Twig_SimpleFunction('isajaxrequest', [$this, 'isAjaxFunc']),
|
||||
new \Twig_SimpleFunction('exif', [$this, 'exifFunc']),
|
||||
new \Twig_SimpleFunction('media_directory', [$this, 'mediaDirFunc']),
|
||||
new \Twig_SimpleFunction('body_class', [$this, 'bodyClassFunc']),
|
||||
new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc']),
|
||||
new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc']),
|
||||
new \Twig_SimpleFunction('read_file', [$this, 'readFileFunc']),
|
||||
new \Twig_SimpleFunction('nicenumber', [$this, 'niceNumberFunc']),
|
||||
new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
|
||||
new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFunc']),
|
||||
new \Twig_SimpleFunction('cron', [$this, 'cronFunc']),
|
||||
new \Twig_SimpleFunction('xss', [$this, 'xssFunc']),
|
||||
new TwigFunction('array', [$this, 'arrayFilter']),
|
||||
new TwigFunction('array_key_value', [$this, 'arrayKeyValueFunc']),
|
||||
new TwigFunction('array_key_exists', 'array_key_exists'),
|
||||
new TwigFunction('array_unique', 'array_unique'),
|
||||
new TwigFunction('array_intersect', [$this, 'arrayIntersectFunc']),
|
||||
new TwigFunction('authorize', [$this, 'authorize']),
|
||||
new TwigFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
|
||||
new TwigFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
|
||||
new TwigFunction('vardump', [$this, 'vardumpFunc']),
|
||||
new TwigFunction('print_r', 'print_r'),
|
||||
new TwigFunction('http_response_code', 'http_response_code'),
|
||||
new TwigFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true]),
|
||||
new TwigFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true]),
|
||||
new TwigFunction('gist', [$this, 'gistFunc']),
|
||||
new TwigFunction('nonce_field', [$this, 'nonceFieldFunc']),
|
||||
new TwigFunction('pathinfo', 'pathinfo'),
|
||||
new TwigFunction('random_string', [$this, 'randomStringFunc']),
|
||||
new TwigFunction('repeat', [$this, 'repeatFunc']),
|
||||
new TwigFunction('regex_replace', [$this, 'regexReplace']),
|
||||
new TwigFunction('regex_filter', [$this, 'regexFilter']),
|
||||
new TwigFunction('string', [$this, 'stringFunc']),
|
||||
new TwigFunction('url', [$this, 'urlFunc']),
|
||||
new TwigFunction('json_decode', [$this, 'jsonDecodeFilter']),
|
||||
new TwigFunction('get_cookie', [$this, 'getCookie']),
|
||||
new TwigFunction('redirect_me', [$this, 'redirectFunc']),
|
||||
new TwigFunction('range', [$this, 'rangeFunc']),
|
||||
new TwigFunction('isajaxrequest', [$this, 'isAjaxFunc']),
|
||||
new TwigFunction('exif', [$this, 'exifFunc']),
|
||||
new TwigFunction('media_directory', [$this, 'mediaDirFunc']),
|
||||
new TwigFunction('body_class', [$this, 'bodyClassFunc']),
|
||||
new TwigFunction('theme_var', [$this, 'themeVarFunc']),
|
||||
new TwigFunction('header_var', [$this, 'pageHeaderVarFunc']),
|
||||
new TwigFunction('read_file', [$this, 'readFileFunc']),
|
||||
new TwigFunction('nicenumber', [$this, 'niceNumberFunc']),
|
||||
new TwigFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
|
||||
new TwigFunction('nicetime', [$this, 'nicetimeFunc']),
|
||||
new TwigFunction('cron', [$this, 'cronFunc']),
|
||||
new TwigFunction('xss', [$this, 'xssFunc']),
|
||||
|
||||
|
||||
// Translations
|
||||
new \Twig_SimpleFunction('t', [$this, 'translate'], ['needs_environment' => true]),
|
||||
new \Twig_SimpleFunction('tl', [$this, 'translateLanguage']),
|
||||
new \Twig_SimpleFunction('ta', [$this, 'translateArray']),
|
||||
new TwigFunction('t', [$this, 'translate'], ['needs_environment' => true]),
|
||||
new TwigFunction('tl', [$this, 'translateLanguage']),
|
||||
new TwigFunction('ta', [$this, 'translateArray']),
|
||||
|
||||
// Object Types
|
||||
new \Twig_SimpleFunction('get_type', [$this, 'getTypeFunc']),
|
||||
new \Twig_SimpleFunction('of_type', [$this, 'ofTypeFunc'])
|
||||
new TwigFunction('get_type', [$this, 'getTypeFunc']),
|
||||
new TwigFunction('of_type', [$this, 'ofTypeFunc'])
|
||||
];
|
||||
}
|
||||
|
||||
@@ -201,6 +209,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new TwigTokenParserStyle(),
|
||||
new TwigTokenParserMarkdown(),
|
||||
new TwigTokenParserSwitch(),
|
||||
new TwigTokenParserCache(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -457,7 +466,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
/**
|
||||
* Gets a human readable output for cron syntax
|
||||
*
|
||||
* @param $at
|
||||
* @param string $at
|
||||
* @return string
|
||||
*/
|
||||
public function niceCronFilter($at)
|
||||
@@ -484,7 +493,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
* @param bool $long_strings
|
||||
*
|
||||
* @param bool $show_tense
|
||||
* @return bool
|
||||
* @return string
|
||||
*/
|
||||
public function nicetimeFunc($date, $long_strings = true, $show_tense = true)
|
||||
{
|
||||
@@ -613,10 +622,11 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
/**
|
||||
* @param string $string
|
||||
*
|
||||
* @param array $context
|
||||
* @param bool $block Block or Line processing
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function markdownFunction($context = false, $string, $block = true)
|
||||
public function markdownFunction($context, $string, $block = true)
|
||||
{
|
||||
$page = $context['page'] ?? null;
|
||||
return Utils::processMarkdown($string, $block, $page);
|
||||
@@ -652,7 +662,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*/
|
||||
public function definedDefaultFilter($value, $default = null)
|
||||
{
|
||||
return null !== $value ? $value : $default;
|
||||
return $value ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -734,9 +744,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Environment $twig
|
||||
* @return string
|
||||
*/
|
||||
public function translate(\Twig_Environment $twig)
|
||||
public function translate(Environment $twig)
|
||||
{
|
||||
// shift off the environment
|
||||
$args = func_get_args();
|
||||
@@ -818,7 +829,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
public function urlFunc($input, $domain = false)
|
||||
{
|
||||
return Utils::url($input, $domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will evaluate Twig $twig through the $environment, and return its results.
|
||||
@@ -829,8 +840,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*/
|
||||
public function evaluateTwigFunc($context, $twig ) {
|
||||
|
||||
$loader = new \Twig_Loader_Filesystem('.');
|
||||
$env = new \Twig_Environment($loader);
|
||||
$loader = new FilesystemLoader('.');
|
||||
$env = new Environment($loader);
|
||||
|
||||
$template = $env->createTemplate($twig);
|
||||
|
||||
@@ -849,15 +860,14 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
return $this->evaluateTwigFunc($context, "{{ $string }}");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Based on Twig_Extension_Debug / twig_var_dump
|
||||
* Based on Twig\Extension\Debug / twig_var_dump
|
||||
* (c) 2011 Fabien Potencier
|
||||
*
|
||||
* @param \Twig_Environment $env
|
||||
* @param string $context
|
||||
* @param Environment $env
|
||||
* @param array $context
|
||||
*/
|
||||
public function dump(\Twig_Environment $env, $context)
|
||||
public function dump(Environment $env, $context)
|
||||
{
|
||||
if (!$env->isDebug() || !$this->debugger) {
|
||||
return;
|
||||
@@ -880,7 +890,8 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
$this->debugger->addMessage($data, 'debug');
|
||||
} else {
|
||||
for ($i = 2; $i < $count; $i++) {
|
||||
$this->debugger->addMessage(func_get_arg($i), 'debug');
|
||||
$var = func_get_arg($i);
|
||||
$this->debugger->addMessage($var, 'debug');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1105,8 +1116,9 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*/
|
||||
public function redirectFunc($url, $statusCode = 303)
|
||||
{
|
||||
header('Location: ' . $url, true, $statusCode);
|
||||
exit();
|
||||
$response = new Response($statusCode, ['location' => $url]);
|
||||
|
||||
$this->grav->close($response);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
74
system/src/Grav/Common/Twig/TwigProfileProcessor.php
Normal file
74
system/src/Grav/Common/Twig/TwigProfileProcessor.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
use Twig\Profiler\Profile;
|
||||
use Clockwork\Request\Timeline;
|
||||
|
||||
class TwigProfileProcessor
|
||||
{
|
||||
private $root;
|
||||
|
||||
public function process(Profile $profile, Timeline $views, $counter = 0, $prefix = '', $sibling = false)
|
||||
{
|
||||
if ($profile->isRoot()) {
|
||||
$this->root = $profile->getDuration();
|
||||
$name = $profile->getName();
|
||||
} else {
|
||||
if ($profile->isTemplate()) {
|
||||
$name = $this->formatTemplate($profile, $prefix);
|
||||
} else {
|
||||
$name = $this->formatNonTemplate($profile, $prefix);
|
||||
}
|
||||
$prefix .= '⎯⎯';
|
||||
}
|
||||
|
||||
$percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0;
|
||||
|
||||
$data = [
|
||||
'tm' => $this->formatTime($profile, $percent),
|
||||
'mu' => Utils::prettySize($profile->getMemoryUsage())
|
||||
];
|
||||
|
||||
if ($profile->isRoot()) {
|
||||
$data += ['pmu' => Utils::prettySize($profile->getPeakMemoryUsage())];
|
||||
}
|
||||
|
||||
|
||||
$views->addEvent(
|
||||
$counter,
|
||||
$profile->getTemplate(),
|
||||
0,
|
||||
$profile->getDuration(),
|
||||
[ 'name' => $name, 'data' => $data ]
|
||||
);
|
||||
|
||||
$nCount = count($profile->getProfiles());
|
||||
foreach ($profile as $i => $p) {
|
||||
$this->process($p, $views, ++$counter, $prefix, $i + 1 !== $nCount);
|
||||
}
|
||||
}
|
||||
|
||||
protected function formatTemplate(Profile $profile, $prefix)
|
||||
{
|
||||
return sprintf('%s⤍ %s', $prefix, $profile->getTemplate());
|
||||
}
|
||||
|
||||
protected function formatNonTemplate(Profile $profile, $prefix)
|
||||
{
|
||||
return sprintf('%s⤍ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName());
|
||||
}
|
||||
|
||||
protected function formatTime(Profile $profile, $percent)
|
||||
{
|
||||
return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent);
|
||||
}
|
||||
}
|
||||
@@ -207,6 +207,7 @@ class Uri
|
||||
|
||||
RouteFactory::setRoot($this->root_path);
|
||||
RouteFactory::setLanguage($language->getLanguageURLPrefix());
|
||||
RouteFactory::setParamValueDelimiter($config->get('system.param_sep'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,16 +301,17 @@ class Uri
|
||||
* Get URI parameter.
|
||||
*
|
||||
* @param string $id
|
||||
* @param bool $default
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function param($id)
|
||||
public function param($id, $default = false)
|
||||
{
|
||||
if (isset($this->params[$id])) {
|
||||
return html_entity_decode(rawurldecode($this->params[$id]));
|
||||
}
|
||||
|
||||
return false;
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -668,6 +670,7 @@ class Uri
|
||||
public static function getCurrentRoute()
|
||||
{
|
||||
if (!static::$currentRoute) {
|
||||
/** @var Uri $uri */
|
||||
$uri = Grav::instance()['uri'];
|
||||
static::$currentRoute = RouteFactory::createFromParts($uri->toArray());
|
||||
}
|
||||
@@ -1340,7 +1343,7 @@ class Uri
|
||||
/**
|
||||
* Check if this is a valid Grav extension
|
||||
*
|
||||
* @param $extension
|
||||
* @param string $extension
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidExtension($extension)
|
||||
@@ -1357,7 +1360,7 @@ class Uri
|
||||
/**
|
||||
* Allow overriding of any element (be careful!)
|
||||
*
|
||||
* @param $data
|
||||
* @param array $data
|
||||
* @return Uri
|
||||
*/
|
||||
public function setUriProperties($data)
|
||||
|
||||
@@ -207,7 +207,7 @@ class User extends Data implements UserInterface
|
||||
/**
|
||||
* Return media object for the User's avatar.
|
||||
*
|
||||
* @return ImageMedium|null
|
||||
* @return Medium|null
|
||||
* @deprecated 1.6 Use ->getAvatarImage() method instead.
|
||||
*/
|
||||
public function getAvatarMedia()
|
||||
|
||||
@@ -13,4 +13,13 @@ use Grav\Framework\Flex\Storage\FileStorage;
|
||||
|
||||
class UserFileStorage extends FileStorage
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @see FlexStorageInterface::getMediaPath()
|
||||
*/
|
||||
public function getMediaPath(string $key = null): ?string
|
||||
{
|
||||
// There is no media support for file storage (fallback to common location).
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,25 +9,28 @@
|
||||
|
||||
namespace Grav\Common\User\FlexUser;
|
||||
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Media\Interfaces\MediaCollectionInterface;
|
||||
use Grav\Common\Page\Media;
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Page\Medium\MediumFactory;
|
||||
use Grav\Common\User\Authentication;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\User\Traits\UserTrait;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\File\Formatter\JsonFormatter;
|
||||
use Grav\Framework\File\Formatter\YamlFormatter;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\FlexObject;
|
||||
use Grav\Framework\Flex\Storage\FileStorage;
|
||||
use Grav\Framework\Flex\Traits\FlexAuthorizeTrait;
|
||||
use Grav\Framework\Flex\Traits\FlexMediaTrait;
|
||||
use Grav\Framework\Form\FormFlashFile;
|
||||
use Grav\Framework\Media\Interfaces\MediaManipulationInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* Flex User
|
||||
@@ -50,7 +53,10 @@ use RocketTheme\Toolbox\File\FileInterface;
|
||||
*/
|
||||
class User extends FlexObject implements UserInterface, MediaManipulationInterface, \Countable
|
||||
{
|
||||
use FlexMediaTrait;
|
||||
use FlexMediaTrait {
|
||||
getMedia as private getFlexMedia;
|
||||
getMediaFolder as private getFlexMediaFolder;
|
||||
}
|
||||
use FlexAuthorizeTrait;
|
||||
use UserTrait;
|
||||
|
||||
@@ -382,31 +388,6 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
|
||||
return $this->getBlueprint()->extra($this->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Blueprint
|
||||
*/
|
||||
public function getBlueprint(string $name = '')
|
||||
{
|
||||
$blueprint = clone parent::getBlueprint($name);
|
||||
|
||||
$blueprint->addDynamicHandler('flex', function (array &$field, $property, array &$call) {
|
||||
$params = (array)$call['params'];
|
||||
$method = array_shift($params);
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
$value = $this->{$method}(...$params);
|
||||
if (\is_array($value) && isset($field[$property]) && \is_array($field[$property])) {
|
||||
$field[$property] = array_merge_recursive($field[$property], $value);
|
||||
} else {
|
||||
$field[$property] = $value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $blueprint->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return unmodified data as raw string.
|
||||
*
|
||||
@@ -446,6 +427,15 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
// TODO: We may want to handle this in the storage layer in the future.
|
||||
$key = $this->getStorageKey();
|
||||
if (!$key || strpos($key, '@@')) {
|
||||
$storage = $this->getFlexDirectory()->getStorage();
|
||||
if ($storage instanceof FileStorage) {
|
||||
$this->setStorageKey($this->getKey());
|
||||
}
|
||||
}
|
||||
|
||||
$password = $this->getProperty('password');
|
||||
if (null !== $password) {
|
||||
$this->unsetProperty('password');
|
||||
@@ -554,6 +544,50 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
|
||||
return \count($this->jsonSerialize());
|
||||
}
|
||||
|
||||
public function getMedia()
|
||||
{
|
||||
$media = $this->getFlexMedia();
|
||||
|
||||
// Deal with shared avatar folder.
|
||||
$path = $this->getAvatarFile();
|
||||
if ($path && !$media[$path] && is_file($path)) {
|
||||
$medium = MediumFactory::fromFile($path);
|
||||
if ($medium) {
|
||||
$media->add($path, $medium);
|
||||
$name = basename($path);
|
||||
if ($name !== $path) {
|
||||
$media->add($name, $medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
public function getMediaFolder(): ?string
|
||||
{
|
||||
$folder = $this->getFlexMediaFolder();
|
||||
if (!$folder) {
|
||||
// Shared media!
|
||||
$this->_loadMedia = false;
|
||||
$folder = $this->getBlueprint()->fields()['avatar']['destination'] ?? 'user://accounts/avatars';
|
||||
}
|
||||
|
||||
return $folder;
|
||||
}
|
||||
|
||||
protected function getAvatarFile(): ?string
|
||||
{
|
||||
$avatars = $this->getElement('avatar');
|
||||
if (\is_array($avatars) && $avatars) {
|
||||
$avatar = array_shift($avatars);
|
||||
|
||||
return $avatar['path'] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the associated media collection (original images).
|
||||
*
|
||||
@@ -561,7 +595,12 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
|
||||
*/
|
||||
protected function getOriginalMedia()
|
||||
{
|
||||
return (new Media($this->getMediaFolder() . '/original', $this->getMediaOrder()))->setTimestamps();
|
||||
$folder = $this->getMediaFolder();
|
||||
if ($folder) {
|
||||
$folder .= '/original';
|
||||
}
|
||||
|
||||
return (new Media($folder, $this->getMediaOrder()))->setTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -573,7 +612,38 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
|
||||
$list_original = [];
|
||||
foreach ($files as $field => $group) {
|
||||
foreach ($group as $filename => $file) {
|
||||
if (strpos($field, '/original')) {
|
||||
if ($file) {
|
||||
$filename = $file->getClientFilename();
|
||||
|
||||
/** @var FormFlashFile $file */
|
||||
$data = $file->jsonSerialize();
|
||||
unset($data['tmp_name'], $data['path']);
|
||||
} else {
|
||||
$data = null;
|
||||
}
|
||||
|
||||
$settings = $this->getBlueprint()->schema()->getProperty($field);
|
||||
|
||||
// Generate random name if required
|
||||
if ($settings['random_name'] ?? false) {
|
||||
$extension = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
$data['name'] = $filename = Utils::generateRandomString(15) . '.' . $extension;
|
||||
}
|
||||
|
||||
// For shared media folder we need to keep path for backwards compatibility.
|
||||
$folder = $this->getMediaFolder();
|
||||
if ($this->_loadMedia) {
|
||||
$filepath = $filename;
|
||||
} else {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$filepath = $locator->findResource($folder, false, true) . '/' . $filename;
|
||||
if ($data) {
|
||||
$data['path'] = $filepath;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_loadMedia && strpos($field, '/original')) {
|
||||
// Special handling for original images.
|
||||
$list_original[$filename] = $file;
|
||||
continue;
|
||||
@@ -581,15 +651,10 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
|
||||
|
||||
$list[$filename] = $file;
|
||||
|
||||
if ($file) {
|
||||
/** @var FormFlashFile $file */
|
||||
$data = $file->jsonSerialize();
|
||||
$path = $file->getClientFilename();
|
||||
unset($data['tmp_name'], $data['path']);
|
||||
|
||||
$this->setNestedProperty("{$field}\n{$path}", $data, "\n");
|
||||
if ($data) {
|
||||
$this->setNestedProperty("{$field}\n{$filepath}", $data, "\n");
|
||||
} else {
|
||||
$this->unsetNestedProperty("{$field}\n{$filename}", "\n");
|
||||
$this->unsetNestedProperty("{$field}\n{$filepath}", "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,7 +716,7 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
|
||||
$imageFile = $originalMedia[$filename] ?? $thumbFile;
|
||||
if ($thumbFile) {
|
||||
$list[$filename] = [
|
||||
'name' => $filename,
|
||||
'name' => $info['name'],
|
||||
'type' => $info['type'],
|
||||
'size' => $info['size'],
|
||||
'image_url' => $imageFile->url(),
|
||||
|
||||
@@ -59,6 +59,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
|
||||
*/
|
||||
public function find($query, $fields = ['username', 'email']): UserInterface
|
||||
{
|
||||
// FIXME: $fields is incompatible to parent class -- add support for finding value from multiple properties.
|
||||
foreach ((array)$fields as $field) {
|
||||
if ($field === 'key') {
|
||||
$user = $this->get($query);
|
||||
|
||||
@@ -13,6 +13,7 @@ use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\DataInterface;
|
||||
use Grav\Common\Media\Interfaces\MediaInterface;
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use RocketTheme\Toolbox\ArrayTraits\ExportInterface;
|
||||
|
||||
/**
|
||||
@@ -185,9 +186,9 @@ interface UserInterface extends DataInterface, MediaInterface, \ArrayAccess, \Js
|
||||
*
|
||||
* Note: if there's no local avatar image for the user, you should call getAvatarUrl() to get the external avatar URL.
|
||||
*
|
||||
* @return ImageMedium|null
|
||||
* @return Medium|null
|
||||
*/
|
||||
public function getAvatarImage(): ?ImageMedium;
|
||||
public function getAvatarImage(): ?Medium;
|
||||
|
||||
/**
|
||||
* Return the User's avatar URL.
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Grav\Common\User\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Medium\ImageMedium;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Page\Medium\StaticImageMedium;
|
||||
use Grav\Common\User\Authentication;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
@@ -107,9 +109,9 @@ trait UserTrait
|
||||
*
|
||||
* Note: if there's no local avatar image for the user, you should call getAvatarUrl() to get the external avatar URL.
|
||||
*
|
||||
* @return ImageMedium|null
|
||||
* @return Medium|null
|
||||
*/
|
||||
public function getAvatarImage(): ?ImageMedium
|
||||
public function getAvatarImage(): ?Medium
|
||||
{
|
||||
$avatars = $this->get('avatar');
|
||||
if (\is_array($avatars) && $avatars) {
|
||||
@@ -119,7 +121,8 @@ trait UserTrait
|
||||
$name = $avatar['name'] ?? null;
|
||||
|
||||
$image = $name ? $media[$name] : null;
|
||||
if ($image instanceof ImageMedium) {
|
||||
if ($image instanceof ImageMedium ||
|
||||
$image instanceof StaticImageMedium) {
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Markdown\ParsedownExtra;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use Negotiation\Accept;
|
||||
use Negotiation\Negotiator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
@@ -663,6 +665,39 @@ abstract class Utils
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output render format, usually the extension provided in the URL. (e.g. `html`, `json`, `xml`, etc).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getPageFormat(): string
|
||||
{
|
||||
/** @var Uri $uri */
|
||||
$uri = Grav::instance()['uri'];
|
||||
|
||||
// Set from uri extension
|
||||
$uri_extension = $uri->extension();
|
||||
if (is_string($uri_extension)) {
|
||||
return $uri_extension;
|
||||
}
|
||||
|
||||
// Use content negotiation via the `accept:` header
|
||||
$http_accept = $_SERVER['HTTP_ACCEPT'] ?? null;
|
||||
if (is_string($http_accept)) {
|
||||
$negotiator = new Negotiator();
|
||||
|
||||
$supported_types = Utils::getSupportPageTypes(['html', 'json']);
|
||||
$priorities = Utils::getMimeTypes($supported_types);
|
||||
|
||||
$media_type = $negotiator->getBest($http_accept, $priorities);
|
||||
$mimetype = $media_type instanceof Accept ? $media_type->getValue() : '';
|
||||
|
||||
return Utils::getExtensionByMime($mimetype);
|
||||
}
|
||||
|
||||
return 'html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mimetype based on filename extension
|
||||
*
|
||||
@@ -1321,7 +1356,7 @@ abstract class Utils
|
||||
*/
|
||||
public static function sortArrayByArray(array $array, array $orderArray)
|
||||
{
|
||||
$ordered = array();
|
||||
$ordered = [];
|
||||
foreach ($orderArray as $key) {
|
||||
if (array_key_exists($key, $array)) {
|
||||
$ordered[$key] = $array[$key];
|
||||
@@ -1476,16 +1511,16 @@ abstract class Utils
|
||||
/**
|
||||
* Parse a readable file size and return a value in bytes
|
||||
*
|
||||
* @param string|int $size
|
||||
* @param string|int|float $size
|
||||
* @return int
|
||||
*/
|
||||
public static function parseSize($size)
|
||||
{
|
||||
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
|
||||
$size = preg_replace('/[^0-9\.]/', '', $size);
|
||||
$size = (float)preg_replace('/[^0-9\.]/', '', $size);
|
||||
|
||||
if ($unit) {
|
||||
$size = $size * pow(1024, stripos('bkmgtpezy', $unit[0]));
|
||||
$size *= 1024 ** stripos('bkmgtpezy', $unit[0]);
|
||||
}
|
||||
|
||||
return (int) abs(round($size));
|
||||
@@ -1574,7 +1609,7 @@ abstract class Utils
|
||||
}
|
||||
|
||||
// Packed representation of IP
|
||||
$ip = inet_pton($ip);
|
||||
$ip = (string)inet_pton($ip);
|
||||
|
||||
// Maximum netmask length = same as packed address
|
||||
$len = 8*strlen($ip);
|
||||
|
||||
273
system/src/Grav/Console/Cli/PageSystemValidatorCommand.php
Normal file
273
system/src/Grav/Console/Cli/PageSystemValidatorCommand.php
Normal file
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Console\Cli
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class PageSystemValidatorCommand extends ConsoleCommand
|
||||
{
|
||||
protected $tests = [
|
||||
// Content
|
||||
'header' => [[]],
|
||||
'summary' => [[], [200], [200, true]],
|
||||
'content' => [[]],
|
||||
'getRawContent' => [[]],
|
||||
'rawMarkdown' => [[]],
|
||||
'value' => [['content'], ['route'], ['order'], ['ordering'], ['folder'], ['slug'], ['name'], /*['frontmatter'],*/ ['header.menu'], ['header.slug']],
|
||||
'title' => [[]],
|
||||
'menu' => [[]],
|
||||
'visible' => [[]],
|
||||
'published' => [[]],
|
||||
'publishDate' => [[]],
|
||||
'unpublishDate' => [[]],
|
||||
'process' => [[]],
|
||||
'slug' => [[]],
|
||||
'order' => [[]],
|
||||
//'id' => [[]],
|
||||
'modified' => [[]],
|
||||
'lastModified' => [[]],
|
||||
'folder' => [[]],
|
||||
'date' => [[]],
|
||||
'dateformat' => [[]],
|
||||
'taxonomy' => [[]],
|
||||
'shouldProcess' => [['twig'], ['markdown']],
|
||||
'isPage' => [[]],
|
||||
'isDir' => [[]],
|
||||
'exists' => [[]],
|
||||
|
||||
// Forms
|
||||
'forms' => [[]],
|
||||
|
||||
// Routing
|
||||
'urlExtension' => [[]],
|
||||
'routable' => [[]],
|
||||
'link' => [[], [false], [true]],
|
||||
'permalink' => [[]],
|
||||
'canonical' => [[], [false], [true]],
|
||||
'url' => [[], [true], [true, true], [true, true, false], [false, false, true, false]],
|
||||
'route' => [[]],
|
||||
'rawRoute' => [[]],
|
||||
'routeAliases' => [[]],
|
||||
'routeCanonical' => [[]],
|
||||
'redirect' => [[]],
|
||||
'relativePagePath' => [[]],
|
||||
'path' => [[]],
|
||||
//'folder' => [[]],
|
||||
'parent' => [[]],
|
||||
'topParent' => [[]],
|
||||
'currentPosition' => [[]],
|
||||
'active' => [[]],
|
||||
'activeChild' => [[]],
|
||||
'home' => [[]],
|
||||
'root' => [[]],
|
||||
|
||||
// Translations
|
||||
'translatedLanguages' => [[], [false], [true]],
|
||||
'untranslatedLanguages' => [[], [false], [true]],
|
||||
'language' => [[]],
|
||||
|
||||
// Legacy
|
||||
'raw' => [[]],
|
||||
'frontmatter' => [[]],
|
||||
'httpResponseCode' => [[]],
|
||||
'httpHeaders' => [[]],
|
||||
'blueprintName' => [[]],
|
||||
'name' => [[]],
|
||||
'childType' => [[]],
|
||||
'template' => [[]],
|
||||
'templateFormat' => [[]],
|
||||
'extension' => [[]],
|
||||
'expires' => [[]],
|
||||
'cacheControl' => [[]],
|
||||
'ssl' => [[]],
|
||||
'metadata' => [[]],
|
||||
'eTag' => [[]],
|
||||
'filePath' => [[]],
|
||||
'filePathClean' => [[]],
|
||||
'orderDir' => [[]],
|
||||
'orderBy' => [[]],
|
||||
'orderManual' => [[]],
|
||||
'maxCount' => [[]],
|
||||
'modular' => [[]],
|
||||
'modularTwig' => [[]],
|
||||
//'children' => [[]],
|
||||
'isFirst' => [[]],
|
||||
'isLast' => [[]],
|
||||
'prevSibling' => [[]],
|
||||
'nextSibling' => [[]],
|
||||
'adjacentSibling' => [[]],
|
||||
'ancestor' => [[]],
|
||||
//'inherited' => [[]],
|
||||
//'inheritedField' => [[]],
|
||||
'find' => [['/']],
|
||||
//'collection' => [[]],
|
||||
//'evaluate' => [[]],
|
||||
'folderExists' => [[]],
|
||||
//'getOriginal' => [[]],
|
||||
//'getAction' => [[]],
|
||||
];
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('page-system-validator')
|
||||
->setDescription('Page validator can be used to compare site before/after update and when migrating to Flex Pages.')
|
||||
->addOption('record', 'r', InputOption::VALUE_NONE, 'Record results')
|
||||
->addOption('check', 'c', InputOption::VALUE_NONE, 'Compare site against previously recorded results')
|
||||
->setHelp('The <info>page-system-validator</info> command can be used to test the pages before and after upgrade');
|
||||
}
|
||||
|
||||
protected function serve()
|
||||
{
|
||||
$this->output->writeln('');
|
||||
|
||||
$this->grav = $grav = Grav::instance();
|
||||
$grav->setup();
|
||||
|
||||
// Initialize
|
||||
$grav['uri']->init();
|
||||
/** @var Config $config */
|
||||
$config = $grav['config'];
|
||||
$config->init();
|
||||
$grav['plugins']->setup();
|
||||
$grav['debugger']->init();
|
||||
// Initialize the timezone.
|
||||
$timezone = $config->get('system.timezone');
|
||||
if ($timezone) {
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
/** @var Uri $uri */
|
||||
$uri = $grav['uri'];
|
||||
$uri->init();
|
||||
$grav->setLocale();
|
||||
$grav['language']->setActive('en');
|
||||
|
||||
// Plugins
|
||||
$grav['accounts'];
|
||||
$grav['plugins']->init();
|
||||
$grav->fireEvent('onPluginsInitialized');
|
||||
|
||||
// Themes
|
||||
$grav['themes']->init();
|
||||
|
||||
// Twig
|
||||
$grav['twig']->init();
|
||||
|
||||
// Pages
|
||||
$grav['pages']->init();
|
||||
$grav->fireEvent('onPagesInitialized', new Event(['pages' => $grav['pages']]));
|
||||
|
||||
if ($this->input->getOption('record')) {
|
||||
$this->output->writeln('Pages: ' . $config->get('system.pages.type', 'page'));
|
||||
|
||||
$this->output->writeln('<magenta>Record tests</magenta>');
|
||||
$this->output->writeln('');
|
||||
|
||||
$results = $this->record();
|
||||
$file = $this->getFile('pages-old');
|
||||
$file->save($results);
|
||||
|
||||
$this->output->writeln('Recorded tests to ' . $file->filename());
|
||||
} elseif ($this->input->getOption('check')) {
|
||||
$this->output->writeln('Pages: ' . $config->get('system.pages.type', 'page'));
|
||||
|
||||
$this->output->writeln('<magenta>Run tests</magenta>');
|
||||
$this->output->writeln('');
|
||||
|
||||
$new = $this->record();
|
||||
$file = $this->getFile('pages-new');
|
||||
$file->save($new);
|
||||
$this->output->writeln('Recorded tests to ' . $file->filename());
|
||||
|
||||
$file = $this->getFile('pages-old');
|
||||
$old = $file->content();
|
||||
|
||||
$results = $this->check($old, $new);
|
||||
$file = $this->getFile('diff');
|
||||
$file->save($results);
|
||||
$this->output->writeln('Recorded results to ' . $file->filename());
|
||||
} else {
|
||||
$this->output->writeln('<green>page-system-validator [-r|--record] [-c|--check]</green>');
|
||||
}
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
private function record()
|
||||
{
|
||||
$pages = $this->grav['pages'];
|
||||
$all = $pages->all();
|
||||
|
||||
$results = [];
|
||||
foreach ($all as $page) {
|
||||
foreach ($this->tests as $method => $params) {
|
||||
$params = $params ?: [[]];
|
||||
foreach ($params as $p) {
|
||||
$result = $page->$method(...$p);
|
||||
if (in_array($method, ['summary', 'content', 'getRawContent'], true)) {
|
||||
$result = preg_replace('/name="(form-nonce|__unique_form_id__)" value="[^"]+"/', 'name="\\1" value="DYNAMIC"', $result);
|
||||
$result = preg_replace('`src=("|\'|")/images/./././././[^"]+\\1`', 'src="\\1images/GENERATED\\1', $result);
|
||||
$result = preg_replace('/\?\d{10}/', '?1234567890', $result);
|
||||
} elseif ($method === 'httpHeaders' && isset($result['Expires'])) {
|
||||
$result['Expires'] = 'Thu, 19 Sep 2019 13:10:24 GMT (REPLACED AS DYNAMIC)';
|
||||
} elseif ($result instanceof PageInterface) {
|
||||
$result = $result->rawRoute();
|
||||
} elseif (is_object($result)) {
|
||||
$result = json_decode(json_encode($result), true);
|
||||
}
|
||||
|
||||
$ps = [];
|
||||
foreach ($p as $val) {
|
||||
$ps[] = (string)var_export($val, true);
|
||||
}
|
||||
$pstr = implode(', ', $ps);
|
||||
$call = "->{$method}({$pstr})";
|
||||
$results[$page->rawRoute()][$call] = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return json_decode(json_encode($results), true);
|
||||
}
|
||||
|
||||
private function check(array $old, array $new)
|
||||
{
|
||||
$errors = [];
|
||||
foreach ($old as $path => $page) {
|
||||
if (!isset($new[$path])) {
|
||||
$errors[$path] = 'PAGE REMOVED';
|
||||
continue;
|
||||
}
|
||||
foreach ($page as $method => $test) {
|
||||
if (($new[$path][$method] ?? null) !== $test) {
|
||||
$errors[$path][$method] = ['old' => $test, 'new' => $new[$path][$method]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return CompiledYamlFile
|
||||
*/
|
||||
private function getFile(string $name)
|
||||
{
|
||||
return CompiledYamlFile::instance('cache://tests/' . $name . '.yaml');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class SchedulerCommand extends ConsoleCommand
|
||||
// Show jobs list
|
||||
|
||||
$jobs = $scheduler->getAllJobs();
|
||||
$job_states = $scheduler->getJobStates()->content();
|
||||
$job_states = (array)$scheduler->getJobStates()->content();
|
||||
$rows = [];
|
||||
|
||||
$table = new Table($this->output);
|
||||
@@ -113,7 +113,7 @@ class SchedulerCommand extends ConsoleCommand
|
||||
$io->newLine();
|
||||
} elseif ($this->input->getOption('details')) {
|
||||
$jobs = $scheduler->getAllJobs();
|
||||
$job_states = $scheduler->getJobStates()->content();
|
||||
$job_states = (array)$scheduler->getJobStates()->content();
|
||||
|
||||
$io->title('Job Details');
|
||||
|
||||
|
||||
137
system/src/Grav/Console/Cli/ServerCommand.php
Normal file
137
system/src/Grav/Console/Cli/ServerCommand.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Console\Cli
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Helpers\LogViewer;
|
||||
use Grav\Common\Helpers\YamlLinter;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\PhpProcess;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class ServerCommand extends ConsoleCommand
|
||||
{
|
||||
const SYMFONY_SERVER = 'Symfony Server';
|
||||
const PHP_SERVER = 'Built-in PHP Server';
|
||||
|
||||
protected $ip;
|
||||
protected $port;
|
||||
protected $io;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('server')
|
||||
->addOption('port', 'p', InputOption::VALUE_OPTIONAL, 'Preferred HTTP port rather than auto-find (default is 8000-9000')
|
||||
->addOption('symfony', null, InputOption::VALUE_NONE, 'Force using Symfony server')
|
||||
->addOption('php', null, InputOption::VALUE_NONE, 'Force using built-in PHP server')
|
||||
->setDescription("Runs built-in web-server, Symfony first, then tries PHP's")
|
||||
->setHelp("Runs built-in web-server, Symfony first, then tries PHP's");
|
||||
}
|
||||
|
||||
protected function serve()
|
||||
{
|
||||
$io = $this->io = new SymfonyStyle($this->input, $this->output);
|
||||
|
||||
$io->title('Grav Web Server');
|
||||
|
||||
// Ensure CLI colors are on
|
||||
ini_set('cli_server.color', 'on');
|
||||
|
||||
// Options
|
||||
$force_symfony = $this->input->getOption('symfony');
|
||||
$force_php = $this->input->getOption('php');
|
||||
|
||||
// Find PHP
|
||||
$executableFinder = new PhpExecutableFinder();
|
||||
$php = $executableFinder->find(false);
|
||||
|
||||
$this->ip = '127.0.0.1';
|
||||
$this->port = intval($this->input->getOption('port') ?? 8000);
|
||||
|
||||
|
||||
// Get an open port
|
||||
while (!$this->portAvailable($this->ip, $this->port)) {
|
||||
$this->port += 1;
|
||||
}
|
||||
|
||||
// Setup the commands
|
||||
$symfony_cmd = ['symfony', 'server:start', '--ansi', '--port=' . $this->port ];
|
||||
$php_cmd = [$php, '-S', $this->ip.':'.$this->port, 'system/router.php'];
|
||||
|
||||
$commands = [
|
||||
self::SYMFONY_SERVER => $symfony_cmd,
|
||||
self::PHP_SERVER => $php_cmd
|
||||
];
|
||||
|
||||
if ($force_symfony) {
|
||||
unset($commands[self::PHP_SERVER]);
|
||||
} elseif ($force_php) {
|
||||
unset($commands[self::SYMFONY_SERVER]);
|
||||
}
|
||||
|
||||
foreach($commands as $name => $command) {
|
||||
$process = $this->runProcess($name, $command);
|
||||
|
||||
if (!$process) {
|
||||
$io->note('Starting ' . $name . '...');
|
||||
}
|
||||
|
||||
// Should only get here if there's an error running
|
||||
if (!$process->isRunning() && (
|
||||
($name === self::SYMFONY_SERVER && $force_symfony) ||
|
||||
($name === self::PHP_SERVER)
|
||||
)) {
|
||||
$io->error('Could not start ' . $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function runProcess($name, $cmd)
|
||||
{
|
||||
$process = new Process($cmd);
|
||||
$process->setTimeout(0);
|
||||
$process->start();
|
||||
|
||||
if ($name === self::PHP_SERVER) {
|
||||
$this->io->success('Built-in PHP web server listening on http://' . $this->ip . ':' . $this->port . ' (PHP v' . phpversion() . ')');
|
||||
}
|
||||
|
||||
$process->wait(function ($type, $buffer) {
|
||||
$this->output->write($buffer);
|
||||
});
|
||||
|
||||
return $process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple function test the port
|
||||
*
|
||||
* @param $ip
|
||||
* @param $port
|
||||
* @return bool
|
||||
*/
|
||||
protected function portAvailable($ip, $port) {
|
||||
$fp = @fsockopen($ip, $port, $errno, $errstr, 0.1);
|
||||
if (!$fp) {
|
||||
return true;
|
||||
} else {
|
||||
fclose($fp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,9 +10,7 @@
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Helpers\LogViewer;
|
||||
use Grav\Common\Helpers\YamlLinter;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
@@ -29,6 +27,18 @@ class YamlLinterCommand extends ConsoleCommand
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com'
|
||||
)
|
||||
->addOption(
|
||||
'all',
|
||||
'a',
|
||||
InputOption::VALUE_NONE,
|
||||
'Go through the whole Grav installation'
|
||||
)
|
||||
->addOption(
|
||||
'folder',
|
||||
'f',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Go through specific folder'
|
||||
)
|
||||
->setDescription('Checks various files for YAML errors')
|
||||
->setHelp("Checks various files for YAML errors");
|
||||
}
|
||||
@@ -42,41 +52,59 @@ class YamlLinterCommand extends ConsoleCommand
|
||||
|
||||
$io->title('Yaml Linter');
|
||||
|
||||
$io->section('User Configuration');
|
||||
$errors = YamlLinter::lintConfig();
|
||||
if ($this->input->getOption('all')) {
|
||||
$io->section('All');
|
||||
$errors = YamlLinter::lint('');
|
||||
|
||||
if (empty($errors)) {
|
||||
$io->success('No YAML Linting issues with configuration');
|
||||
if (empty($errors)) {
|
||||
$io->success('No YAML Linting issues found');
|
||||
} else {
|
||||
$this->displayErrors($errors, $io);
|
||||
}
|
||||
} elseif ($folder = $this->input->getOption('folder')) {
|
||||
$io->section($folder);
|
||||
$errors = YamlLinter::lint($folder);
|
||||
|
||||
if (empty($errors)) {
|
||||
$io->success('No YAML Linting issues found');
|
||||
} else {
|
||||
$this->displayErrors($errors, $io);
|
||||
}
|
||||
} else {
|
||||
$this->displayErrors($errors, $io);
|
||||
$io->section('User Configuration');
|
||||
$errors = YamlLinter::lintConfig();
|
||||
|
||||
if (empty($errors)) {
|
||||
$io->success('No YAML Linting issues with configuration');
|
||||
} else {
|
||||
$this->displayErrors($errors, $io);
|
||||
}
|
||||
|
||||
$io->section('Pages Frontmatter');
|
||||
$errors = YamlLinter::lintPages();
|
||||
|
||||
if (empty($errors)) {
|
||||
$io->success('No YAML Linting issues with pages');
|
||||
} else {
|
||||
$this->displayErrors($errors, $io);
|
||||
}
|
||||
|
||||
$io->section('Page Blueprints');
|
||||
$errors = YamlLinter::lintBlueprints();
|
||||
|
||||
if (empty($errors)) {
|
||||
$io->success('No YAML Linting issues with blueprints');
|
||||
} else {
|
||||
$this->displayErrors($errors, $io);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$io->section('Pages Frontmatter');
|
||||
$errors = YamlLinter::lintPages();
|
||||
|
||||
if (empty($errors)) {
|
||||
$io->success('No YAML Linting issues with pages');
|
||||
} else {
|
||||
$this->displayErrors($errors, $io);
|
||||
}
|
||||
|
||||
$io->section('Page Blueprints');
|
||||
$errors = YamlLinter::lintBlueprints();
|
||||
|
||||
if (empty($errors)) {
|
||||
$io->success('No YAML Linting issues with blueprints');
|
||||
} else {
|
||||
$this->displayErrors($errors, $io);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function displayErrors($errors, $io)
|
||||
protected function displayErrors($errors, SymfonyStyle $io)
|
||||
{
|
||||
$io->error("YAML Linting issues found...");
|
||||
$io->error('YAML Linting issues found...');
|
||||
foreach ($errors as $path => $error) {
|
||||
$io->writeln("<yellow>$path</yellow> - $error");
|
||||
$io->writeln("<yellow>{$path}</yellow> - {$error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class IndexCommand extends ConsoleCommand
|
||||
{
|
||||
/** @var array */
|
||||
/** @var Packages */
|
||||
protected $data;
|
||||
|
||||
/** @var GPM */
|
||||
@@ -188,9 +188,9 @@ class IndexCommand extends ConsoleCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param Packages $data
|
||||
*
|
||||
* @return mixed
|
||||
* @return Packages
|
||||
*/
|
||||
public function filter($data)
|
||||
{
|
||||
@@ -245,6 +245,7 @@ class IndexCommand extends ConsoleCommand
|
||||
|
||||
/**
|
||||
* @param Packages $packages
|
||||
* @return Packages
|
||||
*/
|
||||
public function sort($packages)
|
||||
{
|
||||
|
||||
@@ -551,10 +551,9 @@ class InstallCommand extends ConsoleCommand
|
||||
|
||||
/**
|
||||
* @param Package $package
|
||||
* @param string|null $license
|
||||
*
|
||||
* @param string $license
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
private function downloadPackage($package, $license = null)
|
||||
{
|
||||
@@ -586,7 +585,7 @@ class InstallCommand extends ConsoleCommand
|
||||
$this->output->writeln(' |- Downloading package... <red>error</red> ');
|
||||
$this->output->writeln(" | '- " . $error);
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
Folder::create($this->tmp);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user