mirror of
https://github.com/getgrav/grav.git
synced 2025-12-05 15:29:57 +01:00
Compare commits
162 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
505661404b | ||
|
|
ce51491b4d | ||
|
|
d241223aa3 | ||
|
|
d16f83fdd8 | ||
|
|
02e10ff8fe | ||
|
|
6a44d8f286 | ||
|
|
4b614d871f | ||
|
|
3286d70092 | ||
|
|
9fc37e46fa | ||
|
|
f304f429c5 | ||
|
|
65c73f639f | ||
|
|
d2833a1997 | ||
|
|
aa8f764436 | ||
|
|
618a59921a | ||
|
|
039f71dc61 | ||
|
|
27b8db4c10 | ||
|
|
afd53d76c2 | ||
|
|
57c65ad881 | ||
|
|
a372ae90c2 | ||
|
|
c8739c40a5 | ||
|
|
00ff9ac42d | ||
|
|
e13a8304e6 | ||
|
|
3c2b17853c | ||
|
|
288b2a1953 | ||
|
|
86b1f1fbac | ||
|
|
b5e26133a7 | ||
|
|
c97faa0238 | ||
|
|
69b39b4b21 | ||
|
|
02f544f813 | ||
|
|
69b5a779e4 | ||
|
|
fa5c1e495d | ||
|
|
7fdb2c10cb | ||
|
|
e422eebd3c | ||
|
|
3dca7e3539 | ||
|
|
75210b102e | ||
|
|
f0e97a7277 | ||
|
|
e16c81516e | ||
|
|
140c9a941f | ||
|
|
eb58fe9e97 | ||
|
|
5d4ea87402 | ||
|
|
9f1d7240a9 | ||
|
|
84894274f0 | ||
|
|
ea09002012 | ||
|
|
601ec5cb7a | ||
|
|
85ec2ee3a0 | ||
|
|
965f69f680 | ||
|
|
13a56dd4da | ||
|
|
4cf1b8c400 | ||
|
|
9c805a4317 | ||
|
|
b7b1182e14 | ||
|
|
f695aaaea4 | ||
|
|
b398d04d96 | ||
|
|
ac9ef4da76 | ||
|
|
a10893eaad | ||
|
|
0e0d5b23be | ||
|
|
e6c8b30882 | ||
|
|
a222e353ba | ||
|
|
30cfe3bdfa | ||
|
|
d227a82056 | ||
|
|
9825daa79b | ||
|
|
10d405112c | ||
|
|
c4c70d082c | ||
|
|
23a928c5b7 | ||
|
|
8a7f624558 | ||
|
|
92e8dbf4ea | ||
|
|
aca04cee6b | ||
|
|
7bc45bd9e0 | ||
|
|
9e5363b4df | ||
|
|
37f69d89cc | ||
|
|
30e96aa4ff | ||
|
|
7e1d3b260e | ||
|
|
be558ccac9 | ||
|
|
e991056106 | ||
|
|
b795155345 | ||
|
|
99d0c7cb3e | ||
|
|
01b85f19bc | ||
|
|
65ba214494 | ||
|
|
ccff51144e | ||
|
|
c8f6e7b0e1 | ||
|
|
7cccf8e237 | ||
|
|
e1f646c308 | ||
|
|
2a49a2ba7c | ||
|
|
e9bbd1e0b0 | ||
|
|
84e259f0f1 | ||
|
|
b3f4461f34 | ||
|
|
1df6b76e25 | ||
|
|
2153e20bc5 | ||
|
|
0cbc98ab53 | ||
|
|
0915a0413d | ||
|
|
f4d3d302f4 | ||
|
|
5d5e2264c0 | ||
|
|
b39fc72bd2 | ||
|
|
d6d50c4b66 | ||
|
|
791ef8ad88 | ||
|
|
e0861e5505 | ||
|
|
48c9176d90 | ||
|
|
33cb20561e | ||
|
|
0acb38f586 | ||
|
|
1a41e00a4f | ||
|
|
48ef93e495 | ||
|
|
3f7da86711 | ||
|
|
11aa2314d5 | ||
|
|
179c5065ca | ||
|
|
7c60f73942 | ||
|
|
7095c665b7 | ||
|
|
c6d94885e0 | ||
|
|
d9ddab8239 | ||
|
|
a118d45177 | ||
|
|
416c400367 | ||
|
|
e6839530d8 | ||
|
|
fea9e53be3 | ||
|
|
c5b3792a60 | ||
|
|
4f83b5da5b | ||
|
|
504c8faf4c | ||
|
|
4d6db5b334 | ||
|
|
ec1fc1f1e3 | ||
|
|
563cf8900c | ||
|
|
0850c2f362 | ||
|
|
f30334d80f | ||
|
|
9057a804a2 | ||
|
|
4c5c26033a | ||
|
|
a4f679adcf | ||
|
|
e9e12392ac | ||
|
|
6b4663c2ff | ||
|
|
0411c3a98b | ||
|
|
972a758ac9 | ||
|
|
05d72306c6 | ||
|
|
18b7c0955d | ||
|
|
71cbbf4e1e | ||
|
|
0606e12872 | ||
|
|
afc7cac5ab | ||
|
|
471e3d8954 | ||
|
|
89a92cddc7 | ||
|
|
74988f1254 | ||
|
|
f2f2bc1cf8 | ||
|
|
31c5809e4a | ||
|
|
b4b8b63e24 | ||
|
|
9c0de8b0d3 | ||
|
|
f4cca777c2 | ||
|
|
8d7d143d01 | ||
|
|
d9109e9934 | ||
|
|
be8eb63944 | ||
|
|
9342981d8c | ||
|
|
6e9af3bb29 | ||
|
|
7e39755154 | ||
|
|
97af8919fc | ||
|
|
6cdfaeb8fb | ||
|
|
4ea00b0140 | ||
|
|
864c5027c6 | ||
|
|
0bb55faa2d | ||
|
|
f757863e1c | ||
|
|
3ffd2f5f5e | ||
|
|
6aa135e80a | ||
|
|
07f4bd0699 | ||
|
|
c200a55336 | ||
|
|
14fed2bb75 | ||
|
|
44ecd61489 | ||
|
|
882212520f | ||
|
|
9467939f53 | ||
|
|
e3933ebdf6 | ||
|
|
0d99a03c39 | ||
|
|
bf199e9394 |
8
.github/FUNDING.yml
vendored
Normal file
8
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: grav
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
custom: # Replace with a single custom sponsorship URL
|
||||
@@ -12,13 +12,6 @@ notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: always
|
||||
hipchat:
|
||||
# hipchat_api@grav
|
||||
rooms:
|
||||
- secure: "bqO0wM1B7bJnQw2fuhquSXEqI9gw6WmFytIh9sEWXzbYTzTUP5t0PcKOd3FT2BNMRaDxPJLVl+vG/oqmqDUBkEmOGcG504IQjeNzZqnMz0tXQMIcCc22Las9tFfc4Jf6RVi/qGomFtHGE9Wgii+TAN4zqZaufbNjwd8SyjO0+W8="
|
||||
template:
|
||||
- '%{repository}#%{build_number} (%{branch}): Travis Job Finished [%{duration}] (<a href="%{build_url}">Details</a>)'
|
||||
format: html
|
||||
slack:
|
||||
secure: dowksPsxxCxGKT6nis5hUgkp6+ZDAhoqzQHF9rJnx4hx0iEygPhVBs7pKl9yL2jubYJoLs+EXwE7z1dYgDAEJh4BnfrCokCMLpFGcxVxQC/HeAUdSQ2/RtdBYR5PRT75ScaFpqM/SfXXZVtnwVXAw9Z+JC6BjQ9vmn23m51Jw4k=
|
||||
env:
|
||||
|
||||
143
CHANGELOG.md
143
CHANGELOG.md
@@ -1,3 +1,136 @@
|
||||
# v1.6.13
|
||||
## 08/12/2019
|
||||
|
||||
1. [](#bugfix)
|
||||
* Regression fix for `system\router.php` [#2627](https://github.com/getgrav/grav/issues/2627)
|
||||
|
||||
# v1.6.12
|
||||
## 08/11/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added support for custom `FormFlash` save locations
|
||||
* Added a new `Utils::arrayLower()` method for lowercasing arrays
|
||||
* Support new GRAV_BASEDIR environment variable [#2541](https://github.com/getgrav/grav/pull/2541)
|
||||
* Allow users to override plugin handler priorities [#2165](https://github.com/getgrav/grav/pull/2165)
|
||||
1. [](#improved)
|
||||
* Use new `Utils::getSupportedPageTypes()` to enforce `html,htm` at the front of the list [#2531](https://github.com/getgrav/grav/issues/2531)
|
||||
* Updated vendor libraries
|
||||
* Markdown filter is now page-aware so that it works with modular references [admin#1731](https://github.com/getgrav/grav-plugin-admin/issues/1731)
|
||||
* Check of `GRAV_USER_INSTANCE` constant is already defined [#2621](https://github.com/getgrav/grav/pull/2621)
|
||||
1. [](#bugfix)
|
||||
* Fixed some potential issues when `$grav['user']` is not set
|
||||
* Fixed error when calling `Media::add($name, null)`
|
||||
* Fixed `url()` returning wrong path if using stream with grav root path in it, eg: `user-data://shop` when Grav is in `/shop`
|
||||
* Fixed `url()` not returning a path to non-existing file (`user-data://shop` => `/user/data/shop`) if it is set to fail gracefully
|
||||
* Fixed `url()` returning false on unknown streams, such as `ftp://domain.com`, they should be treated as external URL
|
||||
* Fixed Flex User to have permissions to save and delete his own user
|
||||
* Fixed new Flex User creation not being possible because of username could not be given
|
||||
* Fixed fatal error 'Expiration date must be an integer, a DateInterval or null, "double" given' [#2529](https://github.com/getgrav/grav/issues/2529)
|
||||
* Fixed non-existing Flex object having a bad media folder
|
||||
* Fixed collections using `page@.self:` should allow modular pages if requested
|
||||
* Fixed an error when trying to delete a file from non-existing Flex Object
|
||||
* Fixed `FlexObject::exists()` failing sometimes just after the object has been saved
|
||||
* Fixed CSV formatter not encoding strings with `"` and `,` properly
|
||||
* Fixed var order in `Validation.php` [#2610](https://github.com/getgrav/grav/issues/2610)
|
||||
|
||||
# v1.6.11
|
||||
## 06/21/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added `FormTrait::getAllFlashes()` method to get all the available form flash objects for the form
|
||||
* Added creation and update timestamps to `FormFlash` objects
|
||||
1. [](#improved)
|
||||
* Added `FormFlashInterface`, changed constructor to take `$config` array
|
||||
1. [](#bugfix)
|
||||
* Fixed error in `ImageMedium::url()` if the image cache folder does not exist
|
||||
* Fixed empty form flash name after file upload or form state update
|
||||
* Fixed a bug in `Route::withParam()` method
|
||||
* Fixed issue with `FormFlash` objects when there is no session initialized
|
||||
|
||||
# v1.6.10
|
||||
## 06/14/2019
|
||||
|
||||
1. [](#improved)
|
||||
* Added **page blueprints** to `YamlLinter` CLI and Admin reports
|
||||
* Removed `Gitter` and `Slack` [#2502](https://github.com/getgrav/grav/issues/2502)
|
||||
* Optimizations for Plugin/Theme loading
|
||||
* Generalized markdown classes so they can be used outside of `Page` scope with a custom `Excerpts` class instance
|
||||
* Change minimal port number to 0 (unix socket) [#2452](https://github.com/getgrav/grav/issues/2452)
|
||||
1. [](#bugfix)
|
||||
* Force question to install demo content in theme update [#2493](https://github.com/getgrav/grav/issues/2493)
|
||||
* Fixed GPM errors from blueprints not being logged [#2505](https://github.com/getgrav/grav/issues/2505)
|
||||
* Don't error when IP is invalid [#2507](https://github.com/getgrav/grav/issues/2507)
|
||||
* Fixed regression with `bin/plugin` not listing the plugins available (1c725c0)
|
||||
* Fixed bitwise operator in `TwigExtension::exifFunc()` [#2518](https://github.com/getgrav/grav/issues/2518)
|
||||
* Fixed issue with lang prefix incorrectly identifying as admin [#2511](https://github.com/getgrav/grav/issues/2511)
|
||||
* Fixed issue with `U0ils::pathPrefixedBYLanguageCode()` and trailing slash [#2510](https://github.com/getgrav/grav/issues/2511)
|
||||
* Fixed regresssion issue of `Utils::Url()` not returning `false` on failure. Added new optional `fail_gracefully` 3rd attribute to return string that caused failure [#2524](https://github.com/getgrav/grav/issues/2524)
|
||||
|
||||
# v1.6.9
|
||||
## 05/09/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added `Route::withoutParams()` methods
|
||||
* Added `Pages::setCheckMethod()` method to override page configuration in Admin Plugin
|
||||
* Added `Cache::clearCache('invalidate')` parameter for just invalidating the cache without deleting any cached files
|
||||
* Made `UserCollectionInderface` to extend `Countable` to get the count of existing users
|
||||
1. [](#improved)
|
||||
* Flex admin: added default search options for flex objects
|
||||
* Flex collection and object now fall back to the default template if template file doesn't exist
|
||||
* Updated Vendor libraries including Twig 1.40.1
|
||||
* Updated language files from `https://crowdin.com/project/grav-core`
|
||||
1. [](#bugfix)
|
||||
* Fixed `$grav['route']` from being modified when the route instance gets modified
|
||||
* Fixed Assets options array mixed with standalone priority [#2477](https://github.com/getgrav/grav/issues/2477)
|
||||
* Fix for `avatar_url` provided by 3rd party providers
|
||||
* Fixed non standard `lang` code lengths in `Utils` and `Session` detection
|
||||
* Fixed saving a new object in Flex `SimpleStorage`
|
||||
* Fixed exception in `Flex::getDirectories()` if the first parameter is set
|
||||
* Output correct "Last Updated" in `bin/gpm info` command
|
||||
* Checkbox getting interpreted as string, so created new `Validation::filterCheckbox()`
|
||||
* Fixed backwards compatibility to `select` field with `selectize.create` set to true [git-sync#141](https://github.com/trilbymedia/grav-plugin-git-sync/issues/141)
|
||||
* Fixed `YamlFormatter::decode()` to always return array [#2494](https://github.com/getgrav/grav/pull/2494)
|
||||
* Fixed empty `$grav['request']->getAttribute('route')->getExtension()`
|
||||
|
||||
# v1.6.8
|
||||
## 04/23/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added `FlexCollection::filterBy()` method
|
||||
1. [](#bugfix)
|
||||
* Revert `Use Null Coalesce Operator` [#2466](https://github.com/getgrav/grav/pull/2466)
|
||||
* Fixed `FormTrait::render()` not providing config variable
|
||||
* Updated `bin/grav clean` to clear `cache/compiled` and `user/config/security.yaml`
|
||||
|
||||
# v1.6.7
|
||||
## 04/22/2019
|
||||
|
||||
1. [](#new)
|
||||
* Added a new `bin/grav yamllinter` CLI command to find YAML Linting issues [#2468](https://github.com/getgrav/grav/issues/2468#issuecomment-485151681)
|
||||
1. [](#improved)
|
||||
* Improve `FormTrait` backwards compatibility with existing forms
|
||||
* Added a new `Utils::getSubnet()` function for IPv4/IPv6 parsing [#2465](https://github.com/getgrav/grav/pull/2465)
|
||||
1. [](#bugfix)
|
||||
* Remove disabled fields from the form schema
|
||||
* Fix issue when excluding `inlineJs` and `inlineCss` from Assets pipeline [#2468](https://github.com/getgrav/grav/issues/2468)
|
||||
* Fix for manually set position on external URLs [#2470](https://github.com/getgrav/grav/issues/2470)
|
||||
|
||||
# v1.6.6
|
||||
## 04/17/2019
|
||||
|
||||
1. [](#new)
|
||||
* `FormInterface` now implements `RenderInterface`
|
||||
* Added new `FormInterface::getTask()` method which reads the task from `form.task` in the blueprint
|
||||
1. [](#improved)
|
||||
* Updated vendor libraries to latest
|
||||
1. [](#bugfix)
|
||||
* Rollback `redirect_default_route` logic as it has issues with multi-lang [#2459](https://github.com/getgrav/grav/issues/2459)
|
||||
* Fix potential issue with `|contains` Twig filter on PHP 7.3
|
||||
* Fixed bug in text field filtering: return empty string if value isn't a string or number [#2460](https://github.com/getgrav/grav/issues/2460)
|
||||
* Force Asset `priority` to be an integer and not throw error if invalid string passed [#2461](https://github.com/getgrav/grav/issues/2461)
|
||||
* Fixed bug in text field filtering: return empty string if value isn't a string or number
|
||||
* Fixed `FlexForm` missing getter methods for defining form variables
|
||||
|
||||
# v1.6.5
|
||||
## 04/15/2019
|
||||
|
||||
@@ -58,7 +191,7 @@
|
||||
* Added `Grav\Framework\Object\ObjectIndex` class
|
||||
* Added `Grav\Framework\Flex` classes
|
||||
* Added support for hiding form fields in blueprints by using dynamic property like `security@: admin.foobar`, `scope@: object` or `scope-ignore@: object` to any field
|
||||
* New experimental **FlexObjects** powered `Users` for increased performance and capability (**disabled** by default)
|
||||
* New experimental **FlexObjects** powered `Users` for increased performance and capability (**disabled** by default)
|
||||
* Added PSR-7 and PSR-15 classes
|
||||
* Added `Grav\Framework\DI\Container` class
|
||||
* Added `Grav\Framework\RequestHandler\RequestHandler` class
|
||||
@@ -134,7 +267,7 @@
|
||||
* Added ability to reset `Page::metadata` to allow rebuilding from automatically generated values
|
||||
* Added back missing `page.types` field in system content configuration [admin#1612](https://github.com/getgrav/grav-plugin-admin/issues/1612)
|
||||
* Console commands: add method for invalidating cache
|
||||
* Updated languages
|
||||
* Updated languages
|
||||
* Improved `$page->forms()` call, added `$page->addForms()`
|
||||
* Updated languages from crowdin
|
||||
* Fixed `ImageMedium` constructor warning when file does not exist
|
||||
@@ -155,7 +288,7 @@
|
||||
* Added apcu autoloader optimization
|
||||
* Additional helper methods in `Language`, `Languages`, and `LanguageCodes` classes
|
||||
* Call `onFatalException` event also on internal PHP errors
|
||||
* Built-in PHP Webserver: log requests before handling them
|
||||
* Built-in PHP Webserver: log requests before handling them
|
||||
* Added support for syslog and syslog facility logging (default: 'file')
|
||||
* Improved usability of `System` configuration blueprint with side-tabs
|
||||
1. [](#bugfix)
|
||||
@@ -180,7 +313,7 @@
|
||||
* Fixed failed login if user attempts to log in with upper case non-english letters
|
||||
* Removed extra authenticated/authorized fields when saving existing user from a form
|
||||
* Fixed `Grav\Framework\Route::__toString()` returning relative URL, not relative route
|
||||
* Fixed handling of `append_url_extension` inside of `Page::templateFormat()` [#2264](https://github.com/getgrav/grav/issues/2264)
|
||||
* Fixed handling of `append_url_extension` inside of `Page::templateFormat()` [#2264](https://github.com/getgrav/grav/issues/2264)
|
||||
* Fixed a broken language string [#2261](https://github.com/getgrav/grav/issues/2261)
|
||||
* Fixed clearing cache having no effect on Doctrine cache
|
||||
* Fixed `Medium::relativePath()` for streams
|
||||
@@ -233,7 +366,7 @@
|
||||
* Updated vendor libraries
|
||||
1. [](#bugfix)
|
||||
* Support spaces with filenames in responsive images [#2300](https://github.com/getgrav/grav/pull/2300)
|
||||
|
||||
|
||||
# v1.5.6
|
||||
## 12/14/2018
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ The issue tracker is the preferred channel for [bug reports](#bugs),
|
||||
requests](#pull-requests), but please respect the following restrictions:
|
||||
|
||||
* Please **do not** use the issue tracker for support requests. Use
|
||||
[the Forum](http://getgrav.org/forum) or [the Gitter chat](https://gitter.im/getgrav/grav).
|
||||
[the Forum](http://getgrav.org/forum) or [the Chat](https://chat.getgrav.org/).
|
||||
|
||||
|
||||
<a name="bugs"></a>
|
||||
@@ -110,7 +110,8 @@ Good pull requests - patches, improvements, new features - are a fantastic
|
||||
help. They should remain focused in scope and avoid containing unrelated
|
||||
commits.
|
||||
|
||||
**Please ask first** in [Slack](https://getgrav.org/slack) or in the Forum before embarking on any significant pull request (e.g.
|
||||
**Please ask first** in [the Forum](http://getgrav.org/forum) or [the Chat](https://chat.getgrav.org/)
|
||||
before embarking on any significant pull request (e.g.
|
||||
implementing features, refactoring code..),
|
||||
otherwise you risk spending a lot of time working on something that the
|
||||
project's developers might not want to merge into the project.
|
||||
|
||||
19
bin/grav
19
bin/grav
@@ -3,6 +3,7 @@
|
||||
|
||||
use Grav\Common\Composer;
|
||||
use Grav\Common\Grav;
|
||||
use League\CLImate\CLImate;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
\define('GRAV_CLI', true);
|
||||
@@ -24,7 +25,22 @@ if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
}
|
||||
|
||||
Grav::instance(array('loader' => $autoload));
|
||||
$climate = new League\CLImate\CLImate;
|
||||
$climate->arguments->add([
|
||||
'environment' => [
|
||||
'prefix' => 'e',
|
||||
'longPrefix' => 'env',
|
||||
'description' => 'Configuration Environment',
|
||||
'defaultValue' => 'localhost'
|
||||
]
|
||||
]);
|
||||
$climate->arguments->parse();
|
||||
|
||||
// Set up environment based on params.
|
||||
$environment = $climate->arguments->get('environment');
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav->setup($environment);
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
@@ -46,5 +62,6 @@ $app->addCommands(array(
|
||||
new \Grav\Console\Cli\SchedulerCommand(),
|
||||
new \Grav\Console\Cli\SecurityCommand(),
|
||||
new \Grav\Console\Cli\LogViewerCommand(),
|
||||
new \Grav\Console\Cli\YamlLinterCommand(),
|
||||
));
|
||||
$app->run();
|
||||
|
||||
25
bin/plugin
25
bin/plugin
@@ -79,19 +79,6 @@ $output = new ConsoleOutput();
|
||||
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold')));
|
||||
$output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
|
||||
|
||||
if (is_null($plugin)) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name plugin not found</red>");
|
||||
die;
|
||||
}
|
||||
|
||||
if (!$plugin->enabled) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name not enabled</red>");
|
||||
die;
|
||||
}
|
||||
|
||||
|
||||
if (!$name) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<red>Usage:</red>');
|
||||
@@ -123,6 +110,18 @@ if (!$name) {
|
||||
}
|
||||
|
||||
exit;
|
||||
} else {
|
||||
if (is_null($plugin)) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name plugin not found</red>");
|
||||
die;
|
||||
}
|
||||
|
||||
if (!$plugin->enabled) {
|
||||
$output->writeln('');
|
||||
$output->writeln("<red>$name not enabled</red>");
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
if ($plugin === null) {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"kodus/psr7-server": "*",
|
||||
"nyholm/psr7": "^1.0",
|
||||
|
||||
"twig/twig": "~1.35",
|
||||
"twig/twig": "~1.40",
|
||||
"erusev/parsedown": "1.6.4",
|
||||
"erusev/parsedown-extra": "~0.7",
|
||||
"symfony/yaml": "~4.2",
|
||||
@@ -89,6 +89,8 @@
|
||||
"exclude": ["VERSION"]
|
||||
},
|
||||
"scripts": {
|
||||
"api-16": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.16.md",
|
||||
"api-15": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.md",
|
||||
"post-create-project-cmd": "bin/grav install",
|
||||
"phpstan": "vendor/bin/phpstan analyse -l 2 -c ./tests/phpstan/phpstan.neon system/src --memory-limit=256M",
|
||||
"phpstan-framework": "vendor/bin/phpstan analyse -l 5 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=256M",
|
||||
|
||||
624
composer.lock
generated
624
composer.lock
generated
File diff suppressed because it is too large
Load Diff
4
now.json
Normal file
4
now.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "*.php", "use": "@now/php" }]
|
||||
}
|
||||
@@ -65,7 +65,7 @@ form:
|
||||
|
||||
summary.size:
|
||||
type: text
|
||||
size: x-small
|
||||
size: small
|
||||
append: PLUGIN_ADMIN.CHARACTERS
|
||||
label: PLUGIN_ADMIN.SUMMARY_SIZE
|
||||
help: PLUGIN_ADMIN.SUMMARY_SIZE_HELP
|
||||
|
||||
@@ -12,5 +12,7 @@ form:
|
||||
type: text
|
||||
label: PLUGIN_ADMIN.USERNAME
|
||||
help: PLUGIN_ADMIN.USERNAME_HELP
|
||||
unset-disabled@: true
|
||||
unset-readonly@: true
|
||||
validate:
|
||||
required: true
|
||||
|
||||
@@ -27,3 +27,13 @@ config:
|
||||
title: Accounts
|
||||
icon: fa-users
|
||||
authorize: ['admin.users', 'admin.accounts', 'admin.super']
|
||||
|
||||
form:
|
||||
fields:
|
||||
username:
|
||||
flex-disabled@: exists
|
||||
disabled: false
|
||||
flex-readonly@: exists
|
||||
readonly: false
|
||||
validate:
|
||||
required: true
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '1.6.5');
|
||||
define('GRAV_VERSION', '1.6.13');
|
||||
define('GRAV_TESTING', false);
|
||||
define('DS', '/');
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ GRAV:
|
||||
BAD_DATE: Fecha errónea
|
||||
AGO: antes
|
||||
FROM_NOW: desde ahora
|
||||
JUST_NOW: justo ahora
|
||||
JUST_NOW: hace un momento
|
||||
SECOND: segundo
|
||||
MINUTE: minuto
|
||||
HOUR: hora
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "---\npealkiri: %1$s\n---\n\n# Viga: vigane Frontmatter'i\n\nasukoht: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- 'equipment'
|
||||
- 'informatsioon'
|
||||
- 'rice'
|
||||
- 'money'
|
||||
- 'species'
|
||||
- 'series'
|
||||
- 'kala'
|
||||
- 'lammas'
|
||||
INFLECTOR_IRREGULAR:
|
||||
'person': 'inimesed'
|
||||
'man': 'mees'
|
||||
'child': 'lapsed'
|
||||
INFLECTOR_ORDINALS:
|
||||
'default': '.'
|
||||
'first': '.'
|
||||
'second': '.'
|
||||
'third': '.'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: Kuupäev määramata
|
||||
BAD_DATE: Vigane kuupäev
|
||||
AGO: tagasi
|
||||
FROM_NOW: praegusest
|
||||
JUST_NOW: just nüüd
|
||||
SECOND: sekund
|
||||
MINUTE: minut
|
||||
HOUR: tundi
|
||||
@@ -60,3 +79,7 @@ GRAV:
|
||||
- 'reede'
|
||||
- 'laupäev'
|
||||
- 'pühapäev'
|
||||
CRON:
|
||||
EVERY: iga
|
||||
EVERY_MONTH: iga kuu
|
||||
TEXT_PERIOD: Iga <b />
|
||||
|
||||
@@ -14,6 +14,8 @@ GRAV:
|
||||
'/sis$/i': 'ses'
|
||||
'/([ti])um$/i': '\1a'
|
||||
'/(buffal|tomat)o$/i': '\1es'
|
||||
'/(bu)s$/i': 'Bus'
|
||||
'/(alias|status)/i': 'alias|status'
|
||||
'/(ax|test)is$/i': '\1s'
|
||||
'/s$/i': 's'
|
||||
'/$/': 's'
|
||||
|
||||
@@ -11,6 +11,8 @@ GRAV:
|
||||
- 'fish'
|
||||
- 'sheep'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: Engin dagsetning gefin
|
||||
BAD_DATE: Röng dagsetning
|
||||
AGO: síðan
|
||||
JUST_NOW: í þessu
|
||||
SECOND: sekúndu
|
||||
@@ -45,6 +47,7 @@ GRAV:
|
||||
DEC_PLURAL: árat
|
||||
FORM:
|
||||
VALIDATION_FAIL: <b>Sannvottun mistókst:</b>
|
||||
INVALID_INPUT: Ógilt inntak í
|
||||
MISSING_REQUIRED_FIELD: 'Vantar nauðsynlegan reit:'
|
||||
MONTHS_OF_THE_YEAR:
|
||||
- 'janúar'
|
||||
@@ -67,3 +70,11 @@ GRAV:
|
||||
- 'Föstudagur'
|
||||
- 'Laugardagur'
|
||||
- 'Sunnudagur'
|
||||
CRON:
|
||||
TEXT_TIME: ' á <b />:<b />'
|
||||
TEXT_DOW: ' á <b />'
|
||||
TEXT_MONTH: ' af <b />'
|
||||
TEXT_DOM: ' á <b />'
|
||||
ERROR1: Merkið %s er ekki stutt!
|
||||
ERROR3: Það ætti að setja jquery_element inn í stillingar jqCron
|
||||
ERROR4: Óþekkt segð
|
||||
|
||||
@@ -35,3 +35,12 @@ GRAV:
|
||||
- 'Outubro'
|
||||
- 'Novembro'
|
||||
- 'Dezembro'
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- 'equipment'
|
||||
- 'information'
|
||||
- 'arroz'
|
||||
- 'money'
|
||||
- 'species'
|
||||
- 'series'
|
||||
- 'fish'
|
||||
- 'sheep'
|
||||
|
||||
@@ -1,10 +1,75 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Chyba: Chybný frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
INFLECTOR_PLURALS:
|
||||
'/(quiz)$/i': '\1zes'
|
||||
'/^(ox)$/i': '\1en'
|
||||
'/([m|l])ouse$/i': '\1ice'
|
||||
'/(matr|vert|ind)ix|ex$/i': '\1ices'
|
||||
'/(x|ch|ss|sh)$/i': '\1es'
|
||||
'/([^aeiouy]|qu)ies$/i': '\1y'
|
||||
'/([^aeiouy]|qu)y$/i': '\1ies'
|
||||
'/(hive)$/i': '\1s'
|
||||
'/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
|
||||
'/sis$/i': 'ses'
|
||||
'/([ti])um$/i': '\1a'
|
||||
'/(buffal|tomat)o$/i': '\1oes'
|
||||
'/(bu)s$/i': '\1ses'
|
||||
'/(alias|status)/i': '\1es'
|
||||
'/(octop|vir)us$/i': '\1i'
|
||||
'/(ax|test)is$/i': '\1es'
|
||||
'/s$/i': 's'
|
||||
'/$/': 's'
|
||||
INFLECTOR_SINGULAR:
|
||||
'/(quiz)zes$/i': '\1'
|
||||
'/(matr)ices$/i': '\1ix'
|
||||
'/(vert|ind)ices$/i': '\1ex'
|
||||
'/^(ox)en/i': '\1'
|
||||
'/(alias|status)es$/i': '\1'
|
||||
'/([octop|vir])i$/i': '\1us'
|
||||
'/(cris|ax|test)es$/i': '\1is'
|
||||
'/(shoe)s$/i': '\1'
|
||||
'/(o)es$/i': '\1'
|
||||
'/(bus)es$/i': '\1'
|
||||
'/([m|l])ice$/i': '\1ouse'
|
||||
'/(x|ch|ss|sh)es$/i': '\1'
|
||||
'/(m)ovies$/i': '\1ovie'
|
||||
'/(s)eries$/i': '\1eries'
|
||||
'/([^aeiouy]|qu)ies$/i': '\1y'
|
||||
'/([lr])ves$/i': '\1f'
|
||||
'/(tive)s$/i': '\1'
|
||||
'/(hive)s$/i': '\1'
|
||||
'/([^f])ves$/i': '\1fe'
|
||||
'/(^analy)ses$/i': '\1sis'
|
||||
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
|
||||
'/([ti])a$/i': '\1um'
|
||||
'/(n)ews$/i': '\1ews'
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- 'vybavenie'
|
||||
- 'informácie'
|
||||
- 'ryža'
|
||||
- 'peniaze'
|
||||
- 'druhy'
|
||||
- 'séria'
|
||||
- 'ryba'
|
||||
- 'ovce'
|
||||
INFLECTOR_IRREGULAR:
|
||||
'person': 'ľudia'
|
||||
'man': 'muži'
|
||||
'child': 'deti'
|
||||
'sex': 'pohlavia'
|
||||
'move': 'pohyby'
|
||||
INFLECTOR_ORDINALS:
|
||||
'default': '.'
|
||||
'first': '.'
|
||||
'second': '.'
|
||||
'third': '.'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: Neposkytnutý žiaden dátum
|
||||
BAD_DATE: Nesprávny dátum
|
||||
AGO: pred
|
||||
FROM_NOW: odteraz
|
||||
JUST_NOW: práve teraz
|
||||
SECOND: sekunda
|
||||
MINUTE: minúta
|
||||
HOUR: hodina
|
||||
@@ -14,10 +79,12 @@ GRAV:
|
||||
YEAR: rok
|
||||
DECADE: desaťročie
|
||||
SEC: sek
|
||||
MIN: min
|
||||
HR: hod
|
||||
WK: t
|
||||
MO: m
|
||||
YR: r
|
||||
DEC: dec
|
||||
SECOND_PLURAL: sekúnd
|
||||
MINUTE_PLURAL: minút
|
||||
HOUR_PLURAL: hodín
|
||||
@@ -58,3 +125,20 @@ GRAV:
|
||||
- 'Piatok'
|
||||
- 'Sobota'
|
||||
- 'Nedeľa'
|
||||
CRON:
|
||||
EVERY: každý
|
||||
EVERY_HOUR: každú hodinu
|
||||
EVERY_MINUTE: každú minútu
|
||||
EVERY_DAY_OF_WEEK: každý deň v týždni
|
||||
EVERY_DAY_OF_MONTH: každý deň v mesiaci
|
||||
EVERY_MONTH: každý mesiac
|
||||
TEXT_PERIOD: Každý <b />
|
||||
TEXT_MINS: ' at <b /> minute(s) past the hour'
|
||||
TEXT_TIME: ' at <b />:<b />'
|
||||
TEXT_DOW: ' on <b />'
|
||||
TEXT_MONTH: ' of <b />'
|
||||
TEXT_DOM: ' on <b />'
|
||||
ERROR1: Tag %s nieje podporovaný!
|
||||
ERROR2: Chybný počet položiek
|
||||
ERROR3: jquery_element musí byť nastavený v nastaveniach pre jqCron
|
||||
ERROR4: Neznámy výraz
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "--- titel: %1$s --- # Fel: Ogiltig Frontmatter-sökväg: `%2$s` **%3$s** ``` %4$s ```"
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- 'utrustning'
|
||||
- 'information'
|
||||
- 'ris'
|
||||
- 'pengar'
|
||||
- 'arter'
|
||||
- 'serier'
|
||||
- 'fisk'
|
||||
- 'får'
|
||||
INFLECTOR_IRREGULAR:
|
||||
'person': 'personer'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: Inget datum har angivits
|
||||
BAD_DATE: Ogiltigt datum
|
||||
|
||||
@@ -1,24 +1,44 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# 錯誤: 不正確的 Frontmatter\n\n路徑: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: 沒有提供日期
|
||||
BAD_DATE: 錯誤日期
|
||||
AGO: 之前
|
||||
FROM_NOW: 之後
|
||||
JUST_NOW: 剛剛
|
||||
SECOND: 秒
|
||||
MINUTE: 分
|
||||
HOUR: 小時
|
||||
DAY: 天
|
||||
WEEK: 週
|
||||
MONTH: 月
|
||||
YEAR: 年
|
||||
DECADE: 十年
|
||||
SEC: 秒
|
||||
MIN: 分
|
||||
HR: 小時
|
||||
WK: 週
|
||||
MO: 月
|
||||
YR: 年
|
||||
DEC: 十年
|
||||
SECOND_PLURAL: 秒
|
||||
MINUTE_PLURAL: 分
|
||||
HOUR_PLURAL: 時
|
||||
DAY_PLURAL: 日
|
||||
WEEK_PLURAL: 周
|
||||
HOUR_PLURAL: 小時
|
||||
DAY_PLURAL: 天
|
||||
WEEK_PLURAL: 週
|
||||
MONTH_PLURAL: 月
|
||||
YEAR_PLURAL: 年
|
||||
DECADE_PLURAL: 十年
|
||||
SEC_PLURAL: 秒
|
||||
MIN_PLURAL: 分
|
||||
HR_PLURAL: 時
|
||||
WK_PLURAL: 周
|
||||
WK_PLURAL: 週
|
||||
MO_PLURAL: 月
|
||||
YR_PLURAL: 年
|
||||
DEC_PLURAL: 十年
|
||||
FORM:
|
||||
MISSING_REQUIRED_FIELD: 遺漏必填欄位:
|
||||
MONTHS_OF_THE_YEAR:
|
||||
- '一月'
|
||||
- '二月'
|
||||
|
||||
@@ -1,57 +1,122 @@
|
||||
---
|
||||
GRAV:
|
||||
FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# 錯誤: 不正確的 Frontmatter\n\n路徑: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
FRONTMATTER_ERROR_PAGE: "---\n标题: %1$s\n---\n\n# 错误:无效参数\n\n位置: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
|
||||
INFLECTOR_PLURALS:
|
||||
'/(quiz)$/i': '\1zes'
|
||||
'/^(ox)$/i': '\1en'
|
||||
'/([m|l])ouse$/i': '\1ice'
|
||||
'/(matr|vert|ind)ix|ex$/i': '\1ices'
|
||||
'/(x|ch|ss|sh)$/i': '\1es'
|
||||
'/([^aeiouy]|qu)ies$/i': '\1y'
|
||||
'/([^aeiouy]|qu)y$/i': '\1ies'
|
||||
'/(hive)$/i': '\1s'
|
||||
'/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
|
||||
'/sis$/i': 'ses'
|
||||
'/([ti])um$/i': '\1a'
|
||||
'/(buffal|tomat)o$/i': '\1oes'
|
||||
'/(bu)s$/i': '\1ses'
|
||||
'/(alias|status)/i': '\1es'
|
||||
'/(octop|vir)us$/i': '\1i'
|
||||
'/(ax|test)is$/i': '\1es'
|
||||
'/s$/i': 's'
|
||||
'/$/': 's'
|
||||
INFLECTOR_SINGULAR:
|
||||
'/(quiz)zes$/i': '\1'
|
||||
'/(matr)ices$/i': '\1ix'
|
||||
'/(vert|ind)ices$/i': '\1ex'
|
||||
'/^(ox)en/i': '\1'
|
||||
'/(alias|status)es$/i': '\1'
|
||||
'/([octop|vir])i$/i': '\1us'
|
||||
'/(cris|ax|test)es$/i': '\1is'
|
||||
'/(shoe)s$/i': '\1'
|
||||
'/(o)es$/i': '\1'
|
||||
'/(bus)es$/i': '\1'
|
||||
'/([m|l])ice$/i': '\1ouse'
|
||||
'/(x|ch|ss|sh)es$/i': '\1'
|
||||
'/(m)ovies$/i': '\1ovie'
|
||||
'/(s)eries$/i': '\1eries'
|
||||
'/([^aeiouy]|qu)ies$/i': '\1y'
|
||||
'/([lr])ves$/i': '\1f'
|
||||
'/(tive)s$/i': '\1'
|
||||
'/(hive)s$/i': '\1'
|
||||
'/([^f])ves$/i': '\1fe'
|
||||
'/(^analy)ses$/i': '\1sis'
|
||||
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
|
||||
'/([ti])a$/i': '\1um'
|
||||
'/(n)ews$/i': '\1ews'
|
||||
INFLECTOR_UNCOUNTABLE:
|
||||
- '装备'
|
||||
- '信息'
|
||||
- '大米'
|
||||
- '钱'
|
||||
- '物种'
|
||||
- '系列'
|
||||
- '鱼'
|
||||
- '羊'
|
||||
INFLECTOR_IRREGULAR:
|
||||
'person': '人员'
|
||||
'man': '男人'
|
||||
'child': '儿童'
|
||||
'sex': '性别'
|
||||
'move': '移动'
|
||||
INFLECTOR_ORDINALS:
|
||||
'default': 'th'
|
||||
'first': 'st'
|
||||
'second': 'md'
|
||||
'third': 'rd'
|
||||
NICETIME:
|
||||
NO_DATE_PROVIDED: 沒有提供日期
|
||||
BAD_DATE: 錯誤日期
|
||||
AGO: 之前
|
||||
FROM_NOW: 之後
|
||||
JUST_NOW: 剛剛
|
||||
NO_DATE_PROVIDED: 无日期信息
|
||||
BAD_DATE: 无效日期
|
||||
AGO: 前
|
||||
FROM_NOW: 距今
|
||||
JUST_NOW: 刚刚
|
||||
SECOND: 秒
|
||||
MINUTE: 分
|
||||
HOUR: 小時
|
||||
MINUTE: 分钟
|
||||
HOUR: 小时
|
||||
DAY: 天
|
||||
WEEK: 週
|
||||
WEEK: 周
|
||||
MONTH: 月
|
||||
YEAR: 年
|
||||
DECADE: 十年
|
||||
SEC: 秒
|
||||
MIN: 分
|
||||
HR: 小時
|
||||
WK: 週
|
||||
MIN: 分钟
|
||||
HR: 小时
|
||||
WK: 周
|
||||
MO: 月
|
||||
YR: 年
|
||||
DEC: 十年
|
||||
DEC: 年代
|
||||
SECOND_PLURAL: 秒
|
||||
MINUTE_PLURAL: 分
|
||||
HOUR_PLURAL: 小時
|
||||
HOUR_PLURAL: 小时
|
||||
DAY_PLURAL: 天
|
||||
WEEK_PLURAL: 週
|
||||
WEEK_PLURAL: 周
|
||||
MONTH_PLURAL: 月
|
||||
YEAR_PLURAL: 年
|
||||
DECADE_PLURAL: 十年
|
||||
SEC_PLURAL: 秒
|
||||
MIN_PLURAL: 分
|
||||
HR_PLURAL: 時
|
||||
WK_PLURAL: 週
|
||||
HR_PLURAL: 时
|
||||
WK_PLURAL: 周
|
||||
MO_PLURAL: 月
|
||||
YR_PLURAL: 年
|
||||
DEC_PLURAL: 十年
|
||||
DEC_PLURAL: 年代
|
||||
FORM:
|
||||
MISSING_REQUIRED_FIELD: 遺漏必填欄位:
|
||||
VALIDATION_FAIL: <b>验证失败:</b>
|
||||
INVALID_INPUT: 无效输入
|
||||
MISSING_REQUIRED_FIELD: 必填字段缺失:
|
||||
MONTHS_OF_THE_YEAR:
|
||||
- '一月'
|
||||
- '二月'
|
||||
- '三月'
|
||||
- '四月'
|
||||
- '五月'
|
||||
- '六月'
|
||||
- '七月'
|
||||
- '八月'
|
||||
- '九月'
|
||||
- '十月'
|
||||
- '十一月'
|
||||
- '十二月'
|
||||
- '1月'
|
||||
- '2月'
|
||||
- '3月'
|
||||
- '4月'
|
||||
- '5月'
|
||||
- '6月'
|
||||
- '7月'
|
||||
- '8月'
|
||||
- '9月'
|
||||
- '10月'
|
||||
- '11月'
|
||||
- '12月'
|
||||
DAYS_OF_THE_WEEK:
|
||||
- '星期一'
|
||||
- '星期二'
|
||||
@@ -60,4 +125,20 @@ GRAV:
|
||||
- '星期五'
|
||||
- '星期六'
|
||||
- '星期日'
|
||||
|
||||
CRON:
|
||||
EVERY: 每隔
|
||||
EVERY_HOUR: 每小时
|
||||
EVERY_MINUTE: 每分钟
|
||||
EVERY_DAY_OF_WEEK: 一周中的每一天
|
||||
EVERY_DAY_OF_MONTH: 月份中的每一天
|
||||
EVERY_MONTH: 每月
|
||||
TEXT_PERIOD: 所有 <b />
|
||||
TEXT_MINS: ' 在 <b /> 小时过后的分钟'
|
||||
TEXT_TIME: ' 在 <b />:<b />'
|
||||
TEXT_DOW: ' on <b />'
|
||||
TEXT_MONTH: ' of <b />'
|
||||
TEXT_DOM: ' on <b />'
|
||||
ERROR1: 不支持分享类型 %s
|
||||
ERROR2: 无效数字
|
||||
ERROR3: 请在 jqCron 设置中设定 jquery_element
|
||||
ERROR4: 无法识别表达式
|
||||
|
||||
@@ -17,11 +17,22 @@ if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_N
|
||||
return false;
|
||||
}
|
||||
|
||||
$grav_index = 'index.php';
|
||||
|
||||
/* Check the GRAV_BASEDIR environment variable and use if set */
|
||||
$grav_basedir = getenv('GRAV_BASEDIR') ?: '';
|
||||
|
||||
if (isset($grav_basedir)) {
|
||||
$grav_index = ltrim($grav_basedir, '/') . DIRECTORY_SEPARATOR . $grav_index;
|
||||
$grav_basedir = DIRECTORY_SEPARATOR . trim($grav_basedir, DIRECTORY_SEPARATOR);
|
||||
define('GRAV_ROOT', str_replace(DIRECTORY_SEPARATOR, '/', getcwd()) . $grav_basedir);
|
||||
}
|
||||
|
||||
$_SERVER = array_merge($_SERVER, $_ENV);
|
||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'] . $grav_basedir .DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['SCRIPT_NAME'] = $grav_basedir . DIRECTORY_SEPARATOR . 'index.php';
|
||||
$_SERVER['PHP_SELF'] = $grav_basedir . DIRECTORY_SEPARATOR . 'index.php';
|
||||
|
||||
error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4);
|
||||
|
||||
require 'index.php';
|
||||
require $grav_index;
|
||||
|
||||
@@ -172,7 +172,8 @@ class Assets extends PropertyObject
|
||||
// If pipeline disabled, set to position if provided, else after
|
||||
if (isset($options['pipeline'])) {
|
||||
if ($options['pipeline'] === false) {
|
||||
$excludes = strtolower($type . '_pipeline_before_excludes');
|
||||
$exclude_type = ($type === $this::JS_TYPE || $type === $this::INLINE_JS_TYPE) ? $this::JS_TYPE : $this::CSS_TYPE;
|
||||
$excludes = strtolower($exclude_type . '_pipeline_before_excludes');
|
||||
if ($this->{$excludes}) {
|
||||
$default = 'after';
|
||||
} else {
|
||||
@@ -271,7 +272,7 @@ class Assets extends PropertyObject
|
||||
|
||||
$type = $asset->getType();
|
||||
|
||||
if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false) {
|
||||
if ($asset->getRemote() && $this->{$type . '_pipeline_include_externals'} === false && $asset['position'] === 'pipeline' ) {
|
||||
if ($this->{$type . '_pipeline_before_excludes'}) {
|
||||
$asset->setPosition('after');
|
||||
} else {
|
||||
@@ -281,6 +282,7 @@ class Assets extends PropertyObject
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($asset[$key] === $value) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -78,6 +78,9 @@ abstract class BaseAsset extends PropertyObject
|
||||
}
|
||||
}
|
||||
|
||||
// Force priority to be an int
|
||||
$this->priority = (int) $this->priority;
|
||||
|
||||
// Do some special stuff for CSS/JS (not inline)
|
||||
if (!Utils::startsWith($this->getType(), 'inline')) {
|
||||
$this->base_url = rtrim($uri->rootUrl($config->get('system.absolute_urls')), '/') . '/';
|
||||
|
||||
@@ -24,19 +24,21 @@ trait LegacyAssetsTrait
|
||||
// First argument is always the asset
|
||||
array_shift($args);
|
||||
|
||||
if (\count($args) === 0) {
|
||||
if (count($args) === 0) {
|
||||
return [];
|
||||
}
|
||||
if (\count($args) === 1 && \is_array($args[0])) {
|
||||
// New options array format
|
||||
if (count($args) === 1 && is_array($args[0])) {
|
||||
return $args[0];
|
||||
}
|
||||
// Handle obscure case where options array is mixed with a priority
|
||||
if (count($args) === 2 && is_array($args[0]) && is_int($args[1])) {
|
||||
$arguments = $args[0];
|
||||
$arguments['priority'] = $args[1];
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case(Assets::INLINE_CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
break;
|
||||
|
||||
case(Assets::JS_TYPE):
|
||||
$defaults = ['priority' => null, 'pipeline' => true, 'loading' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
@@ -55,6 +57,11 @@ trait LegacyAssetsTrait
|
||||
|
||||
break;
|
||||
|
||||
case(Assets::INLINE_CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'group' => null];
|
||||
$arguments = $this->createArgumentsFromLegacy($args, $defaults);
|
||||
break;
|
||||
|
||||
default:
|
||||
case(Assets::CSS_TYPE):
|
||||
$defaults = ['priority' => null, 'pipeline' => true, 'group' => null, 'loading' => null];
|
||||
|
||||
@@ -437,6 +437,9 @@ class Cache extends Getters
|
||||
case 'tmp-only':
|
||||
$remove_paths = self::$tmp_remove;
|
||||
break;
|
||||
case 'invalidate':
|
||||
$remove_paths = [];
|
||||
break;
|
||||
default:
|
||||
if (Grav::instance()['config']->get('system.cache.clear_images_by_default')) {
|
||||
$remove_paths = self::$standard_remove;
|
||||
@@ -528,7 +531,6 @@ class Cache extends Getters
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the cache lifetime programmatically
|
||||
*
|
||||
@@ -540,7 +542,7 @@ class Cache extends Getters
|
||||
return;
|
||||
}
|
||||
|
||||
$interval = $future - $this->now;
|
||||
$interval = (int)($future - $this->now);
|
||||
if ($interval > 0 && $interval < $this->getLifetime()) {
|
||||
$this->lifetime = $interval;
|
||||
}
|
||||
@@ -555,7 +557,7 @@ class Cache extends Getters
|
||||
public function getLifetime()
|
||||
{
|
||||
if ($this->lifetime === null) {
|
||||
$this->lifetime = $this->config->get('system.cache.lifetime') ?: 604800; // 1 week default
|
||||
$this->lifetime = (int)($this->config->get('system.cache.lifetime') ?: 604800); // 1 week default
|
||||
}
|
||||
|
||||
return $this->lifetime;
|
||||
|
||||
@@ -378,14 +378,12 @@ class Blueprint extends BlueprintForm
|
||||
$grav = Grav::instance();
|
||||
$actions = (array)$call['params'];
|
||||
|
||||
/** @var UserInterface $user */
|
||||
if (isset($grav['user'])) {
|
||||
$user = Grav::instance()['user'];
|
||||
foreach ($actions as $action) {
|
||||
if (!$user->authorize($action)) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
return;
|
||||
}
|
||||
/** @var UserInterface|null $user */
|
||||
$user = $grav['user'] ?? null;
|
||||
foreach ($actions as $action) {
|
||||
if (!$user || !$user->authorize($action)) {
|
||||
$this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
|
||||
if ($rule) {
|
||||
// Item has been defined in blueprints.
|
||||
if (!empty($rule['validate']['ignore'])) {
|
||||
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
|
||||
// Skip validation in the ignored field.
|
||||
continue;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
$val = $rules[$key] ?? $rules['*'] ?? null;
|
||||
$rule = \is_string($val) ? $this->items[$val] : null;
|
||||
|
||||
if (empty($rule['validate']['ignore'])) {
|
||||
if (empty($rule['disabled']) && empty($rule['validate']['ignore'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
|
||||
if ($rule) {
|
||||
// Item has been defined in blueprints.
|
||||
if (!empty($rule['validate']['ignore'])) {
|
||||
if (!empty($rule['disabled']) || !empty($rule['validate']['ignore'])) {
|
||||
// Skip any data in the ignored field.
|
||||
unset($results[$key]);
|
||||
continue;
|
||||
@@ -240,6 +240,8 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
if (
|
||||
// Not an input field
|
||||
!$field
|
||||
// Field has been disabled
|
||||
|| !empty($field['disabled'])
|
||||
// Field validation is set to be ignored
|
||||
|| !empty($field['validate']['ignore'])
|
||||
// Field is toggleable and the toggle is turned off
|
||||
@@ -273,7 +275,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
|
||||
$field = $this->items[$field];
|
||||
|
||||
// Skip ignored field, it will not be required.
|
||||
if (!empty($field['validate']['ignore'])) {
|
||||
if (!empty($field['disabled']) || !empty($field['validate']['ignore'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ class Blueprints
|
||||
public function get($type)
|
||||
{
|
||||
if (!isset($this->instances[$type])) {
|
||||
$this->instances[$type] = $this->loadFile($type);
|
||||
$blueprint = $this->loadFile($type);
|
||||
$this->instances[$type] = $blueprint;
|
||||
}
|
||||
|
||||
return $this->instances[$type];
|
||||
@@ -99,6 +100,15 @@ class Blueprints
|
||||
$blueprint->setContext($this->search);
|
||||
}
|
||||
|
||||
return $blueprint->load()->init();
|
||||
try {
|
||||
$blueprint->load()->init();
|
||||
} catch (\RuntimeException $e) {
|
||||
$log = Grav::instance()['log'];
|
||||
$log->error(sprintf('Blueprint %s cannot be loaded: %s', $name, $e->getMessage()));
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $blueprint;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ class Validation
|
||||
if (!isset($field['type'])) {
|
||||
$field['type'] = 'text';
|
||||
}
|
||||
$type = $validate['type'] ?? $field['type'];
|
||||
|
||||
$validate = (array)($field['validate'] ?? null);
|
||||
$type = $validate['type'] ?? $field['type'];
|
||||
$required = $validate['required'] ?? false;
|
||||
|
||||
// If value isn't required, we will stop validation if empty value is given.
|
||||
@@ -154,6 +155,10 @@ class Validation
|
||||
|
||||
protected static function filterText($value, array $params, array $field)
|
||||
{
|
||||
if (!\is_string($value) && !is_numeric($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!empty($params['trim'])) {
|
||||
$value = trim($value);
|
||||
}
|
||||
@@ -161,6 +166,11 @@ class Validation
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
protected static function filterCheckbox($value, array $params, array $field)
|
||||
{
|
||||
return (bool) $value;
|
||||
}
|
||||
|
||||
protected static function filterCommaList($value, array $params, array $field)
|
||||
{
|
||||
return \is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
@@ -567,6 +577,11 @@ class Validation
|
||||
}
|
||||
}
|
||||
|
||||
// If creating new values is allowed, no further checks are needed.
|
||||
if (!empty($field['selectize']['create'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$options = $field['options'] ?? [];
|
||||
$use = $field['use'] ?? 'values';
|
||||
|
||||
|
||||
@@ -10,54 +10,10 @@
|
||||
namespace Grav\Common\Form;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Grav\Framework\Form\FormFlash as FrameworkFormFlash;
|
||||
|
||||
class FormFlash extends \Grav\Framework\Form\FormFlash
|
||||
class FormFlash extends FrameworkFormFlash
|
||||
{
|
||||
/**
|
||||
* @param string $sessionId
|
||||
*/
|
||||
public static function clearSession(string $sessionId): void
|
||||
{
|
||||
$folder = static::getSessionTmpDir($sessionId);
|
||||
if (is_dir($folder)) {
|
||||
Folder::delete($folder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sessionId
|
||||
* @return string
|
||||
*/
|
||||
public static function getSessionTmpDir(string $sessionId): string
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
return $locator->findResource("tmp://forms/{$sessionId}", true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserInterface|null $user
|
||||
* @return $this
|
||||
*/
|
||||
public function setUser(UserInterface $user = null)
|
||||
{
|
||||
if ($user && $user->username) {
|
||||
$this->user = [
|
||||
'username' => $user->username,
|
||||
'email' => $user->email ?? ''
|
||||
];
|
||||
} else {
|
||||
$this->user = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @deprecated 1.6 For backwards compatibility only, do not use
|
||||
@@ -89,8 +45,11 @@ class FormFlash extends \Grav\Framework\Form\FormFlash
|
||||
*/
|
||||
public function uploadFile(string $field, string $filename, array $upload): bool
|
||||
{
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
if (!$this->uniqueId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
Folder::create($tmp_dir);
|
||||
|
||||
$tmp_file = $upload['file']['tmp_name'];
|
||||
@@ -118,8 +77,11 @@ class FormFlash extends \Grav\Framework\Form\FormFlash
|
||||
*/
|
||||
public function cropFile(string $field, string $filename, array $upload, array $crop): bool
|
||||
{
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
if (!$this->uniqueId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
Folder::create($tmp_dir);
|
||||
|
||||
$tmp_file = $upload['file']['tmp_name'];
|
||||
@@ -136,32 +98,4 @@ class FormFlash extends \Grav\Framework\Form\FormFlash
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return YamlFile
|
||||
*/
|
||||
protected function getTmpIndex(): YamlFile
|
||||
{
|
||||
// Do not use CompiledYamlFile as the file can change multiple times per second.
|
||||
return YamlFile::instance($this->getTmpDir() . '/index.yaml');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
protected function removeTmpFile(string $name): void
|
||||
{
|
||||
$filename = $this->getTmpDir() . '/' . $name;
|
||||
if ($name && is_file($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeTmpDir(): void
|
||||
{
|
||||
$tmpDir = $this->getTmpDir();
|
||||
if (file_exists($tmpDir)) {
|
||||
Folder::delete($tmpDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,19 +11,22 @@ namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class CachedCollection extends Iterator {
|
||||
|
||||
class CachedCollection extends Iterator
|
||||
{
|
||||
protected static $cache;
|
||||
|
||||
public function __construct($items)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$method = static::class . __METHOD__;
|
||||
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[get_called_class() . __METHOD__])) {
|
||||
self::$cache[get_called_class() . __METHOD__] = $items;
|
||||
if (!isset(self::$cache[$method])) {
|
||||
self::$cache[$method] = $items;
|
||||
}
|
||||
|
||||
foreach (self::$cache[get_called_class() . __METHOD__] as $name => $item) {
|
||||
foreach (self::$cache[$method] as $name => $item) {
|
||||
$this->append([$name => $item]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
class Package {
|
||||
|
||||
class Package
|
||||
{
|
||||
/**
|
||||
* @var Data
|
||||
*/
|
||||
|
||||
@@ -770,7 +770,7 @@ class GPM extends Iterator
|
||||
* @param array $ignore_packages_list
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function checkNoOtherPackageNeedsThisDependencyInALowerVersion(
|
||||
$slug,
|
||||
@@ -793,8 +793,8 @@ class GPM extends Iterator
|
||||
$compatible = $this->checkNextSignificantReleasesAreCompatible($version,
|
||||
$other_dependency_version);
|
||||
if (!$compatible) {
|
||||
if (!in_array($dependent_package, $ignore_packages_list)) {
|
||||
throw new \Exception("Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
|
||||
if (!in_array($dependent_package, $ignore_packages_list, true)) {
|
||||
throw new \RuntimeException("Package <cyan>$slug</cyan> is required in an older version by package <cyan>$dependent_package</cyan>. This package needs a newer version, and because of this it cannot be installed. The <cyan>$dependent_package</cyan> package must be updated to use a newer release of <cyan>$slug</cyan>.",
|
||||
2);
|
||||
}
|
||||
}
|
||||
@@ -850,10 +850,10 @@ class GPM extends Iterator
|
||||
) {
|
||||
//Needs a Grav update first
|
||||
throw new \RuntimeException("<red>One of the packages require PHP {$dependencies['php']}. Please update PHP to resolve this");
|
||||
} else {
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
|
||||
//First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell.
|
||||
@@ -863,10 +863,10 @@ class GPM extends Iterator
|
||||
) {
|
||||
//Needs a Grav update first
|
||||
throw new \RuntimeException("<red>One of the packages require Grav {$dependencies['grav']}. Please update Grav to the latest release.");
|
||||
} else {
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($dependencies[$dependency_slug]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isPluginInstalled($dependency_slug)) {
|
||||
@@ -1092,6 +1092,7 @@ class GPM extends Iterator
|
||||
if ($this->versionFormatIsEqualOrHigher($version)) {
|
||||
return trim(substr($version, 2));
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
@@ -1104,7 +1105,7 @@ class GPM extends Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function versionFormatIsNextSignificantRelease($version)
|
||||
public function versionFormatIsNextSignificantRelease($version): bool
|
||||
{
|
||||
return strpos($version, '~') === 0;
|
||||
}
|
||||
@@ -1118,7 +1119,7 @@ class GPM extends Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function versionFormatIsEqualOrHigher($version)
|
||||
public function versionFormatIsEqualOrHigher($version): bool
|
||||
{
|
||||
return strpos($version, '>=') === 0;
|
||||
}
|
||||
@@ -1136,7 +1137,7 @@ class GPM extends Iterator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkNextSignificantReleasesAreCompatible($version1, $version2)
|
||||
public function checkNextSignificantReleasesAreCompatible($version1, $version2): bool
|
||||
{
|
||||
$version1array = explode('.', $version1);
|
||||
$version2array = explode('.', $version2);
|
||||
|
||||
@@ -103,7 +103,7 @@ class Licenses
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the License File object
|
||||
* Get the License File object
|
||||
*
|
||||
* @return \RocketTheme\Toolbox\File\FileInterface
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@ abstract class AbstractPackageCollection extends BaseCollection
|
||||
public function __construct($items)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
foreach ($items as $name => $data) {
|
||||
$data->set('slug', $name);
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
|
||||
@@ -25,6 +25,7 @@ class Plugins extends AbstractPackageCollection
|
||||
{
|
||||
/** @var \Grav\Common\Plugins $plugins */
|
||||
$plugins = Grav::instance()['plugins'];
|
||||
|
||||
parent::__construct($plugins->all());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class AbstractPackageCollection extends BaseCollection
|
||||
{
|
||||
parent::__construct();
|
||||
if ($repository === null) {
|
||||
throw new \RuntimeException("A repository is required to indicate the origin of the remote collection");
|
||||
throw new \RuntimeException('A repository is required to indicate the origin of the remote collection');
|
||||
}
|
||||
|
||||
$channel = Grav::instance()['config']->get('system.gpm.releases', 'stable');
|
||||
|
||||
@@ -37,9 +37,9 @@ class GravCore extends AbstractPackageCollection
|
||||
$this->fetch($refresh, $callback);
|
||||
|
||||
$this->data = json_decode($this->raw, true);
|
||||
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
|
||||
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
|
||||
$this->min_php = isset($this->data['min_php']) ? $this->data['min_php'] : null;
|
||||
$this->version = $this->data['version'] ?? '-';
|
||||
$this->date = $this->data['date'] ?? '-';
|
||||
$this->min_php = $this->data['min_php'] ?? null;
|
||||
|
||||
if (isset($this->data['assets'])) {
|
||||
foreach ((array)$this->data['assets'] as $slug => $data) {
|
||||
|
||||
@@ -9,24 +9,20 @@
|
||||
|
||||
namespace Grav\Common\Helpers;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Page\Markdown\Excerpts as ExcerptsObject;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Excerpts
|
||||
{
|
||||
/**
|
||||
* Process Grav image media URL from HTML tag
|
||||
*
|
||||
* @param string $html HTML tag e.g. `<img src="image.jpg" />`
|
||||
* @param PageInterface $page The current page object
|
||||
* @return string Returns final HTML string
|
||||
* @param string $html HTML tag e.g. `<img src="image.jpg" />`
|
||||
* @param PageInterface|null $page Page, defaults to the current page object
|
||||
* @return string Returns final HTML string
|
||||
*/
|
||||
public static function processImageHtml($html, PageInterface $page)
|
||||
public static function processImageHtml($html, PageInterface $page = null)
|
||||
{
|
||||
$excerpt = static::getExcerptFromHtml($html, 'img');
|
||||
|
||||
@@ -112,157 +108,29 @@ class Excerpts
|
||||
* Process a Link excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @param PageInterface $page
|
||||
* @param PageInterface|null $page Page, defaults to the current page object
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
public static function processLinkExcerpt($excerpt, PageInterface $page, $type = 'link')
|
||||
public static function processLinkExcerpt($excerpt, PageInterface $page = null, $type = 'link')
|
||||
{
|
||||
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
|
||||
$excerpts = new ExcerptsObject($page);
|
||||
|
||||
$url_parts = static::parseUrl($url);
|
||||
|
||||
// If there is a query, then parse it and build action calls.
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
|
||||
$carry[$parts[0]] = $value;
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
// Valid attributes supported.
|
||||
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
|
||||
|
||||
// Unless told to not process, go through actions.
|
||||
if (array_key_exists('noprocess', $actions)) {
|
||||
unset($actions['noprocess']);
|
||||
} else {
|
||||
// Loop through actions for the image and call them.
|
||||
foreach ($actions as $attrib => $value) {
|
||||
$key = $attrib;
|
||||
|
||||
if (in_array($attrib, $valid_attributes, true)) {
|
||||
// support both class and classes.
|
||||
if ($attrib === 'classes') {
|
||||
$attrib = 'class';
|
||||
}
|
||||
$excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
|
||||
unset($actions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
// If no query elements left, unset query.
|
||||
if (empty($url_parts['query'])) {
|
||||
unset ($url_parts['query']);
|
||||
}
|
||||
|
||||
// Set path to / if not set.
|
||||
if (empty($url_parts['path'])) {
|
||||
$url_parts['path'] = '';
|
||||
}
|
||||
|
||||
// If scheme isn't http(s)..
|
||||
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
|
||||
// Handle custom streams.
|
||||
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
|
||||
$url_parts['path'] = Grav::instance()['base_url_relative'] . '/' . static::resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
|
||||
unset($url_parts['stream'], $url_parts['scheme']);
|
||||
}
|
||||
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
// Handle paths and such.
|
||||
$url_parts = Uri::convertUrl($page, $url_parts, $type);
|
||||
|
||||
// Build the URL from the component parts and set it on the element.
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
|
||||
return $excerpt;
|
||||
return $excerpts->processLinkExcerpt($excerpt, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an image excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @param PageInterface $page
|
||||
* @param PageInterface|null $page Page, defaults to the current page object
|
||||
* @return array
|
||||
*/
|
||||
public static function processImageExcerpt(array $excerpt, PageInterface $page)
|
||||
public static function processImageExcerpt(array $excerpt, PageInterface $page = null)
|
||||
{
|
||||
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
|
||||
$url_parts = static::parseUrl($url);
|
||||
$excerpts = new ExcerptsObject($page);
|
||||
|
||||
$media = null;
|
||||
$filename = null;
|
||||
|
||||
if (!empty($url_parts['stream'])) {
|
||||
$filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? '');
|
||||
|
||||
$media = $page->getMedia();
|
||||
|
||||
} else {
|
||||
$grav = Grav::instance();
|
||||
|
||||
// File is also local if scheme is http(s) and host matches.
|
||||
$local_file = isset($url_parts['path'])
|
||||
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https'], true))
|
||||
&& (empty($url_parts['host']) || $url_parts['host'] === $grav['uri']->host());
|
||||
|
||||
if ($local_file) {
|
||||
$filename = basename($url_parts['path']);
|
||||
$folder = dirname($url_parts['path']);
|
||||
|
||||
// Get the local path to page media if possible.
|
||||
if ($folder === $page->url(false, false, false)) {
|
||||
// Get the media objects for this page.
|
||||
$media = $page->getMedia();
|
||||
} else {
|
||||
// see if this is an external page to this one
|
||||
$base_url = rtrim($grav['base_url_relative'] . $grav['pages']->base(), '/');
|
||||
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
|
||||
|
||||
/** @var PageInterface $ext_page */
|
||||
$ext_page = $grav['pages']->dispatch($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->getMedia();
|
||||
} else {
|
||||
$grav->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a media file that matches the path referenced..
|
||||
if ($media && $filename && isset($media[$filename])) {
|
||||
// Get the medium object.
|
||||
/** @var Medium $medium */
|
||||
$medium = $media[$filename];
|
||||
|
||||
// Process operations
|
||||
$medium = static::processMediaActions($medium, $url_parts);
|
||||
$element_excerpt = $excerpt['element']['attributes'];
|
||||
|
||||
$alt = $element_excerpt['alt'] ?? '';
|
||||
$title = $element_excerpt['title'] ?? '';
|
||||
$class = $element_excerpt['class'] ?? '';
|
||||
$id = $element_excerpt['id'] ?? '';
|
||||
|
||||
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
|
||||
|
||||
} else {
|
||||
// Not a current page media file, see if it needs converting to relative.
|
||||
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
return $excerpts->processImageExcerpt($excerpt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,104 +138,13 @@ class Excerpts
|
||||
*
|
||||
* @param Medium $medium
|
||||
* @param string|array $url
|
||||
* @param PageInterface|null $page Page, defaults to the current page object
|
||||
* @return Medium
|
||||
*/
|
||||
public static function processMediaActions($medium, $url)
|
||||
public static function processMediaActions($medium, $url, PageInterface $page = null)
|
||||
{
|
||||
if (!is_array($url)) {
|
||||
$url_parts = parse_url($url);
|
||||
} else {
|
||||
$url_parts = $url;
|
||||
}
|
||||
$excerpts = new ExcerptsObject($page);
|
||||
|
||||
$actions = [];
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(explode('&', $url_parts['query']), function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = $parts[1] ?? null;
|
||||
$carry[] = ['method' => $parts[0], 'params' => $value];
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
}
|
||||
|
||||
if (Grav::instance()['config']->get('system.images.auto_fix_orientation')) {
|
||||
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
|
||||
}
|
||||
$defaults = Grav::instance()['config']->get('system.images.defaults');
|
||||
if (is_array($defaults) && count($defaults)) {
|
||||
foreach ($defaults as $method => $params) {
|
||||
$actions[] = [
|
||||
'method' => $method,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action) {
|
||||
$matches = [];
|
||||
|
||||
if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
|
||||
$args = [explode(',', $matches[1])];
|
||||
} else {
|
||||
$args = explode(',', $action['params']);
|
||||
}
|
||||
|
||||
$medium = call_user_func_array([$medium, $action['method']], $args);
|
||||
}
|
||||
|
||||
if (isset($url_parts['fragment'])) {
|
||||
$medium->urlHash($url_parts['fragment']);
|
||||
}
|
||||
|
||||
return $medium;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variation of parse_url() which works also with local streams.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array|bool
|
||||
*/
|
||||
protected static function parseUrl($url)
|
||||
{
|
||||
$url_parts = Utils::multibyteParseUrl($url);
|
||||
|
||||
if (isset($url_parts['scheme'])) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
// Special handling for the streams.
|
||||
if ($locator->schemeExists($url_parts['scheme'])) {
|
||||
if (isset($url_parts['host'])) {
|
||||
// Merge host and path into a path.
|
||||
$url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
|
||||
unset($url_parts['host']);
|
||||
}
|
||||
|
||||
$url_parts['stream'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $url_parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return bool|string
|
||||
*/
|
||||
protected static function resolveStream($url)
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if ($locator->isStream($url)) {
|
||||
return $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
|
||||
}
|
||||
|
||||
return $url;
|
||||
return $excerpts->processMediaActions($medium, $url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ class Truncator {
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
*/
|
||||
public function truncate(
|
||||
$text,
|
||||
|
||||
89
system/src/Grav/Common/Helpers/YamlLinter.php
Normal file
89
system/src/Grav/Common/Helpers/YamlLinter.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Helpers
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Helpers;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\File\MarkdownFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class YamlLinter
|
||||
{
|
||||
public static function lint()
|
||||
{
|
||||
$errors = static::lintConfig();
|
||||
$errors = $errors + static::lintPages();
|
||||
$errors = $errors + static::lintBlueprints();
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public static function lintPages()
|
||||
{
|
||||
return static::recurseFolder('page://');
|
||||
}
|
||||
|
||||
public static function lintConfig()
|
||||
{
|
||||
return static::recurseFolder('config://');
|
||||
}
|
||||
|
||||
public static function lintBlueprints()
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
$current_theme = Grav::instance()['config']->get('system.pages.theme');
|
||||
$theme_path = 'themes://' . $current_theme . '/blueprints';
|
||||
|
||||
$locator->addPath('blueprints', '', [$theme_path]);
|
||||
return static::recurseFolder('blueprints://');
|
||||
}
|
||||
|
||||
public static function recurseFolder($path, $extensions = 'md|yaml')
|
||||
{
|
||||
$lint_errors = [];
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
|
||||
if ($locator->isStream($path)) {
|
||||
$directory = $locator->getRecursiveIterator($path, $flags);
|
||||
} else {
|
||||
$directory = new \RecursiveDirectoryIterator($path, $flags);
|
||||
}
|
||||
$recursive = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$iterator = new \RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($iterator as $filepath => $file) {
|
||||
try {
|
||||
Yaml::parse(static::extractYaml($filepath));
|
||||
} catch (\Exception $e) {
|
||||
$lint_errors[str_replace(GRAV_ROOT, '', $filepath)] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return $lint_errors;
|
||||
}
|
||||
|
||||
protected static function extractYaml($path)
|
||||
{
|
||||
$extension = pathinfo($path, PATHINFO_EXTENSION);
|
||||
if ($extension === 'md') {
|
||||
$file = MarkdownFile::instance($path);
|
||||
$contents = $file->frontmatter();
|
||||
} else {
|
||||
$contents = file_get_contents($path);
|
||||
}
|
||||
return $contents;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -104,6 +104,11 @@ class Language
|
||||
public function getAvailable()
|
||||
{
|
||||
$languagesArray = $this->languages; //Make local copy
|
||||
|
||||
$languagesArray = array_map(function($value) {
|
||||
return preg_quote($value);
|
||||
}, $languagesArray);
|
||||
|
||||
sort($languagesArray);
|
||||
|
||||
return implode('|', array_reverse($languagesArray));
|
||||
@@ -229,7 +234,7 @@ class Language
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's a URL prefix based on configuration
|
||||
* Get a URL prefix based on configuration
|
||||
*
|
||||
* @param string|null $lang
|
||||
* @return string
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
|
||||
class Parsedown extends \Parsedown
|
||||
{
|
||||
@@ -18,12 +19,21 @@ class Parsedown extends \Parsedown
|
||||
/**
|
||||
* Parsedown constructor.
|
||||
*
|
||||
* @param PageInterface $page
|
||||
* @param Excerpts|null $excerpts
|
||||
* @param array|null $defaults
|
||||
*/
|
||||
public function __construct($page, $defaults)
|
||||
public function __construct($excerpts = null, $defaults = null)
|
||||
{
|
||||
$this->init($page, $defaults);
|
||||
if (!$excerpts || $excerpts instanceof PageInterface || null !== $defaults) {
|
||||
// Deprecated in Grav 1.6.10
|
||||
if ($defaults) {
|
||||
$defaults = ['markdown' => $defaults];
|
||||
}
|
||||
$excerpts = new Excerpts($excerpts, $defaults);
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->init($excerpts, $defaults);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
|
||||
class ParsedownExtra extends \ParsedownExtra
|
||||
{
|
||||
@@ -18,14 +19,23 @@ class ParsedownExtra extends \ParsedownExtra
|
||||
/**
|
||||
* ParsedownExtra constructor.
|
||||
*
|
||||
* @param PageInterface $page
|
||||
* @param Excerpts|null $excerpts
|
||||
* @param array|null $defaults
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($page, $defaults)
|
||||
public function __construct($excerpts = null, $defaults = null)
|
||||
{
|
||||
if (!$excerpts || $excerpts instanceof PageInterface || null !== $defaults) {
|
||||
// Deprecated in Grav 1.6.10
|
||||
if ($defaults) {
|
||||
$defaults = ['markdown' => $defaults];
|
||||
}
|
||||
$excerpts = new Excerpts($excerpts, $defaults);
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use new ' . __CLASS__ . '(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this->init($page, $defaults);
|
||||
$this->init($excerpts, $defaults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,13 @@
|
||||
|
||||
namespace Grav\Common\Markdown;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Helpers\Excerpts;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
trait ParsedownGravTrait
|
||||
{
|
||||
/** @var PageInterface $page */
|
||||
protected $page;
|
||||
/** @var Excerpts */
|
||||
protected $excerpts;
|
||||
|
||||
protected $special_chars;
|
||||
protected $twig_link_regex = '/\!*\[(?:.*)\]\((\{([\{%#])\s*(.*?)\s*(?:\2|\})\})\)/';
|
||||
@@ -28,28 +26,49 @@ trait ParsedownGravTrait
|
||||
/**
|
||||
* Initialization function to setup key variables needed by the MarkdownGravLinkTrait
|
||||
*
|
||||
* @param PageInterface $page
|
||||
* @param PageInterface|Excerpts|null $excerpts
|
||||
* @param array|null $defaults
|
||||
*/
|
||||
protected function init($page, $defaults)
|
||||
protected function init($excerpts = null, $defaults = null)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->page = $page;
|
||||
$this->BlockTypes['{'] [] = 'TwigTag';
|
||||
$this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
|
||||
|
||||
if ($defaults === null) {
|
||||
$defaults = (array)Grav::instance()['config']->get('system.pages.markdown');
|
||||
if (!$excerpts || $excerpts instanceof PageInterface) {
|
||||
// Deprecated in Grav 1.6.10
|
||||
if ($defaults) {
|
||||
$defaults = ['markdown' => $defaults];
|
||||
}
|
||||
$this->excerpts = new Excerpts($excerpts, $defaults);
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '($page, $defaults) is deprecated since Grav 1.6.10, use ->init(new ' . Excerpts::class . '($page, [\'markdown\' => $defaults])) instead.', E_USER_DEPRECATED);
|
||||
} else {
|
||||
$this->excerpts = $excerpts;
|
||||
}
|
||||
|
||||
$this->setBreaksEnabled($defaults['auto_line_breaks']);
|
||||
$this->setUrlsLinked($defaults['auto_url_links']);
|
||||
$this->setMarkupEscaped($defaults['escape_markup']);
|
||||
$this->setSpecialChars($defaults['special_chars']);
|
||||
$this->BlockTypes['{'][] = 'TwigTag';
|
||||
$this->special_chars = ['>' => 'gt', '<' => 'lt', '"' => 'quot'];
|
||||
|
||||
$grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $this, 'page' => $page]));
|
||||
$defaults = $this->excerpts->getConfig();
|
||||
|
||||
if (isset($defaults['markdown']['auto_line_breaks'])) {
|
||||
$this->setBreaksEnabled($defaults['markdown']['auto_line_breaks']);
|
||||
}
|
||||
if (isset($defaults['markdown']['auto_url_links'])) {
|
||||
$this->setUrlsLinked($defaults['markdown']['auto_url_links']);
|
||||
}
|
||||
if (isset($defaults['markdown']['escape_markup'])) {
|
||||
$this->setMarkupEscaped($defaults['markdown']['escape_markup']);
|
||||
}
|
||||
if (isset($defaults['markdown']['special_chars'])) {
|
||||
$this->setSpecialChars($defaults['markdown']['special_chars']);
|
||||
}
|
||||
|
||||
$this->excerpts->fireInitializedEvent($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Excerpts
|
||||
*/
|
||||
public function getExcerpts()
|
||||
{
|
||||
return $this->excerpts;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +133,8 @@ trait ParsedownGravTrait
|
||||
*/
|
||||
protected function isBlockContinuable($Type)
|
||||
{
|
||||
$continuable = \in_array($Type, $this->continuable_blocks) || method_exists($this, 'block' . $Type . 'Continue');
|
||||
$continuable = \in_array($Type, $this->continuable_blocks, true)
|
||||
|| method_exists($this, 'block' . $Type . 'Continue');
|
||||
|
||||
return $continuable;
|
||||
}
|
||||
@@ -128,7 +148,8 @@ trait ParsedownGravTrait
|
||||
*/
|
||||
protected function isBlockCompletable($Type)
|
||||
{
|
||||
$completable = \in_array($Type, $this->completable_blocks) || method_exists($this, 'block' . $Type . 'Complete');
|
||||
$completable = \in_array($Type, $this->completable_blocks, true)
|
||||
|| method_exists($this, 'block' . $Type . 'Complete');
|
||||
|
||||
return $completable;
|
||||
}
|
||||
@@ -210,7 +231,7 @@ trait ParsedownGravTrait
|
||||
|
||||
// if this is an image process it
|
||||
if (isset($excerpt['element']['attributes']['src'])) {
|
||||
$excerpt = Excerpts::processImageExcerpt($excerpt, $this->page);
|
||||
$excerpt = $this->excerpts->processImageExcerpt($excerpt);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
@@ -218,11 +239,7 @@ trait ParsedownGravTrait
|
||||
|
||||
protected function inlineLink($excerpt)
|
||||
{
|
||||
if (isset($excerpt['type'])) {
|
||||
$type = $excerpt['type'];
|
||||
} else {
|
||||
$type = 'link';
|
||||
}
|
||||
$type = $excerpt['type'] ?? 'link';
|
||||
|
||||
// do some trickery to get around Parsedown requirement for valid URL if its Twig in there
|
||||
if (preg_match($this->twig_link_regex, $excerpt['text'], $matches)) {
|
||||
@@ -238,13 +255,15 @@ trait ParsedownGravTrait
|
||||
|
||||
// if this is a link
|
||||
if (isset($excerpt['element']['attributes']['href'])) {
|
||||
$excerpt = Excerpts::processLinkExcerpt($excerpt, $this->page, $type);
|
||||
$excerpt = $this->excerpts->processLinkExcerpt($excerpt, $type);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
// For extending this class via plugins
|
||||
/**
|
||||
* For extending this class via plugins
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (isset($this->{$method}) === true) {
|
||||
|
||||
329
system/src/Grav/Common/Page/Markdown/Excerpts.php
Normal file
329
system/src/Grav/Common/Page/Markdown/Excerpts.php
Normal file
@@ -0,0 +1,329 @@
|
||||
<?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\Markdown;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Medium\Link;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Page\Medium\Medium;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class Excerpts
|
||||
{
|
||||
/** @var PageInterface */
|
||||
protected $page;
|
||||
/** @var array */
|
||||
protected $config;
|
||||
|
||||
public function __construct(PageInterface $page = null, array $config = null)
|
||||
{
|
||||
$this->page = $page ?? Grav::instance()['page'] ?? null;
|
||||
|
||||
// Add defaults to the configuration.
|
||||
if (null === $config || !isset($config['markdown'], $config['images'])) {
|
||||
$c = Grav::instance()['config'];
|
||||
$config = $config ?? [];
|
||||
$config += [
|
||||
'markdown' => $c->get('system.pages.markdown', []),
|
||||
'images' => $c->get('system.images', [])
|
||||
];
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getPage(): PageInterface
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function fireInitializedEvent($markdown): void
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
$grav->fireEvent('onMarkdownInitialized', new Event(['markdown' => $markdown, 'page' => $this->page]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a Link excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @param string $type
|
||||
* @return array
|
||||
*/
|
||||
public function processLinkExcerpt(array $excerpt, string $type = 'link'): array
|
||||
{
|
||||
$url = htmlspecialchars_decode(rawurldecode($excerpt['element']['attributes']['href']));
|
||||
|
||||
$url_parts = $this->parseUrl($url);
|
||||
|
||||
// If there is a query, then parse it and build action calls.
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(
|
||||
explode('&', $url_parts['query']),
|
||||
static function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = isset($parts[1]) ? rawurldecode($parts[1]) : true;
|
||||
$carry[$parts[0]] = $value;
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Valid attributes supported.
|
||||
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes'];
|
||||
|
||||
// Unless told to not process, go through actions.
|
||||
if (array_key_exists('noprocess', $actions)) {
|
||||
unset($actions['noprocess']);
|
||||
} else {
|
||||
// Loop through actions for the image and call them.
|
||||
foreach ($actions as $attrib => $value) {
|
||||
$key = $attrib;
|
||||
|
||||
if (in_array($attrib, $valid_attributes, true)) {
|
||||
// support both class and classes.
|
||||
if ($attrib === 'classes') {
|
||||
$attrib = 'class';
|
||||
}
|
||||
$excerpt['element']['attributes'][$attrib] = str_replace(',', ' ', $value);
|
||||
unset($actions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$url_parts['query'] = http_build_query($actions, null, '&', PHP_QUERY_RFC3986);
|
||||
}
|
||||
|
||||
// If no query elements left, unset query.
|
||||
if (empty($url_parts['query'])) {
|
||||
unset ($url_parts['query']);
|
||||
}
|
||||
|
||||
// Set path to / if not set.
|
||||
if (empty($url_parts['path'])) {
|
||||
$url_parts['path'] = '';
|
||||
}
|
||||
|
||||
// If scheme isn't http(s)..
|
||||
if (!empty($url_parts['scheme']) && !in_array($url_parts['scheme'], ['http', 'https'])) {
|
||||
// Handle custom streams.
|
||||
if ($type !== 'image' && !empty($url_parts['stream']) && !empty($url_parts['path'])) {
|
||||
$grav = Grav::instance();
|
||||
$url_parts['path'] = $grav['base_url_relative'] . '/' . $this->resolveStream("{$url_parts['scheme']}://{$url_parts['path']}");
|
||||
unset($url_parts['stream'], $url_parts['scheme']);
|
||||
}
|
||||
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
// Handle paths and such.
|
||||
$url_parts = Uri::convertUrl($this->page, $url_parts, $type);
|
||||
|
||||
// Build the URL from the component parts and set it on the element.
|
||||
$excerpt['element']['attributes']['href'] = Uri::buildUrl($url_parts);
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an image excerpt
|
||||
*
|
||||
* @param array $excerpt
|
||||
* @return array
|
||||
*/
|
||||
public function processImageExcerpt(array $excerpt): array
|
||||
{
|
||||
$url = htmlspecialchars_decode(urldecode($excerpt['element']['attributes']['src']));
|
||||
$url_parts = $this->parseUrl($url);
|
||||
|
||||
$media = null;
|
||||
$filename = null;
|
||||
|
||||
if (!empty($url_parts['stream'])) {
|
||||
$filename = $url_parts['scheme'] . '://' . ($url_parts['path'] ?? '');
|
||||
|
||||
$media = $this->page->getMedia();
|
||||
|
||||
} else {
|
||||
$grav = Grav::instance();
|
||||
|
||||
// File is also local if scheme is http(s) and host matches.
|
||||
$local_file = isset($url_parts['path'])
|
||||
&& (empty($url_parts['scheme']) || in_array($url_parts['scheme'], ['http', 'https'], true))
|
||||
&& (empty($url_parts['host']) || $url_parts['host'] === $grav['uri']->host());
|
||||
|
||||
if ($local_file) {
|
||||
$filename = basename($url_parts['path']);
|
||||
$folder = dirname($url_parts['path']);
|
||||
|
||||
// Get the local path to page media if possible.
|
||||
if ($this->page && $folder === $this->page->url(false, false, false)) {
|
||||
// Get the media objects for this page.
|
||||
$media = $this->page->getMedia();
|
||||
} else {
|
||||
// see if this is an external page to this one
|
||||
$base_url = rtrim($grav['base_url_relative'] . $grav['pages']->base(), '/');
|
||||
$page_route = '/' . ltrim(str_replace($base_url, '', $folder), '/');
|
||||
|
||||
/** @var PageInterface $ext_page */
|
||||
$ext_page = $grav['pages']->dispatch($page_route, true);
|
||||
if ($ext_page) {
|
||||
$media = $ext_page->getMedia();
|
||||
} else {
|
||||
$grav->fireEvent('onMediaLocate', new Event(['route' => $page_route, 'media' => &$media]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a media file that matches the path referenced..
|
||||
if ($media && $filename && isset($media[$filename])) {
|
||||
// Get the medium object.
|
||||
/** @var Medium $medium */
|
||||
$medium = $media[$filename];
|
||||
|
||||
// Process operations
|
||||
$medium = $this->processMediaActions($medium, $url_parts);
|
||||
$element_excerpt = $excerpt['element']['attributes'];
|
||||
|
||||
$alt = $element_excerpt['alt'] ?? '';
|
||||
$title = $element_excerpt['title'] ?? '';
|
||||
$class = $element_excerpt['class'] ?? '';
|
||||
$id = $element_excerpt['id'] ?? '';
|
||||
|
||||
$excerpt['element'] = $medium->parsedownElement($title, $alt, $class, $id, true);
|
||||
|
||||
} else {
|
||||
// Not a current page media file, see if it needs converting to relative.
|
||||
$excerpt['element']['attributes']['src'] = Uri::buildUrl($url_parts);
|
||||
}
|
||||
|
||||
return $excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process media actions
|
||||
*
|
||||
* @param Medium $medium
|
||||
* @param string|array $url
|
||||
* @return Medium|Link
|
||||
*/
|
||||
public function processMediaActions($medium, $url)
|
||||
{
|
||||
$url_parts = is_string($url) ? $this->parseUrl($url) : $url;
|
||||
$actions = [];
|
||||
|
||||
// if there is a query, then parse it and build action calls
|
||||
if (isset($url_parts['query'])) {
|
||||
$actions = array_reduce(
|
||||
explode('&', $url_parts['query']),
|
||||
static function ($carry, $item) {
|
||||
$parts = explode('=', $item, 2);
|
||||
$value = $parts[1] ?? null;
|
||||
$carry[] = ['method' => $parts[0], 'params' => $value];
|
||||
|
||||
return $carry;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
$config = $this->getConfig();
|
||||
if (!empty($config['images']['auto_fix_orientation'])) {
|
||||
$actions[] = ['method' => 'fixOrientation', 'params' => ''];
|
||||
}
|
||||
|
||||
$defaults = $config['images']['defaults'] ?? [];
|
||||
if (count($defaults)) {
|
||||
foreach ($defaults as $method => $params) {
|
||||
$actions[] = [
|
||||
'method' => $method,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($actions as $action) {
|
||||
$matches = [];
|
||||
|
||||
if (preg_match('/\[(.*)\]/', $action['params'], $matches)) {
|
||||
$args = [explode(',', $matches[1])];
|
||||
} else {
|
||||
$args = explode(',', $action['params']);
|
||||
}
|
||||
|
||||
$medium = call_user_func_array([$medium, $action['method']], $args);
|
||||
}
|
||||
|
||||
if (isset($url_parts['fragment'])) {
|
||||
$medium->urlHash($url_parts['fragment']);
|
||||
}
|
||||
|
||||
return $medium;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variation of parse_url() which works also with local streams.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function parseUrl(string $url)
|
||||
{
|
||||
$url_parts = Utils::multibyteParseUrl($url);
|
||||
|
||||
if (isset($url_parts['scheme'])) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
// Special handling for the streams.
|
||||
if ($locator->schemeExists($url_parts['scheme'])) {
|
||||
if (isset($url_parts['host'])) {
|
||||
// Merge host and path into a path.
|
||||
$url_parts['path'] = $url_parts['host'] . (isset($url_parts['path']) ? '/' . $url_parts['path'] : '');
|
||||
unset($url_parts['host']);
|
||||
}
|
||||
|
||||
$url_parts['stream'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $url_parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function resolveStream(string $url)
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
if ($locator->isStream($url)) {
|
||||
return $locator->findResource($url, false) ?: $locator->findResource($url, false, true);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -154,6 +154,9 @@ abstract class AbstractMedia implements ExportInterface, MediaCollectionInterfac
|
||||
*/
|
||||
public function add($name, $file)
|
||||
{
|
||||
if (!$file) {
|
||||
return;
|
||||
}
|
||||
$this->offsetSet($name, $file);
|
||||
switch ($file->type) {
|
||||
case 'image':
|
||||
|
||||
@@ -170,8 +170,7 @@ class ImageMedium extends Medium
|
||||
{
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$image_path = $locator->findResource('cache://images', true);
|
||||
$image_dir = $locator->findResource('cache://images', false);
|
||||
$image_path = $locator->findResource('cache://images', true) ?: $locator->findResource('cache://images', true, true);
|
||||
$saved_image_path = $this->saveImage();
|
||||
|
||||
$output = preg_replace('|^' . preg_quote(GRAV_ROOT, '|') . '|', '', $saved_image_path);
|
||||
@@ -181,6 +180,7 @@ class ImageMedium extends Medium
|
||||
}
|
||||
|
||||
if (Utils::startsWith($output, $image_path)) {
|
||||
$image_dir = $locator->findResource('cache://images', false);
|
||||
$output = '/' . $image_dir . preg_replace('|^' . preg_quote($image_path, '|') . '|', '', $output);
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ class ImageMedium extends Medium
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the ability to override the Inmage's Pretty name stored in cache
|
||||
* Allows the ability to override the image's pretty name stored in cache
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace Grav\Common\Page\Medium;
|
||||
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
|
||||
trait ParsedownHtmlTrait
|
||||
{
|
||||
@@ -33,7 +34,7 @@ trait ParsedownHtmlTrait
|
||||
$element = $this->parsedownElement($title, $alt, $class, $id, $reset);
|
||||
|
||||
if (!$this->parsedown) {
|
||||
$this->parsedown = new Parsedown(null, null);
|
||||
$this->parsedown = new Parsedown(new Excerpts());
|
||||
}
|
||||
|
||||
return $this->parsedown->elementToHtml($element);
|
||||
|
||||
@@ -19,6 +19,7 @@ use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Markdown\ParsedownExtra;
|
||||
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\Uri;
|
||||
use Grav\Common\Utils;
|
||||
@@ -27,7 +28,6 @@ use Negotiation\Accept;
|
||||
use Negotiation\Negotiator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\MarkdownFile;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
|
||||
define('PAGE_ORDER_PREFIX_REGEX', '/^[0-9]+\./u');
|
||||
|
||||
@@ -819,23 +819,31 @@ class Page implements PageInterface
|
||||
/** @var Config $config */
|
||||
$config = Grav::instance()['config'];
|
||||
|
||||
$defaults = (array)$config->get('system.pages.markdown');
|
||||
$markdownDefaults = (array)$config->get('system.pages.markdown');
|
||||
if (isset($this->header()->markdown)) {
|
||||
$defaults = array_merge($defaults, $this->header()->markdown);
|
||||
$markdownDefaults = array_merge($markdownDefaults, $this->header()->markdown);
|
||||
}
|
||||
|
||||
// pages.markdown_extra is deprecated, but still check it...
|
||||
if (!isset($defaults['extra']) && (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null)) {
|
||||
if (!isset($markdownDefaults['extra']) && (isset($this->markdown_extra) || $config->get('system.pages.markdown_extra') !== null)) {
|
||||
user_error('Configuration option \'system.pages.markdown_extra\' is deprecated since Grav 1.5, use \'system.pages.markdown.extra\' instead', E_USER_DEPRECATED);
|
||||
|
||||
$defaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra');
|
||||
$markdownDefaults['extra'] = $this->markdown_extra ?: $config->get('system.pages.markdown_extra');
|
||||
}
|
||||
|
||||
$extra = $markdownDefaults['extra'] ?? false;
|
||||
$defaults = [
|
||||
'markdown' => $markdownDefaults,
|
||||
'images' => $config->get('system.images', [])
|
||||
];
|
||||
|
||||
$excerpts = new Excerpts($this, $defaults);
|
||||
|
||||
// Initialize the preferred variant of Parsedown
|
||||
if ($defaults['extra']) {
|
||||
$parsedown = new ParsedownExtra($this, $defaults);
|
||||
if ($extra) {
|
||||
$parsedown = new ParsedownExtra($excerpts);
|
||||
} else {
|
||||
$parsedown = new Parsedown($this, $defaults);
|
||||
$parsedown = new Parsedown($excerpts);
|
||||
}
|
||||
|
||||
$this->content = $parsedown->text($this->content);
|
||||
@@ -1397,12 +1405,12 @@ class Page implements PageInterface
|
||||
return $this->template_format;
|
||||
}
|
||||
|
||||
// Use content negotitation via the `accept:` header
|
||||
$http_accept = $_SERVER['HTTP_ACCEPT'] ?? false;
|
||||
// Use content negotiation via the `accept:` header
|
||||
$http_accept = $_SERVER['HTTP_ACCEPT'] ?? null;
|
||||
if (is_string($http_accept)) {
|
||||
$negotiator = new Negotiator();
|
||||
|
||||
$supported_types = Grav::instance()['config']->get('system.pages.types', ['html', 'json']);
|
||||
$supported_types = Utils::getSupportPageTypes(['html', 'json']);
|
||||
$priorities = Utils::getMimeTypes($supported_types);
|
||||
|
||||
$media_type = $negotiator->getBest($http_accept, $priorities);
|
||||
@@ -2930,7 +2938,7 @@ class Page implements PageInterface
|
||||
case 'page':
|
||||
case 'self':
|
||||
$results = new Collection();
|
||||
$results = $results->addPage($page)->nonModular();
|
||||
$results = $results->addPage($page);
|
||||
break;
|
||||
|
||||
case 'descendants':
|
||||
|
||||
@@ -88,6 +88,13 @@ class Pages
|
||||
*/
|
||||
protected $ignore_hidden;
|
||||
|
||||
/** @var string */
|
||||
protected $check_method;
|
||||
|
||||
protected $pages_cache_id;
|
||||
|
||||
protected $initialized = false;
|
||||
|
||||
/**
|
||||
* @var Types
|
||||
*/
|
||||
@@ -98,8 +105,6 @@ class Pages
|
||||
*/
|
||||
static protected $home_route;
|
||||
|
||||
protected $pages_cache_id;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -226,11 +231,20 @@ class Pages
|
||||
return $this->baseUrl($lang, $absolute) . Uri::filterPath($route);
|
||||
}
|
||||
|
||||
public function setCheckMethod($method)
|
||||
{
|
||||
$this->check_method = strtolower($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class initialization. Must be called before using this class.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if ($this->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = $this->grav['config'];
|
||||
$this->ignore_files = $config->get('system.pages.ignore_files');
|
||||
$this->ignore_folders = $config->get('system.pages.ignore_folders');
|
||||
@@ -240,6 +254,10 @@ class Pages
|
||||
$this->children = [];
|
||||
$this->routes = [];
|
||||
|
||||
if (!$this->check_method) {
|
||||
$this->setCheckMethod($config->get('system.cache.check.method', 'file'));
|
||||
}
|
||||
|
||||
$this->buildPages();
|
||||
}
|
||||
|
||||
@@ -947,7 +965,7 @@ class Pages
|
||||
$taxonomy = $this->grav['taxonomy'];
|
||||
|
||||
// how should we check for last modified? Default is by file
|
||||
switch (strtolower($config->get('system.cache.check.method', 'file'))) {
|
||||
switch ($this->check_method) {
|
||||
case 'none':
|
||||
case 'off':
|
||||
$hash = 0;
|
||||
|
||||
@@ -151,15 +151,32 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
|
||||
if (\is_string($params)) {
|
||||
$dispatcher->addListener($eventName, [$this, $params]);
|
||||
} elseif (\is_string($params[0])) {
|
||||
$dispatcher->addListener($eventName, [$this, $params[0]], $params[1] ?? 0);
|
||||
$dispatcher->addListener($eventName, [$this, $params[0]], $this->getPriority($params, $eventName));
|
||||
} else {
|
||||
foreach ($params as $listener) {
|
||||
$dispatcher->addListener($eventName, [$this, $listener[0]], $listener[1] ?? 0);
|
||||
$dispatcher->addListener($eventName, [$this, $listener[0]], $this->getPriority($listener, $eventName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @param string $eventName
|
||||
*/
|
||||
private function getPriority($params, $eventName)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$override = implode('.', ["priorities", $this->name, $eventName, $params[0]]);
|
||||
if ($grav['config']->get($override) !== null)
|
||||
{
|
||||
return $grav['config']->get($override);
|
||||
} elseif (isset($params[1])) {
|
||||
return $params[1];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $events
|
||||
*/
|
||||
|
||||
@@ -133,12 +133,25 @@ class Plugins extends Iterator
|
||||
*/
|
||||
public static function all()
|
||||
{
|
||||
$plugins = Grav::instance()['plugins'];
|
||||
$grav = Grav::instance();
|
||||
$plugins = $grav['plugins'];
|
||||
$list = [];
|
||||
|
||||
foreach ($plugins as $instance) {
|
||||
$name = $instance->name;
|
||||
$result = self::get($name);
|
||||
|
||||
try {
|
||||
$result = self::get($name);
|
||||
} catch (\Exception $e) {
|
||||
$exception = new \RuntimeException(sprintf('Plugin %s: %s', $name, $e->getMessage()), $e->getCode(), $e);
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $grav['debugger'];
|
||||
$debugger->addMessage("Plugin {$name} cannot be loaded, please check Exceptions tab", 'error');
|
||||
$debugger->addException($exception);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$list[$name] = $result;
|
||||
@@ -185,24 +198,31 @@ class Plugins extends Iterator
|
||||
$grav = Grav::instance();
|
||||
$locator = $grav['locator'];
|
||||
|
||||
$filePath = $locator->findResource('plugins://' . $name . DS . $name . PLUGIN_EXT);
|
||||
if (!is_file($filePath)) {
|
||||
$file = $locator->findResource('plugins://' . $name . DS . $name . PLUGIN_EXT);
|
||||
|
||||
if (is_file($file)) {
|
||||
// Local variables available in the file: $grav, $config, $name, $file
|
||||
$class = include_once $file;
|
||||
|
||||
$pluginClassFormat = [
|
||||
'Grav\\Plugin\\' . ucfirst($name). 'Plugin',
|
||||
'Grav\\Plugin\\' . Inflector::camelize($name) . 'Plugin'
|
||||
];
|
||||
|
||||
foreach ($pluginClassFormat as $pluginClass) {
|
||||
if (class_exists($pluginClass)) {
|
||||
$class = new $pluginClass($name, $grav);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$grav['log']->addWarning(
|
||||
sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $name)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
require_once $filePath;
|
||||
|
||||
$pluginClassName = 'Grav\\Plugin\\' . ucfirst($name) . 'Plugin';
|
||||
if (!class_exists($pluginClassName)) {
|
||||
$pluginClassName = 'Grav\\Plugin\\' . $grav['inflector']->camelize($name) . 'Plugin';
|
||||
if (!class_exists($pluginClassName)) {
|
||||
throw new \RuntimeException(sprintf("Plugin '%s' class not found! Try reinstalling this plugin.", $name));
|
||||
}
|
||||
}
|
||||
return new $pluginClassName($name, $grav);
|
||||
return $class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,10 +30,13 @@ class RequestProcessor extends ProcessorBase
|
||||
$request = $request->withParsedBody(json_decode($request->getBody()->getContents(), true));
|
||||
}
|
||||
|
||||
$uri = $request->getUri();
|
||||
$ext = mb_strtolower(pathinfo($uri->getPath(), PATHINFO_EXTENSION));
|
||||
|
||||
$request = $request
|
||||
->withAttribute('grav', $this->container)
|
||||
->withAttribute('time', $_SERVER['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME)
|
||||
->withAttribute('route', Uri::getCurrentRoute())
|
||||
->withAttribute('route', Uri::getCurrentRoute()->withExtension($ext))
|
||||
->withAttribute('referrer', $this->container['uri']->referrer());
|
||||
|
||||
$event = new RequestHandlerEvent(['request' => $request, 'handler' => $handler]);
|
||||
|
||||
@@ -27,9 +27,10 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container['accounts'] = function (Container $container) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $container['debugger'];
|
||||
if ($container['config']->get('system.accounts.type') === 'flex') {
|
||||
$type = strtolower(defined('GRAV_USER_INSTANCE') ? GRAV_USER_INSTANCE : $container['config']->get('system.accounts.type', 'data'));
|
||||
if ($type === 'flex') {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $container['debugger'];
|
||||
$debugger->addMessage('User Accounts: Flex Directory');
|
||||
return $this->flexAccounts($container);
|
||||
}
|
||||
@@ -46,7 +47,9 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
|
||||
protected function dataAccounts(Container $container)
|
||||
{
|
||||
define('GRAV_USER_INSTANCE', 'DATA');
|
||||
if (!defined('GRAV_USER_INSTANCE')) {
|
||||
define('GRAV_USER_INSTANCE', 'DATA');
|
||||
}
|
||||
|
||||
// Use User class for backwards compatibility.
|
||||
return new DataUser\UserCollection(User::class);
|
||||
@@ -54,7 +57,9 @@ class AccountsServiceProvider implements ServiceProviderInterface
|
||||
|
||||
protected function flexAccounts(Container $container)
|
||||
{
|
||||
define('GRAV_USER_INSTANCE', 'FLEX');
|
||||
if (!defined('GRAV_USER_INSTANCE')) {
|
||||
define('GRAV_USER_INSTANCE', 'FLEX');
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $container['config'];
|
||||
|
||||
@@ -80,8 +80,6 @@ class PagesServiceProvider implements ServiceProviderInterface
|
||||
}
|
||||
// Default route test and redirect
|
||||
if ($config->get('system.pages.redirect_default_route') && $page->route() !== $path) {
|
||||
$uri->setUriProperties(['path' => $page->route()]);
|
||||
$url = $uri->toOriginalString();
|
||||
$c->redirect($url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ class RequestServiceProvider implements ServiceProviderInterface
|
||||
return $creator->fromGlobals();
|
||||
};
|
||||
|
||||
$container['route'] = function() {
|
||||
return Uri::getCurrentRoute();
|
||||
};
|
||||
$container['route'] = $container->factory(function() {
|
||||
return clone Uri::getCurrentRoute();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use Grav\Common\Config\Config;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Session;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
use RocketTheme\Toolbox\Session\Message;
|
||||
@@ -49,14 +50,17 @@ class SessionServiceProvider implements ServiceProviderInterface
|
||||
// Activate admin if we're inside the admin path.
|
||||
$is_admin = false;
|
||||
if ($config->get('plugins.admin.enabled')) {
|
||||
$base = '/' . trim($config->get('plugins.admin.route'), '/');
|
||||
$admin_base = '/' . trim($config->get('plugins.admin.route'), '/');
|
||||
|
||||
// Uri::route() is not processed yet, let's quickly get what we need.
|
||||
$current_route = str_replace(Uri::filterPath($uri->rootUrl(false)), '', parse_url($uri->url(true), PHP_URL_PATH));
|
||||
|
||||
// Test to see if path starts with a supported language + admin base
|
||||
$lang = Utils::pathPrefixedByLangCode($current_route);
|
||||
$lang_admin_base = '/' . $lang . $admin_base;
|
||||
|
||||
// Check no language, simple language prefix (en) and region specific language prefix (en-US).
|
||||
$pos = strpos($current_route, $base);
|
||||
if ($pos === 0 || $pos === 3 || $pos === 6) {
|
||||
if (Utils::startsWith($current_route, $admin_base) || Utils::startsWith($current_route, $lang_admin_base)) {
|
||||
$cookie_lifetime = $config->get('plugins.admin.session.timeout', 1800);
|
||||
$enabled = $is_admin = true;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,19 @@ class Themes extends Iterator
|
||||
}
|
||||
|
||||
$theme = $directory->getFilename();
|
||||
$result = $this->get($theme);
|
||||
|
||||
try {
|
||||
$result = $this->get($theme);
|
||||
} catch (\Exception $e) {
|
||||
$exception = new \RuntimeException(sprintf('Theme %s: %s', $theme, $e->getMessage()), $e->getCode(), $e);
|
||||
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this->grav['debugger'];
|
||||
$debugger->addMessage("Theme {$theme} cannot be loaded, please check Exceptions tab", 'error');
|
||||
$debugger->addException($exception);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$list[$theme] = $result;
|
||||
@@ -196,8 +208,7 @@ class Themes extends Iterator
|
||||
|
||||
foreach ($themeClassFormat as $themeClass) {
|
||||
if (class_exists($themeClass)) {
|
||||
$themeClassName = $themeClass;
|
||||
$class = new $themeClassName($grav, $config, $name);
|
||||
$class = new $themeClass($grav, $config, $name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
|
||||
new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
|
||||
new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
|
||||
new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['is_safe' => ['html']]),
|
||||
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']),
|
||||
@@ -447,11 +447,15 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*/
|
||||
public function containsFilter($haystack, $needle)
|
||||
{
|
||||
return (strpos($haystack, $needle) !== false);
|
||||
if (empty($needle)) {
|
||||
return $haystack;
|
||||
}
|
||||
|
||||
return (strpos($haystack, (string) $needle) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a human readable output for cron sytnax
|
||||
* Gets a human readable output for cron syntax
|
||||
*
|
||||
* @param $at
|
||||
* @return string
|
||||
@@ -612,9 +616,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
* @param bool $block Block or Line processing
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function markdownFunction($string, $block = true)
|
||||
public function markdownFunction($context = false, $string, $block = true)
|
||||
{
|
||||
return Utils::processMarkdown($string, $block);
|
||||
$page = $context['page'] ?? null;
|
||||
return Utils::processMarkdown($string, $block, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1000,10 +1005,10 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
*/
|
||||
public function authorize($action)
|
||||
{
|
||||
/** @var UserInterface $user */
|
||||
$user = $this->grav['user'];
|
||||
/** @var UserInterface|null $user */
|
||||
$user = $this->grav['user'] ?? null;
|
||||
|
||||
if (!$user->authenticated || (isset($user->authorized) && !$user->authorized)) {
|
||||
if (!$user || !$user->authenticated || (isset($user->authorized) && !$user->authorized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1132,7 +1137,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the Exif data for a file
|
||||
* Get the Exif data for a file
|
||||
*
|
||||
* @param string $image
|
||||
* @param bool $raw
|
||||
@@ -1150,7 +1155,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
|
||||
|
||||
$exif_reader = $this->grav['exif']->getReader();
|
||||
|
||||
if ($image & file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) {
|
||||
if ($image && file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) {
|
||||
|
||||
$exif_data = $exif_reader->read($image);
|
||||
|
||||
|
||||
@@ -1154,7 +1154,7 @@ class Uri
|
||||
$this->scheme = $env['X-FORWARDED-PROTO'];
|
||||
} elseif (isset($env['HTTP_CLOUDFRONT_FORWARDED_PROTO'])) {
|
||||
$this->scheme = $env['HTTP_CLOUDFRONT_FORWARDED_PROTO'];
|
||||
} elseif (isset($env['REQUEST_SCHEME'])) {
|
||||
} elseif (isset($env['REQUEST_SCHEME']) && empty($env['HTTPS'])) {
|
||||
$this->scheme = $env['REQUEST_SCHEME'];
|
||||
} else {
|
||||
$https = $env['HTTPS'] ?? '';
|
||||
@@ -1286,7 +1286,7 @@ class Uri
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's post from either $_POST or JSON response object
|
||||
* Get post from either $_POST or JSON response object
|
||||
* By default returns all data, or can return a single item
|
||||
*
|
||||
* @param string $element
|
||||
@@ -1345,7 +1345,7 @@ class Uri
|
||||
*/
|
||||
public function isValidExtension($extension)
|
||||
{
|
||||
$valid_page_types = implode('|', Grav::instance()['config']->get('system.pages.types'));
|
||||
$valid_page_types = implode('|', Utils::getSupportPageTypes());
|
||||
|
||||
// Strip the file extension for valid page types
|
||||
if (preg_match('/(' . $valid_page_types . ')/', $extension)) {
|
||||
|
||||
@@ -118,4 +118,13 @@ class UserCollection implements UserCollectionInterface
|
||||
|
||||
return $file_path && unlink($file_path);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
// check for existence of a user account
|
||||
$account_dir = $file_path = Grav::instance()['locator']->findResource('account://');
|
||||
$accounts = glob($account_dir . '/*.yaml') ?: [];
|
||||
|
||||
return count($accounts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
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;
|
||||
@@ -381,6 +382,31 @@ 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.
|
||||
*
|
||||
@@ -431,6 +457,20 @@ class User extends FlexObject implements UserInterface, MediaManipulationInterfa
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
public function isAuthorized(string $action, string $scope = null, UserInterface $user = null): bool
|
||||
{
|
||||
if (null === $user) {
|
||||
/** @var UserInterface $user */
|
||||
$user = Grav::instance()['user'] ?? null;
|
||||
}
|
||||
|
||||
if ($user instanceof User && $user->getStorageKey() === $this->getStorageKey()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::isAuthorized($action, $scope, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace Grav\Common\User\Interfaces;
|
||||
|
||||
interface UserCollectionInterface
|
||||
interface UserCollectionInterface extends \Countable
|
||||
{
|
||||
/**
|
||||
* Load user account.
|
||||
|
||||
@@ -148,12 +148,13 @@ trait UserTrait
|
||||
|
||||
// Try looking for provider.
|
||||
$provider = $this->get('provider');
|
||||
if (\is_array($provider)) {
|
||||
if (isset($provider['avatar_url']) && \is_string($provider['avatar_url'])) {
|
||||
return $provider['avatar_url'];
|
||||
$provider_options = $this->get($provider);
|
||||
if (\is_array($provider_options)) {
|
||||
if (isset($provider_options['avatar_url']) && \is_string($provider_options['avatar_url'])) {
|
||||
return $provider_options['avatar_url'];
|
||||
}
|
||||
if (isset($provider['avatar']) && \is_string($provider['avatar'])) {
|
||||
return $provider['avatar'];
|
||||
if (isset($provider_options['avatar']) && \is_string($provider_options['avatar'])) {
|
||||
return $provider_options['avatar'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use Grav\Common\Helpers\Truncator;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Markdown\Parsedown;
|
||||
use Grav\Common\Markdown\ParsedownExtra;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
@@ -27,55 +28,103 @@ abstract class Utils
|
||||
/**
|
||||
* Simple helper method to make getting a Grav URL easier
|
||||
*
|
||||
* @param string $input
|
||||
* @param string|object $input
|
||||
* @param bool $domain
|
||||
* @param bool $fail_gracefully
|
||||
* @return bool|null|string
|
||||
*/
|
||||
public static function url($input, $domain = false)
|
||||
public static function url($input, $domain = false, $fail_gracefully = false)
|
||||
{
|
||||
if (!trim((string)$input)) {
|
||||
$input = '/';
|
||||
if ((!is_string($input) && !method_exists($input, '__toString')) || !trim($input)) {
|
||||
if ($fail_gracefully) {
|
||||
$input = '/';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Grav::instance()['config']->get('system.absolute_urls', false)) {
|
||||
$domain = true;
|
||||
}
|
||||
$input = (string)$input;
|
||||
|
||||
if (Grav::instance()['uri']->isExternal($input)) {
|
||||
if (Uri::isExternal($input)) {
|
||||
return $input;
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Uri $uri */
|
||||
$uri = Grav::instance()['uri'];
|
||||
$uri = $grav['uri'];
|
||||
|
||||
$root = $uri->rootUrl();
|
||||
$input = Utils::replaceFirstOccurrence($root, '', $input);
|
||||
|
||||
$input = ltrim((string)$input, '/');
|
||||
|
||||
if (Utils::contains((string)$input, '://')) {
|
||||
if (static::contains((string)$input, '://')) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$locator = $grav['locator'];
|
||||
|
||||
$parts = Uri::parseUrl($input);
|
||||
|
||||
if ($parts) {
|
||||
$resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false);
|
||||
if (is_array($parts)) {
|
||||
// Make sure we always have scheme, host, port and path.
|
||||
$scheme = $parts['scheme'] ?? '';
|
||||
$host = $parts['host'] ?? '';
|
||||
$port = $parts['port'] ?? '';
|
||||
$path = $parts['path'] ?? '';
|
||||
|
||||
if (isset($parts['query'])) {
|
||||
$resource = $resource . '?' . $parts['query'];
|
||||
if ($scheme && !$port) {
|
||||
// If URL has a scheme, we need to check if it's one of Grav streams.
|
||||
if (!$locator->schemeExists($scheme)) {
|
||||
// If scheme does not exists as a stream, assume it's external.
|
||||
return str_replace(' ', '%20', $input);
|
||||
}
|
||||
|
||||
// Attempt to find the resource (because of parse_url() we need to put host back to path).
|
||||
$resource = $locator->findResource("{$scheme}://{$host}{$path}", false);
|
||||
|
||||
if ($resource === false) {
|
||||
if (!$fail_gracefully) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return location where the file would be if it was saved.
|
||||
$resource = $locator->findResource("{$scheme}://{$host}{$path}", false, true);
|
||||
}
|
||||
|
||||
} elseif ($host || $port) {
|
||||
// If URL doesn't have scheme but has host or port, it is external.
|
||||
return str_replace(' ', '%20', $input);
|
||||
}
|
||||
|
||||
if (!empty($resource)) {
|
||||
// Add query string back.
|
||||
if (isset($parts['query'])) {
|
||||
$resource .= '?' . $parts['query'];
|
||||
}
|
||||
|
||||
// Add fragment back.
|
||||
if (isset($parts['fragment'])) {
|
||||
$resource .= '#' . $parts['fragment'];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Not a valid URL (can still be a stream).
|
||||
$resource = $locator->findResource($input, false);
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
$root = $uri->rootUrl();
|
||||
|
||||
if (static::startsWith($input, $root)) {
|
||||
$input = static::replaceFirstOccurrence($root, '', $input);
|
||||
}
|
||||
|
||||
$input = ltrim($input, '/');
|
||||
|
||||
$resource = $input;
|
||||
}
|
||||
|
||||
if (!$fail_gracefully && $resource === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$domain = $domain ?: $grav['config']->get('system.absolute_urls', false);
|
||||
|
||||
return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
|
||||
}
|
||||
@@ -274,6 +323,35 @@ abstract class Utils
|
||||
return (object)array_merge((array)$obj1, (array)$obj2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lowercase an entire array. Useful when combined with `in_array()`
|
||||
*
|
||||
* @param array $a
|
||||
* @return array|false
|
||||
*/
|
||||
public static function arrayLower(Array $a)
|
||||
{
|
||||
return array_map('mb_strtolower', $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple function to remove item/s in an array by value
|
||||
*
|
||||
* @param $search array
|
||||
* @param $value string|array
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayRemoveValue(Array $search, $value)
|
||||
{
|
||||
foreach ((array) $value as $val) {
|
||||
$key = array_search($val, $search);
|
||||
if ($key !== false) {
|
||||
unset($search[$key]);
|
||||
}
|
||||
}
|
||||
return $search;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive Merge with uniqueness
|
||||
*
|
||||
@@ -974,17 +1052,19 @@ abstract class Utils
|
||||
*
|
||||
* @param string $string The path
|
||||
*
|
||||
* @return bool
|
||||
* @return bool|string Either false or the language
|
||||
*
|
||||
*/
|
||||
public static function pathPrefixedByLangCode($string)
|
||||
{
|
||||
if (strlen($string) <= 3) {
|
||||
return false;
|
||||
$languages_enabled = Grav::instance()['config']->get('system.languages.supported', []);
|
||||
$parts = explode('/', trim($string, '/'));
|
||||
|
||||
if (count($parts) > 0 && in_array($parts[0], $languages_enabled)) {
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
$languages_enabled = Grav::instance()['config']->get('system.languages.supported', []);
|
||||
|
||||
return $string[0] === '/' && $string[3] === '/' && \in_array(substr($string, 1, 2), $languages_enabled, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1054,12 +1134,9 @@ abstract class Utils
|
||||
*/
|
||||
private static function generateNonceString($action, $previousTick = false)
|
||||
{
|
||||
$username = '';
|
||||
if (isset(Grav::instance()['user'])) {
|
||||
$user = Grav::instance()['user'];
|
||||
$username = $user->username;
|
||||
}
|
||||
$grav = Grav::instance();
|
||||
|
||||
$username = isset($grav['user']) ? $grav['user']->username : '';
|
||||
$token = session_id();
|
||||
$i = self::nonceTick();
|
||||
|
||||
@@ -1067,7 +1144,7 @@ abstract class Utils
|
||||
$i--;
|
||||
}
|
||||
|
||||
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . Grav::instance()['config']->get('security.salt'));
|
||||
return ($i . '|' . $action . '|' . $username . '|' . $token . '|' . $grav['config']->get('security.salt'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1281,7 +1358,7 @@ abstract class Utils
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's path based on a token
|
||||
* Get path based on a token
|
||||
*
|
||||
* @param string $path
|
||||
* @param PageInterface|null $page
|
||||
@@ -1341,6 +1418,8 @@ abstract class Utils
|
||||
$post_max_size = static::parseSize(ini_get('post_max_size'));
|
||||
if ($post_max_size > 0) {
|
||||
$max_size = $post_max_size;
|
||||
} else {
|
||||
$max_size = 0;
|
||||
}
|
||||
|
||||
$upload_max = static::parseSize(ini_get('upload_max_filesize'));
|
||||
@@ -1388,7 +1467,7 @@ abstract class Utils
|
||||
$pow = min($pow, count($units) - 1);
|
||||
|
||||
// Uncomment one of the following alternatives
|
||||
$bytes /= pow(1024, $pow);
|
||||
$bytes /= 1024 ** $pow;
|
||||
// $bytes /= (1 << (10 * $pow));
|
||||
|
||||
return round($bytes, $precision) . ' ' . $units[$pow];
|
||||
@@ -1404,11 +1483,12 @@ abstract class Utils
|
||||
{
|
||||
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
|
||||
$size = preg_replace('/[^0-9\.]/', '', $size);
|
||||
|
||||
if ($unit) {
|
||||
return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
|
||||
} else {
|
||||
return round($size);
|
||||
$size = $size * pow(1024, stripos('bkmgtpezy', $unit[0]));
|
||||
}
|
||||
|
||||
return (int) abs(round($size));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1446,19 +1526,28 @@ abstract class Utils
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @param bool $block Block or Line processing
|
||||
* @param bool $block Block or Line processing
|
||||
* @param null $page
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function processMarkdown($string, $block = true)
|
||||
public static function processMarkdown($string, $block = true, $page = null)
|
||||
{
|
||||
$page = Grav::instance()['page'] ?? null;
|
||||
$defaults = Grav::instance()['config']->get('system.pages.markdown');
|
||||
$grav = Grav::instance();
|
||||
$page = $page ?? $grav['page'] ?? null;
|
||||
$defaults = [
|
||||
'markdown' => $grav['config']->get('system.pages.markdown', []),
|
||||
'images' => $grav['config']->get('system.images', [])
|
||||
];
|
||||
$extra = $defaults['markdown']['extra'] ?? false;
|
||||
|
||||
$excerpts = new Excerpts($page, $defaults);
|
||||
|
||||
// Initialize the preferred variant of Parsedown
|
||||
if ($defaults['extra']) {
|
||||
$parsedown = new ParsedownExtra($page, $defaults);
|
||||
if ($extra) {
|
||||
$parsedown = new ParsedownExtra($excerpts);
|
||||
} else {
|
||||
$parsedown = new Parsedown($page, $defaults);
|
||||
$parsedown = new Parsedown($excerpts);
|
||||
}
|
||||
|
||||
if ($block) {
|
||||
@@ -1469,4 +1558,62 @@ abstract class Utils
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the subnet of an ip with CIDR prefix size
|
||||
*
|
||||
* @param string $ip
|
||||
* @param int $prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getSubnet($ip, $prefix = 64)
|
||||
{
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
return $ip;
|
||||
}
|
||||
|
||||
// Packed representation of IP
|
||||
$ip = inet_pton($ip);
|
||||
|
||||
// Maximum netmask length = same as packed address
|
||||
$len = 8*strlen($ip);
|
||||
if ($prefix > $len) $prefix = $len;
|
||||
|
||||
$mask = str_repeat('f', $prefix>>2);
|
||||
|
||||
switch($prefix & 3)
|
||||
{
|
||||
case 3: $mask .= 'e'; break;
|
||||
case 2: $mask .= 'c'; break;
|
||||
case 1: $mask .= '8'; break;
|
||||
}
|
||||
$mask = str_pad($mask, $len>>2, '0');
|
||||
|
||||
// Packed representation of netmask
|
||||
$mask = pack('H*', $mask);
|
||||
// Bitwise - Take all bits that are both 1 to generate subnet
|
||||
$subnet = inet_ntop($ip & $mask);
|
||||
|
||||
return $subnet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to ensure html, htm in the front of the supported page types
|
||||
*
|
||||
* @param array|null $defaults
|
||||
* @return array|mixed
|
||||
*/
|
||||
public static function getSupportPageTypes(array $defaults = null)
|
||||
{
|
||||
$types = Grav::instance()['config']->get('system.pages.types', $defaults);
|
||||
|
||||
// remove html/htm
|
||||
$types = static::arrayRemoveValue($types, ['html', 'htm']);
|
||||
|
||||
// put them back at the front
|
||||
$types = array_merge(['html', 'htm'], $types);
|
||||
|
||||
return $types;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,6 @@ class CleanCommand extends Command
|
||||
'vendor/doctrine/collections/composer.json',
|
||||
'vendor/doctrine/collections/phpunit.xml.dist',
|
||||
'vendor/doctrine/collections/tests',
|
||||
'vendor/dragonmantank/cron-expression/.editorconfig',
|
||||
'vendor/dragonmantank/cron-expression/composer.json',
|
||||
'vendor/dragonmantank/cron-expression/tests',
|
||||
'vendor/donatj/phpuseragentparser/.git',
|
||||
'vendor/donatj/phpuseragentparser/.gitignore',
|
||||
'vendor/donatj/phpuseragentparser/.editorconfig',
|
||||
@@ -83,6 +80,9 @@ class CleanCommand extends Command
|
||||
'vendor/donatj/phpuseragentparser/phpunit.xml.dist',
|
||||
'vendor/donatj/phpuseragentparser/Tests',
|
||||
'vendor/donatj/phpuseragentparser/Tools',
|
||||
'vendor/dragonmantank/cron-expression/.editorconfig',
|
||||
'vendor/dragonmantank/cron-expression/composer.json',
|
||||
'vendor/dragonmantank/cron-expression/tests',
|
||||
'vendor/erusev/parsedown/composer.json',
|
||||
'vendor/erusev/parsedown/phpunit.xml.dist',
|
||||
'vendor/erusev/parsedown/.travis.yml',
|
||||
@@ -123,6 +123,8 @@ class CleanCommand extends Command
|
||||
'vendor/gregwar/cache/Gregwar/Cache/demo',
|
||||
'vendor/gregwar/cache/Gregwar/Cache/tests',
|
||||
'vendor/guzzlehttp/psr7/composer.json',
|
||||
'vendor/guzzlehttp/psr7/.editorconfig',
|
||||
'vendor/kodus/psr7-server/composer.json',
|
||||
'vendor/ircmaxell/password-compat/composer.json',
|
||||
'vendor/ircmaxell/password-compat/phpunit.xml.dist',
|
||||
'vendor/ircmaxell/password-compat/version-test.php',
|
||||
@@ -158,7 +160,7 @@ class CleanCommand extends Command
|
||||
'vendor/monolog/monolog/.php_cs',
|
||||
'vendor/monolog/monolog/tests',
|
||||
'vendor/nyholm/psr7/composer.json',
|
||||
'vendor/nyholm/psr7-server/composer.json',
|
||||
'vendor/nyholm/psr7/phpstan.neon.dist',
|
||||
'vendor/phive/twig-extensions-deferred/.gitignore',
|
||||
'vendor/phive/twig-extensions-deferred/.travis.yml',
|
||||
'vendor/phive/twig-extensions-deferred/composer.json',
|
||||
@@ -184,6 +186,11 @@ class CleanCommand extends Command
|
||||
'vendor/psr/simple-cache/composer.json',
|
||||
'vendor/psr/log/composer.json',
|
||||
'vendor/psr/log/.gitignore',
|
||||
'vendor/ralouphie/getallheaders/.gitignore',
|
||||
'vendor/ralouphie/getallheaders/.travis.yml',
|
||||
'vendor/ralouphie/getallheaders/composer.json',
|
||||
'vendor/ralouphie/getallheaders/phpunit.xml',
|
||||
'vendor/ralouphie/getallheaders/tests/',
|
||||
'vendor/rockettheme/toolbox/.git',
|
||||
'vendor/rockettheme/toolbox/.gitignore',
|
||||
'vendor/rockettheme/toolbox/.scrutinizer.yml',
|
||||
@@ -209,12 +216,15 @@ class CleanCommand extends Command
|
||||
'vendor/symfony/event-dispatcher/composer.json',
|
||||
'vendor/symfony/event-dispatcher/phpunit.xml.dist',
|
||||
'vendor/symfony/event-dispatcher/Tests',
|
||||
'vendor/symfony/polyfill-ctype/composer.json',
|
||||
'vendor/symfony/polyfill-iconv/.git',
|
||||
'vendor/symfony/polyfill-iconv/.gitignore',
|
||||
'vendor/symfony/polyfill-iconv/composer.json',
|
||||
'vendor/symfony/polyfill-mbstring/.git',
|
||||
'vendor/symfony/polyfill-mbstring/.gitignore',
|
||||
'vendor/symfony/polyfill-mbstring/composer.json',
|
||||
'vendor/symfony/polyfill-php72/composer.json',
|
||||
'vendor/symfony/polyfill-php73/composer.json',
|
||||
'vendor/symfony/process/.gitignore',
|
||||
'vendor/symfony/process/composer.json',
|
||||
'vendor/symfony/process/phpunit.xml.dist',
|
||||
@@ -246,6 +256,8 @@ class CleanCommand extends Command
|
||||
'vendor/willdurand/negotiation/composer.json',
|
||||
'vendor/willdurand/negotiation/phpunit.xml.dist',
|
||||
'vendor/willdurand/negotiation/tests',
|
||||
'user/config/security.yaml',
|
||||
'cache/compiled/',
|
||||
];
|
||||
|
||||
protected function configure()
|
||||
|
||||
@@ -21,6 +21,7 @@ class ClearCacheCommand extends ConsoleCommand
|
||||
->setName('cache')
|
||||
->setAliases(['clearcache', 'cache-clear'])
|
||||
->setDescription('Clears Grav cache')
|
||||
->addOption('invalidate', null, InputOption::VALUE_NONE, 'Invalidate cache, but do not remove any files')
|
||||
->addOption('purge', null, InputOption::VALUE_NONE, 'If set purge old caches')
|
||||
->addOption('all', null, InputOption::VALUE_NONE, 'If set will remove all including compiled, twig, doctrine caches')
|
||||
->addOption('assets-only', null, InputOption::VALUE_NONE, 'If set will remove only assets/*')
|
||||
@@ -64,6 +65,8 @@ class ClearCacheCommand extends ConsoleCommand
|
||||
$remove = 'cache-only';
|
||||
} elseif ($this->input->getOption('tmp-only')) {
|
||||
$remove = 'tmp-only';
|
||||
} elseif ($this->input->getOption('invalidate')) {
|
||||
$remove = 'invalidate';
|
||||
} else {
|
||||
$remove = 'standard';
|
||||
}
|
||||
|
||||
82
system/src/Grav/Console/Cli/YamlLinterCommand.php
Normal file
82
system/src/Grav/Console/Cli/YamlLinterCommand.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?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\Style\SymfonyStyle;
|
||||
|
||||
class YamlLinterCommand extends ConsoleCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('yamllinter')
|
||||
->addOption(
|
||||
'env',
|
||||
'e',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com'
|
||||
)
|
||||
->setDescription('Checks various files for YAML errors')
|
||||
->setHelp("Checks various files for YAML errors");
|
||||
}
|
||||
|
||||
protected function serve()
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$grav->setup();
|
||||
|
||||
$io = new SymfonyStyle($this->input, $this->output);
|
||||
|
||||
$io->title('Yaml Linter');
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function displayErrors($errors, $io)
|
||||
{
|
||||
$io->error("YAML Linting issues found...");
|
||||
foreach ($errors as $path => $error) {
|
||||
$io->writeln("<yellow>$path</yellow> - $error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ class InfoCommand extends ConsoleCommand
|
||||
|
||||
if ($info === 'date') {
|
||||
$name = 'Last Update';
|
||||
$data = date('D, j M Y, H:i:s, P ', strtotime('2014-09-16T00:07:16Z'));
|
||||
$data = date('D, j M Y, H:i:s, P ', strtotime($data));
|
||||
}
|
||||
|
||||
$name = str_pad($name, 12);
|
||||
|
||||
@@ -372,7 +372,7 @@ class InstallCommand extends ConsoleCommand
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion('Do you wish to install this demo content? [y|N] ', false);
|
||||
|
||||
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
|
||||
$answer = $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln(" '- <red>Skipped!</red> ");
|
||||
|
||||
@@ -437,7 +437,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementes JsonSerializable interface.
|
||||
* Implements JsonSerializable interface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
@@ -84,7 +84,7 @@ class ArrayCollection extends BaseArrayCollection implements CollectionInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementes JsonSerializable interface.
|
||||
* Implements JsonSerializable interface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
@@ -53,11 +53,11 @@ class CsvFormatter extends AbstractFormatter
|
||||
$header = array_keys(reset($data));
|
||||
|
||||
// Encode the field names
|
||||
$string = implode($delimiter, $header). "\n";
|
||||
$string = $this->encodeLine($header, $delimiter);
|
||||
|
||||
// Encode the data
|
||||
foreach ($data as $row) {
|
||||
$string .= implode($delimiter, $row). "\n";
|
||||
$string .= $this->encodeLine($row, $delimiter);
|
||||
}
|
||||
|
||||
return $string;
|
||||
@@ -87,4 +87,23 @@ class CsvFormatter extends AbstractFormatter
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
protected function encodeLine(array $line, $delimiter = null): string
|
||||
{
|
||||
foreach ($line as $key => &$value) {
|
||||
$value = $this->escape((string)$value);
|
||||
}
|
||||
unset($value);
|
||||
|
||||
return implode($delimiter, $line). "\n";
|
||||
}
|
||||
|
||||
protected function escape(string $value)
|
||||
{
|
||||
if (preg_match('/[,"\r\n]/u', $value)) {
|
||||
$value = '"' . preg_replace('/"/', '""', $value) . '"';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,12 +97,12 @@ class YamlFormatter extends AbstractFormatter
|
||||
@ini_set('yaml.decode_php', $saved);
|
||||
|
||||
if ($decoded !== false) {
|
||||
return $decoded;
|
||||
return (array) $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return YamlParser::parse($data);
|
||||
return (array) YamlParser::parse($data);
|
||||
} catch (ParseException $e) {
|
||||
if ($this->useCompatibleDecoder()) {
|
||||
return (array) FallbackYamlParser::parse($data);
|
||||
|
||||
@@ -91,7 +91,7 @@ class Flex implements \Countable
|
||||
// Return the directories in the given order.
|
||||
$directories = [];
|
||||
foreach ($types as $type) {
|
||||
$directories = $this->types[$type] ?? null;
|
||||
$directories[$type] = $this->types[$type] ?? null;
|
||||
}
|
||||
|
||||
return $keepMissing ? $directories : array_filter($directories);
|
||||
@@ -168,6 +168,11 @@ class Flex implements \Countable
|
||||
$keyFieldFind = 'storage_key';
|
||||
|
||||
foreach ($keys as $flexKey) {
|
||||
if (!$flexKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$flexKey = (string)$flexKey;
|
||||
// Normalize key and type using fallback to default type if it was set.
|
||||
[$key, $type, $guess] = $this->resolveKeyAndType($flexKey, $defaultType);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace Grav\Framework\Flex;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Grav\Common\Debugger;
|
||||
use Grav\Common\Grav;
|
||||
@@ -123,6 +124,22 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
|
||||
return $matching;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $filters
|
||||
* @return FlexCollectionInterface|Collection
|
||||
*/
|
||||
public function filterBy(array $filters)
|
||||
{
|
||||
$expr = Criteria::expr();
|
||||
$criteria = Criteria::create();
|
||||
|
||||
foreach ($filters as $key => $value) {
|
||||
$criteria->andWhere($expr->eq($key, $value));
|
||||
}
|
||||
|
||||
return $this->matching($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @see FlexCollectionInterface::getFlexType()
|
||||
@@ -301,7 +318,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
|
||||
]));
|
||||
|
||||
$output = $this->getTemplate($layout)->render(
|
||||
['grav' => $grav, 'block' => $block, 'collection' => $this, 'layout' => $layout] + $context
|
||||
['grav' => $grav, 'config' => $grav['config'], 'block' => $block, 'collection' => $this, 'layout' => $layout] + $context
|
||||
);
|
||||
|
||||
if ($debugger->enabled()) {
|
||||
@@ -474,7 +491,12 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
|
||||
$twig = $grav['twig'];
|
||||
|
||||
try {
|
||||
return $twig->twig()->resolveTemplate(["flex-objects/layouts/{$this->getFlexType()}/collection/{$layout}.html.twig"]);
|
||||
return $twig->twig()->resolveTemplate(
|
||||
[
|
||||
"flex-objects/layouts/{$this->getFlexType()}/collection/{$layout}.html.twig",
|
||||
"flex-objects/layouts/_default/collection/{$layout}.html.twig"
|
||||
]
|
||||
);
|
||||
} catch (LoaderError $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
|
||||
@@ -315,15 +315,16 @@ class FlexDirectory implements FlexAuthorizeInterface
|
||||
$gravCache = $grav['cache'];
|
||||
$config = $this->getConfig('cache.' . $namespace);
|
||||
if (empty($config['enabled'])) {
|
||||
throw new \RuntimeException(sprintf('Flex: %s %s cache not enabled', $this->type, $namespace));
|
||||
}
|
||||
$timeout = $config['timeout'] ?? 60;
|
||||
$cache = new MemoryCache('flex-objects-' . $this->getFlexType());
|
||||
} else {
|
||||
$timeout = $config['timeout'] ?? 60;
|
||||
|
||||
$key = $gravCache->getKey();
|
||||
if (Utils::isAdminPlugin()) {
|
||||
$key = substr($key, 0, -1);
|
||||
$key = $gravCache->getKey();
|
||||
if (Utils::isAdminPlugin()) {
|
||||
$key = substr($key, 0, -1);
|
||||
}
|
||||
$cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getFlexType() . $key, $timeout);
|
||||
}
|
||||
$cache = new DoctrineCache($gravCache->getCacheDriver(), 'flex-objects-' . $this->getFlexType() . $key, $timeout);
|
||||
} catch (\Exception $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
@@ -519,7 +520,9 @@ class FlexDirectory implements FlexAuthorizeInterface
|
||||
// Store updated rows to the cache.
|
||||
if ($updated) {
|
||||
try {
|
||||
$debugger->addMessage(sprintf('Flex: Caching %d %s: %s', \count($updated), $this->type, implode(', ', array_keys($updated))), 'debug');
|
||||
if (!$cache instanceof MemoryCache) {
|
||||
$debugger->addMessage(sprintf('Flex: Caching %d %s: %s', \count($updated), $this->type, implode(', ', array_keys($updated))), 'debug');
|
||||
}
|
||||
$cache->setMultiple($updated);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$debugger->addException($e);
|
||||
@@ -640,7 +643,10 @@ class FlexDirectory implements FlexAuthorizeInterface
|
||||
/** @var string|FlexIndexInterface $className */
|
||||
$className = $this->getIndexClass();
|
||||
$keys = $className::loadEntriesFromStorage($storage);
|
||||
$debugger->addMessage(sprintf('Flex: Caching %s index of %d objects', $this->type, \count($keys)), 'debug');
|
||||
if (!$cache instanceof MemoryCache) {
|
||||
$debugger->addMessage(sprintf('Flex: Caching %s index of %d objects', $this->type, \count($keys)),
|
||||
'debug');
|
||||
}
|
||||
try {
|
||||
$cache->set('__keys', $keys);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
|
||||
@@ -12,11 +12,16 @@ namespace Grav\Framework\Flex;
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Form\Traits\FormTrait;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
/**
|
||||
* Class FlexForm
|
||||
@@ -45,11 +50,12 @@ class FlexForm implements FlexFormInterface
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->form = $form;
|
||||
|
||||
$uniqueId = $object->exists() ? $object->getStorageKey() : "{$object->getFlexType()}:new";
|
||||
$this->setObject($object);
|
||||
$this->setId($this->getName());
|
||||
$this->setUniqueId(md5($uniqueId));
|
||||
$this->errors = [];
|
||||
$this->messages = [];
|
||||
$this->submitted = false;
|
||||
|
||||
$flash = $this->getFlash();
|
||||
@@ -77,7 +83,7 @@ class FlexForm implements FlexFormInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data|FlexObjectInterface
|
||||
* @return Data|FlexObjectInterface|object
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
@@ -98,7 +104,7 @@ class FlexForm implements FlexFormInterface
|
||||
$value = $this->data ? $this->data[$name] : null;
|
||||
|
||||
// Return the form data or fall back to the object property.
|
||||
return $value ?? $this->getObject()->value($name);
|
||||
return $value ?? $this->getObject()->getFormValue($name);
|
||||
}
|
||||
|
||||
public function getDefaultValue(string $name)
|
||||
@@ -221,6 +227,42 @@ class FlexForm implements FlexFormInterface
|
||||
$this->doUnserialize($data);
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
$method = "get{$name}";
|
||||
if (method_exists($this, $method)) {
|
||||
return $this->{$method}();
|
||||
}
|
||||
|
||||
$form = $this->getBlueprint()->form();
|
||||
|
||||
return $form[$name] ?? null;
|
||||
}
|
||||
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$method = "set{$name}";
|
||||
if (method_exists($this, $method)) {
|
||||
$this->{$method}($value);
|
||||
}
|
||||
}
|
||||
|
||||
public function __isset($name)
|
||||
{
|
||||
$method = "get{$name}";
|
||||
if (method_exists($this, $method)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$form = $this->getBlueprint()->form();
|
||||
|
||||
return isset($form[$name]);
|
||||
}
|
||||
|
||||
public function __unset($name)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this method clones the object.
|
||||
*
|
||||
@@ -234,6 +276,29 @@ class FlexForm implements FlexFormInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $layout
|
||||
* @return Template|TemplateWrapper
|
||||
* @throws LoaderError
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
protected function getTemplate($layout)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Twig $twig */
|
||||
$twig = $grav['twig'];
|
||||
|
||||
return $twig->twig()->resolveTemplate(
|
||||
[
|
||||
"flex-objects/layouts/{$this->getFlexType()}/form/{$layout}.html.twig",
|
||||
"flex-objects/layouts/_default/form/{$layout}.html.twig",
|
||||
"forms/{$layout}/form.html.twig",
|
||||
'forms/default/form.html.twig'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
@@ -243,6 +308,7 @@ class FlexForm implements FlexFormInterface
|
||||
{
|
||||
/** @var FlexObject $object */
|
||||
$object = clone $this->getObject();
|
||||
|
||||
$object->update($data, $files);
|
||||
$object->save();
|
||||
|
||||
|
||||
@@ -98,6 +98,16 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
|
||||
return $this->orderBy($orderings);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @see FlexCollectionInterface::filterBy()
|
||||
*/
|
||||
public function filterBy(array $filters)
|
||||
{
|
||||
return $this->__call('filterBy', [$filters]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @see FlexCollectionInterface::getFlexType()
|
||||
@@ -302,9 +312,10 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
|
||||
// Ordering can be done by using index only.
|
||||
$previous = null;
|
||||
foreach (array_reverse($orderings) as $field => $ordering) {
|
||||
$field = (string)$field;
|
||||
if ($this->getKeyField() === $field) {
|
||||
$keys = $this->getKeys();
|
||||
$search = array_combine($keys, $keys);
|
||||
$search = array_combine($keys, $keys) ?: [];
|
||||
} elseif ($field === 'flex_key') {
|
||||
$search = $this->getFlexKeys();
|
||||
} else {
|
||||
@@ -386,10 +397,6 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
|
||||
$cached = $result;
|
||||
}
|
||||
|
||||
if ($cached === null) {
|
||||
throw new \RuntimeException('Flex: Internal error');
|
||||
}
|
||||
|
||||
$cache->set($key, $cached);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$debugger->addException($e);
|
||||
@@ -456,7 +463,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
|
||||
$first = reset($entries);
|
||||
if ($first) {
|
||||
$keys = array_keys($first);
|
||||
$keys = array_combine($keys, $keys);
|
||||
$keys = array_combine($keys, $keys) ?: [];
|
||||
} else {
|
||||
$keys = [];
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ use Psr\SimpleCache\InvalidArgumentException;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Template;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
/**
|
||||
@@ -162,6 +163,13 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
{
|
||||
$options = $options ?? $this->getFlexDirectory()->getConfig('data.search.options', []);
|
||||
$properties = $properties ?? $this->getFlexDirectory()->getConfig('data.search.fields', []);
|
||||
if (!$properties) {
|
||||
foreach ($this->getFlexDirectory()->getConfig('admin.list.fields', []) as $property => $value) {
|
||||
if (!empty($value['link'])) {
|
||||
$properties[] = $property;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$weight = 0;
|
||||
foreach ((array)$properties as $property) {
|
||||
@@ -273,7 +281,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
return (float)$options['ends_with'];
|
||||
}
|
||||
if ((!$tested || !empty($options['contains'])) && Utils::contains($value, $search, $options['case_sensitive'] ?? false)) {
|
||||
return (float)$options['contains'];
|
||||
return (float)($options['contains'] ?? 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -384,7 +392,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $cache ? $cache->get($key) : null;
|
||||
$data = $cache && $key ? $cache->get($key) : null;
|
||||
|
||||
$block = $data ? HtmlBlock::fromArray($data) : null;
|
||||
} catch (InvalidArgumentException $e) {
|
||||
@@ -403,7 +411,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
}
|
||||
|
||||
if (!$block) {
|
||||
$block = HtmlBlock::create($key);
|
||||
$block = HtmlBlock::create($key ?: null);
|
||||
$block->setChecksum($checksum);
|
||||
if ($key === false) {
|
||||
$block->disableCache();
|
||||
@@ -416,7 +424,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
]));
|
||||
|
||||
$output = $this->getTemplate($layout)->render(
|
||||
['grav' => $grav, 'block' => $block, 'object' => $this, 'layout' => $layout] + $context
|
||||
['grav' => $grav, 'config' => $grav['config'], 'block' => $block, 'object' => $this, 'layout' => $layout] + $context
|
||||
);
|
||||
|
||||
if ($debugger->enabled()) {
|
||||
@@ -427,7 +435,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
$block->setContent($output);
|
||||
|
||||
try {
|
||||
$cache && $block->isCached() && $cache->set($key, $block->toArray());
|
||||
$cache && $key && $block->isCached() && $cache->set($key, $block->toArray());
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$debugger->addException($e);
|
||||
}
|
||||
@@ -534,7 +542,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
$result = $this->getFlexDirectory()->getStorage()->replaceRows([$this->getStorageKey() => $this->prepareStorage()]);
|
||||
|
||||
$value = reset($result);
|
||||
$storageKey = key($result);
|
||||
$storageKey = (string)key($result);
|
||||
if ($value && $storageKey) {
|
||||
$this->setStorageKey($storageKey);
|
||||
if (!$this->hasKey()) {
|
||||
@@ -542,6 +550,15 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: For some reason locator caching isn't cleared for the file, investigate!
|
||||
$locator = Grav::instance()['locator'];
|
||||
$locator->clearCache();
|
||||
|
||||
// Make sure that the object exists before continuing (just in case).
|
||||
if (!$this->exists()) {
|
||||
throw new \RuntimeException('Saving failed: Object does not exist!');
|
||||
}
|
||||
|
||||
if (method_exists($this, 'saveUpdatedMedia')) {
|
||||
$this->saveUpdatedMedia();
|
||||
}
|
||||
@@ -551,7 +568,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
if (method_exists($this, 'clearMediaCache')) {
|
||||
$this->clearMediaCache();
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
} catch (\Exception $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
$debugger->addException($e);
|
||||
@@ -579,7 +596,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
if (method_exists($this, 'clearMediaCache')) {
|
||||
$this->clearMediaCache();
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
} catch (\Exception $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
$debugger->addException($e);
|
||||
@@ -729,9 +746,9 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
/** @var Flex $flex */
|
||||
$flex = $grav['flex_directory'];
|
||||
$directory = $flex->getDirectory($type);
|
||||
/** @var Flex|null $flex */
|
||||
$flex = $grav['flex_objects'] ?? null;
|
||||
$directory = $flex ? $flex->getDirectory($type) : null;
|
||||
if (!$directory) {
|
||||
throw new \InvalidArgumentException("Cannot unserialize '{$type}': Not found");
|
||||
}
|
||||
@@ -800,7 +817,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
|
||||
/**
|
||||
* @param string $layout
|
||||
* @return TemplateWrapper
|
||||
* @return Template|TemplateWrapper
|
||||
* @throws LoaderError
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
@@ -812,7 +829,12 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
$twig = $grav['twig'];
|
||||
|
||||
try {
|
||||
return $twig->twig()->resolveTemplate(["flex-objects/layouts/{$this->getFlexType()}/object/{$layout}.html.twig"]);
|
||||
return $twig->twig()->resolveTemplate(
|
||||
[
|
||||
"flex-objects/layouts/{$this->getFlexType()}/object/{$layout}.html.twig",
|
||||
"flex-objects/layouts/_default/object/{$layout}.html.twig"
|
||||
]
|
||||
);
|
||||
} catch (LoaderError $e) {
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = Grav::instance()['debugger'];
|
||||
|
||||
@@ -70,6 +70,14 @@ interface FlexCollectionInterface extends FlexCommonInterface, ObjectCollectionI
|
||||
*/
|
||||
public function sort(array $orderings);
|
||||
|
||||
/**
|
||||
* Filter collection by filter array with keys and values.
|
||||
*
|
||||
* @param array $filters
|
||||
* @return FlexCollectionInterface
|
||||
*/
|
||||
public function filterBy(array $filters);
|
||||
|
||||
/**
|
||||
* Get timestamps from all the objects in the collection.
|
||||
*
|
||||
|
||||
@@ -128,7 +128,7 @@ abstract class AbstractFilesystemStorage implements FlexStorageInterface
|
||||
return $path;
|
||||
}
|
||||
|
||||
return (string) $locator->findResource($path) ?: $locator->findResource($path, true, true);
|
||||
return (string)($locator->findResource($path) ?: $locator->findResource($path, true, true));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -455,6 +455,8 @@ class FolderStorage extends AbstractFilesystemStorage
|
||||
protected function initOptions(array $options): void
|
||||
{
|
||||
$extension = $this->dataFormatter->getDefaultFileExtension();
|
||||
|
||||
/** @var string $pattern */
|
||||
$pattern = !empty($options['pattern']) ? $options['pattern'] : $this->dataPattern;
|
||||
|
||||
$this->dataFolder = $options['folder'];
|
||||
|
||||
@@ -192,6 +192,9 @@ class SimpleStorage extends AbstractFilesystemStorage
|
||||
|
||||
$list = [];
|
||||
foreach ($rows as $key => $row) {
|
||||
if (strpos($key, '@@')) {
|
||||
$key = $this->getNewKey();
|
||||
}
|
||||
$this->data[$key] = $list[$key] = $row;
|
||||
}
|
||||
|
||||
@@ -224,7 +227,12 @@ class SimpleStorage extends AbstractFilesystemStorage
|
||||
$keys = array_keys($this->data);
|
||||
$keys[array_search($src, $keys, true)] = $dst;
|
||||
|
||||
$this->data = array_combine($keys, $this->data);
|
||||
$data = array_combine($keys, $this->data);
|
||||
if (false === $data) {
|
||||
throw new \LogicException('Bad data');
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Grav\Framework\Flex\Traits;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Framework\Flex\FlexDirectory;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
|
||||
/**
|
||||
@@ -25,10 +26,11 @@ trait FlexAuthorizeTrait
|
||||
public function isAuthorized(string $action, string $scope = null, UserInterface $user = null) : bool
|
||||
{
|
||||
if (null === $user) {
|
||||
$user = Grav::instance()['user'];
|
||||
/** @var UserInterface $user */
|
||||
$user = Grav::instance()['user'] ?? null;
|
||||
}
|
||||
|
||||
return $this->isAuthorizedAction($user, $action, $scope) || $this->isAuthorizedSuperAdmin($user);
|
||||
return $user && ($this->isAuthorizedAction($user, $action, $scope) || $this->isAuthorizedSuperAdmin($user));
|
||||
}
|
||||
|
||||
protected function isAuthorizedSuperAdmin(UserInterface $user): bool
|
||||
@@ -44,7 +46,11 @@ trait FlexAuthorizeTrait
|
||||
$action = $this->exists() ? 'update' : 'create';
|
||||
}
|
||||
|
||||
return $user->authorize(sprintf($this->_authorize, $scope, $action));
|
||||
$directory = $this instanceof FlexDirectory ? $this : $this->getFlexDirectory();
|
||||
$config = $directory->getConfig();
|
||||
$allowed = $config->get("{$scope}.actions.{$action}") ?? $config->get("actions.{$action}") ?? true;
|
||||
|
||||
return $allowed && $user->authorize(sprintf($this->_authorize, $scope, $action));
|
||||
}
|
||||
|
||||
protected function setAuthorizeRule(string $authorize) : void
|
||||
|
||||
@@ -49,7 +49,7 @@ trait FlexMediaTrait
|
||||
*/
|
||||
public function getStorageFolder()
|
||||
{
|
||||
return $this->getFlexDirectory()->getStorageFolder($this->getStorageKey());
|
||||
return $this->exists() ? $this->getFlexDirectory()->getStorageFolder($this->getStorageKey()) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +57,7 @@ trait FlexMediaTrait
|
||||
*/
|
||||
public function getMediaFolder()
|
||||
{
|
||||
return $this->getFlexDirectory()->getMediaFolder($this->getStorageKey());
|
||||
return $this->exists() ? $this->getFlexDirectory()->getMediaFolder($this->getStorageKey()) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,6 +153,12 @@ trait FlexMediaTrait
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
$path = $media->getPath();
|
||||
if (!$path) {
|
||||
$language = $grav['language'];
|
||||
|
||||
throw new RuntimeException($language->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE'), 400);
|
||||
}
|
||||
|
||||
if ($locator->isStream($path)) {
|
||||
$path = $locator->findResource($path, true, true);
|
||||
$locator->clearCache($path);
|
||||
@@ -202,12 +208,16 @@ trait FlexMediaTrait
|
||||
}
|
||||
|
||||
$media = $this->getMedia();
|
||||
$path = $media->getPath();
|
||||
if (!$path) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $grav['locator'];
|
||||
|
||||
$targetPath = $media->getPath() . '/' . $dirname;
|
||||
$targetFile = $media->getPath() . '/' . $filename;
|
||||
$targetPath = $path . '/' . $dirname;
|
||||
$targetFile = $path . '/' . $filename;
|
||||
if ($locator->isStream($targetFile)) {
|
||||
$targetPath = $locator->findResource($targetPath, true, true);
|
||||
$targetFile = $locator->findResource($targetFile, true, true);
|
||||
@@ -229,7 +239,6 @@ trait FlexMediaTrait
|
||||
}
|
||||
|
||||
// Remove Extra Files
|
||||
|
||||
foreach (scandir($targetPath, SCANDIR_SORT_NONE) as $file) {
|
||||
$preg_name = preg_quote($fileParts['filename'], '`');
|
||||
$preg_ext =preg_quote($fileParts['extension'], '`');
|
||||
|
||||
@@ -10,12 +10,18 @@
|
||||
namespace Grav\Framework\Form;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Form\Interfaces\FormFlashInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
class FormFlash implements \JsonSerializable
|
||||
class FormFlash implements FormFlashInterface
|
||||
{
|
||||
/** @var bool */
|
||||
protected $exists;
|
||||
/** @var string */
|
||||
protected $sessionId;
|
||||
/** @var string */
|
||||
@@ -26,6 +32,10 @@ class FormFlash implements \JsonSerializable
|
||||
protected $url;
|
||||
/** @var array */
|
||||
protected $user;
|
||||
/** @var int */
|
||||
protected $createdTimestamp;
|
||||
/** @var int */
|
||||
protected $updatedTimestamp;
|
||||
/** @var array */
|
||||
protected $data;
|
||||
/** @var array */
|
||||
@@ -34,29 +44,35 @@ class FormFlash implements \JsonSerializable
|
||||
protected $uploadedFiles;
|
||||
/** @var string[] */
|
||||
protected $uploadObjects;
|
||||
/** @var bool */
|
||||
protected $exists;
|
||||
/** @var string */
|
||||
protected $folder;
|
||||
|
||||
/**
|
||||
* @param string $sessionId
|
||||
* @return string
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getSessionTmpDir(string $sessionId): string
|
||||
public function __construct($config)
|
||||
{
|
||||
return "tmp://forms/{$sessionId}";
|
||||
}
|
||||
// Backwards compatibility with Grav 1.6 plugins.
|
||||
if (!is_array($config)) {
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '($sessionId, $uniqueId, $formName) is deprecated since Grav 1.6.11, use $config parameter instead', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* FormFlashObject constructor.
|
||||
* @param string $sessionId
|
||||
* @param string $uniqueId
|
||||
* @param string|null $formName
|
||||
*/
|
||||
public function __construct(string $sessionId, string $uniqueId, string $formName = null)
|
||||
{
|
||||
$this->sessionId = $sessionId;
|
||||
$this->uniqueId = $uniqueId;
|
||||
$args = func_get_args();
|
||||
$config = [
|
||||
'session_id' => $args[0],
|
||||
'unique_id' => $args[1] ?? null,
|
||||
'form_name' => $args[2] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
$this->sessionId = $config['session_id'] ?? 'no-session';
|
||||
$this->uniqueId = $config['unique_id'] ?? '';
|
||||
|
||||
$folder = $config['folder'] ?? ($this->sessionId ? 'tmp://forms/' . $this->sessionId : '');
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
$this->folder = $folder && $locator->isStream($folder) ? $locator->findResource($folder, true, true) : $folder;
|
||||
$file = $this->getTmpIndex();
|
||||
$this->exists = $file->exists();
|
||||
|
||||
@@ -66,65 +82,58 @@ class FormFlash implements \JsonSerializable
|
||||
} catch (\Exception $e) {
|
||||
$data = [];
|
||||
}
|
||||
$this->formName = null !== $formName ? $content['form'] ?? '' : '';
|
||||
$this->formName = $content['form'] ?? $config['form_name'] ?? '';
|
||||
$this->url = $data['url'] ?? '';
|
||||
$this->user = $data['user'] ?? null;
|
||||
$this->updatedTimestamp = $data['timestamps']['updated'] ?? time();
|
||||
$this->createdTimestamp = $data['timestamps']['created'] ?? $this->updatedTimestamp;
|
||||
$this->data = $data['data'] ?? null;
|
||||
$this->files = $data['files'] ?? [];
|
||||
} else {
|
||||
$this->formName = $formName;
|
||||
$this->formName = $config['form_name'] ?? '';
|
||||
$this->url = '';
|
||||
$this->createdTimestamp = $this->updatedTimestamp = time();
|
||||
$this->files = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSessionId(): string
|
||||
{
|
||||
return $this->sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getUniqueId(): string
|
||||
{
|
||||
return $this->uniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 1.6.11 Use '->getUniqueId()' method instead.
|
||||
*/
|
||||
public function getUniqieId(): string
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6.11, use ->getUniqueId() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getUniqueId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getFormName(): string
|
||||
{
|
||||
return $this->formName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqieId(): string
|
||||
{
|
||||
return $this->uniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function save(): self
|
||||
{
|
||||
$file = $this->getTmpIndex();
|
||||
$file->save($this->jsonSerialize());
|
||||
$this->exists = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete(): self
|
||||
{
|
||||
$this->removeTmpDir();
|
||||
$this->files = [];
|
||||
$this->exists = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getUrl(): string
|
||||
{
|
||||
@@ -132,18 +141,7 @@ class FormFlash implements \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
public function setUrl(string $url): self
|
||||
{
|
||||
$this->url = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getUsername(): string
|
||||
{
|
||||
@@ -151,7 +149,7 @@ class FormFlash implements \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getUserEmail(): string
|
||||
{
|
||||
@@ -159,40 +157,84 @@ class FormFlash implements \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $username
|
||||
* @return $this
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setUserName(string $username = null): self
|
||||
public function getCreatedTimestamp(): int
|
||||
{
|
||||
$this->user['username'] = $username;
|
||||
|
||||
return $this;
|
||||
return $this->createdTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $email
|
||||
* @return $this
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setUserEmail(string $email = null): self
|
||||
public function getUpdatedTimestamp(): int
|
||||
{
|
||||
$this->user['email'] = $email;
|
||||
|
||||
return $this;
|
||||
return $this->updatedTimestamp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getData(): ?array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setData(?array $data): void
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @return array
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function save(): self
|
||||
{
|
||||
if (!($this->folder && $this->uniqueId)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->data || $this->files) {
|
||||
// Only save if there is data or files to be saved.
|
||||
$file = $this->getTmpIndex();
|
||||
$file->save($this->jsonSerialize());
|
||||
$this->exists = true;
|
||||
} elseif ($this->exists) {
|
||||
// Delete empty form flash if it exists (it carries no information).
|
||||
return $this->delete();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function delete(): self
|
||||
{
|
||||
if ($this->folder && $this->uniqueId) {
|
||||
$this->removeTmpDir();
|
||||
$this->files = [];
|
||||
$this->exists = false;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getFilesByField(string $field): array
|
||||
{
|
||||
@@ -208,8 +250,7 @@ class FormFlash implements \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $includeOriginal
|
||||
* @return array
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getFilesByFields($includeOriginal = false): array
|
||||
{
|
||||
@@ -225,12 +266,7 @@ class FormFlash implements \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* Add uploaded file to the form flash.
|
||||
*
|
||||
* @param UploadedFileInterface $upload
|
||||
* @param string|null $field
|
||||
* @param array|null $crop
|
||||
* @return string Return name of the file
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addUploadedFile(UploadedFileInterface $upload, string $field = null, array $crop = null): string
|
||||
{
|
||||
@@ -256,12 +292,7 @@ class FormFlash implements \JsonSerializable
|
||||
|
||||
|
||||
/**
|
||||
* Add existing file to the form flash.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $field
|
||||
* @param array $crop
|
||||
* @return bool
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addFile(string $filename, string $field, array $crop = null): bool
|
||||
{
|
||||
@@ -282,11 +313,7 @@ class FormFlash implements \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any file from form flash.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @return bool
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function removeFile(string $name, string $field = null): bool
|
||||
{
|
||||
@@ -318,7 +345,7 @@ class FormFlash implements \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear form flash from all uploaded files.
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function clearFiles()
|
||||
{
|
||||
@@ -332,7 +359,7 @@ class FormFlash implements \JsonSerializable
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
@@ -341,17 +368,72 @@ class FormFlash implements \JsonSerializable
|
||||
'unique_id' => $this->uniqueId,
|
||||
'url' => $this->url,
|
||||
'user' => $this->user,
|
||||
'timestamps' => [
|
||||
'created' => $this->createdTimestamp,
|
||||
'updated' => time(),
|
||||
],
|
||||
'data' => $this->data,
|
||||
'files' => $this->files
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
public function setUrl(string $url): self
|
||||
{
|
||||
$this->url = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserInterface|null $user
|
||||
* @return $this
|
||||
*/
|
||||
public function setUser(UserInterface $user = null)
|
||||
{
|
||||
if ($user && $user->username) {
|
||||
$this->user = [
|
||||
'username' => $user->username,
|
||||
'email' => $user->email ?? ''
|
||||
];
|
||||
} else {
|
||||
$this->user = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $username
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserName(string $username = null): self
|
||||
{
|
||||
$this->user['username'] = $username;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $email
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserEmail(string $email = null): self
|
||||
{
|
||||
$this->user['email'] = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTmpDir(): string
|
||||
{
|
||||
return static::getSessionTmpDir($this->sessionId) . '/' . $this->uniqueId;
|
||||
return $this->folder && $this->uniqueId ? "{$this->folder}/{$this->uniqueId}" : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,8 +450,9 @@ class FormFlash implements \JsonSerializable
|
||||
*/
|
||||
protected function removeTmpFile(string $name): void
|
||||
{
|
||||
$filename = $this->getTmpDir() . '/' . $name;
|
||||
if ($name && is_file($filename)) {
|
||||
$tmpDir = $this->getTmpDir();
|
||||
$filename = $tmpDir ? $tmpDir . '/' . $name : '';
|
||||
if ($name && $filename && is_file($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
@@ -377,7 +460,7 @@ class FormFlash implements \JsonSerializable
|
||||
protected function removeTmpDir(): void
|
||||
{
|
||||
$tmpDir = $this->getTmpDir();
|
||||
if (file_exists($tmpDir)) {
|
||||
if ($tmpDir && file_exists($tmpDir)) {
|
||||
Folder::delete($tmpDir);
|
||||
}
|
||||
}
|
||||
@@ -390,6 +473,10 @@ class FormFlash implements \JsonSerializable
|
||||
*/
|
||||
protected function addFileInternal(?string $field, string $name, array $data, array $crop = null): void
|
||||
{
|
||||
if (!($this->folder && $this->uniqueId)) {
|
||||
throw new \RuntimeException('Cannot upload files: form flash folder not defined');
|
||||
}
|
||||
|
||||
$field = $field ?: 'undefined';
|
||||
if (!isset($this->files[$field])) {
|
||||
$this->files[$field] = [];
|
||||
|
||||
165
system/src/Grav/Framework/Form/Interfaces/FormFlashInterface.php
Normal file
165
system/src/Grav/Framework/Form/Interfaces/FormFlashInterface.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Form
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Form\Interfaces;
|
||||
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
interface FormFlashInterface extends \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @param array $config Available configuration keys: session_id, unique_id, form_name
|
||||
*/
|
||||
public function __construct($config);
|
||||
|
||||
/**
|
||||
* Get session Id associated to this form instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionId(): string;
|
||||
|
||||
/**
|
||||
* Get unique identifier associated to this form instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueId(): string;
|
||||
|
||||
/**
|
||||
* Get form name associated to this form instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFormName(): string;
|
||||
|
||||
/**
|
||||
* Get URL associated to this form instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl(): string;
|
||||
|
||||
/**
|
||||
* Get username from the user who was associated to this form instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername(): string;
|
||||
|
||||
/**
|
||||
* Get email from the user who was associated to this form instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(): string;
|
||||
|
||||
|
||||
/**
|
||||
* Get creation timestamp for this form flash.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCreatedTimestamp(): int;
|
||||
|
||||
/**
|
||||
* Get last updated timestamp for this form flash.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getUpdatedTimestamp(): int;
|
||||
|
||||
/**
|
||||
* Get raw form data.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getData(): ?array;
|
||||
|
||||
/**
|
||||
* Set raw form data.
|
||||
*
|
||||
* @param array|null $data
|
||||
*/
|
||||
public function setData(?array $data): void;
|
||||
|
||||
/**
|
||||
* Check if this form flash exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(): bool;
|
||||
|
||||
/**
|
||||
* Save this form flash.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function save();
|
||||
|
||||
/**
|
||||
* Delete this form flash.
|
||||
*/
|
||||
public function delete();
|
||||
|
||||
/**
|
||||
* Get all files associated to a form field.
|
||||
*
|
||||
* @param string $field
|
||||
* @return array
|
||||
*/
|
||||
public function getFilesByField(string $field): array;
|
||||
|
||||
/**
|
||||
* Get all files grouped by the associated form fields.
|
||||
*
|
||||
* @param bool $includeOriginal
|
||||
* @return array
|
||||
*/
|
||||
public function getFilesByFields($includeOriginal = false): array;
|
||||
|
||||
/**
|
||||
* Add uploaded file to the form flash.
|
||||
*
|
||||
* @param UploadedFileInterface $upload
|
||||
* @param string|null $field
|
||||
* @param array|null $crop
|
||||
* @return string Return name of the file
|
||||
*/
|
||||
public function addUploadedFile(UploadedFileInterface $upload, string $field = null, array $crop = null): string;
|
||||
|
||||
/**
|
||||
* Add existing file to the form flash.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $field
|
||||
* @param array $crop
|
||||
* @return bool
|
||||
*/
|
||||
public function addFile(string $filename, string $field, array $crop = null): bool;
|
||||
|
||||
/**
|
||||
* Remove any file from form flash.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $field
|
||||
* @return bool
|
||||
*/
|
||||
public function removeFile(string $name, string $field = null): bool;
|
||||
|
||||
/**
|
||||
* Clear form flash from all uploaded files.
|
||||
*/
|
||||
public function clearFiles();
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize(): array;
|
||||
}
|
||||
@@ -11,6 +11,7 @@ namespace Grav\Framework\Form\Interfaces;
|
||||
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Framework\Interfaces\RenderInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
@@ -18,7 +19,7 @@ use Psr\Http\Message\UploadedFileInterface;
|
||||
* Interface FormInterface
|
||||
* @package Grav\Framework\Form
|
||||
*/
|
||||
interface FormInterface extends \Serializable
|
||||
interface FormInterface extends RenderInterface, \Serializable
|
||||
{
|
||||
/**
|
||||
* Get HTML id="..." attribute.
|
||||
@@ -83,6 +84,13 @@ interface FormInterface extends \Serializable
|
||||
*/
|
||||
public function getNonce(): string;
|
||||
|
||||
/**
|
||||
* Get task for the form if set in blueprints.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTask(): string;
|
||||
|
||||
/**
|
||||
* Get form action (URL). If action is empty, it points to the current page.
|
||||
*
|
||||
@@ -132,6 +140,11 @@ interface FormInterface extends \Serializable
|
||||
*/
|
||||
public function isValid(): bool;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getError(): ?string;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
|
||||
@@ -14,10 +14,17 @@ use Grav\Common\Data\Data;
|
||||
use Grav\Common\Data\ValidationException;
|
||||
use Grav\Common\Form\FormFlash;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Common\User\Interfaces\UserInterface;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\ContentBlock\HtmlBlock;
|
||||
use Grav\Framework\Form\Interfaces\FormInterface;
|
||||
use Grav\Framework\Session\SessionInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\TemplateWrapper;
|
||||
|
||||
/**
|
||||
* Trait FormTrait
|
||||
@@ -25,6 +32,13 @@ use Psr\Http\Message\UploadedFileInterface;
|
||||
*/
|
||||
trait FormTrait
|
||||
{
|
||||
/** @var string */
|
||||
public $status = 'success';
|
||||
/** @var string */
|
||||
public $message;
|
||||
/** @var string[] */
|
||||
public $messages = [];
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
/** @var string */
|
||||
@@ -33,8 +47,6 @@ trait FormTrait
|
||||
private $uniqueid;
|
||||
/** @var bool */
|
||||
private $submitted;
|
||||
/** @var string[] */
|
||||
private $errors;
|
||||
/** @var Data|object|null */
|
||||
private $data;
|
||||
/** @var array|UploadedFileInterface[] */
|
||||
@@ -94,6 +106,11 @@ trait FormTrait
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getTask(): string
|
||||
{
|
||||
return $this->getBlueprint()->get('form/task') ?? '';
|
||||
}
|
||||
|
||||
public function getData(string $name = null)
|
||||
{
|
||||
return null !== $name ? $this->data[$name] : $this->data;
|
||||
@@ -154,12 +171,24 @@ trait FormTrait
|
||||
*/
|
||||
public function handleRequest(ServerRequestInterface $request): FormInterface
|
||||
{
|
||||
// Set current form to be active.
|
||||
$grav = Grav::instance();
|
||||
$forms = $grav['forms'] ?? null;
|
||||
if ($forms) {
|
||||
$forms->setActiveForm($this);
|
||||
|
||||
/** @var Twig $twig */
|
||||
$twig = $grav['twig'];
|
||||
$twig->twig_vars['form'] = $this;
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
[$data, $files] = $this->parseRequest($request);
|
||||
|
||||
$this->submit($data, $files);
|
||||
} catch (\Exception $e) {
|
||||
$this->errors[] = $e->getMessage();
|
||||
$this->setError($e->getMessage());
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -181,12 +210,17 @@ trait FormTrait
|
||||
|
||||
public function isValid(): bool
|
||||
{
|
||||
return !$this->errors;
|
||||
return $this->status === 'success';
|
||||
}
|
||||
|
||||
public function getError(): ?string
|
||||
{
|
||||
return !$this->isValid() ? $this->message : null;
|
||||
}
|
||||
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->errors;
|
||||
return !$this->isValid() ? $this->messages : [];
|
||||
}
|
||||
|
||||
public function isSubmitted(): bool
|
||||
@@ -196,7 +230,7 @@ trait FormTrait
|
||||
|
||||
public function validate(): bool
|
||||
{
|
||||
if ($this->errors) {
|
||||
if (!$this->isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -204,19 +238,14 @@ trait FormTrait
|
||||
$this->validateData($this->data);
|
||||
$this->validateUploads($this->getFiles());
|
||||
} catch (ValidationException $e) {
|
||||
$list = [];
|
||||
foreach ($e->getMessages() as $field => $errors) {
|
||||
$list[] = $errors;
|
||||
}
|
||||
$list = array_merge(...$list);
|
||||
$this->errors = $list;
|
||||
$this->setErrors($e->getMessages());
|
||||
} catch (\Exception $e) {
|
||||
$this->errors[] = $e->getMessage();
|
||||
$this->setError($e->getMessage());
|
||||
}
|
||||
|
||||
$this->filterData($this->data);
|
||||
|
||||
return empty($this->errors);
|
||||
return $this->isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,7 +271,7 @@ trait FormTrait
|
||||
|
||||
$this->submitted = true;
|
||||
} catch (\Exception $e) {
|
||||
$this->errors[] = $e->getMessage();
|
||||
$this->setError($e->getMessage());
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -255,7 +284,9 @@ trait FormTrait
|
||||
|
||||
$this->data = null;
|
||||
$this->files = [];
|
||||
$this->errors = [];
|
||||
$this->status = 'success';
|
||||
$this->message = null;
|
||||
$this->messages = [];
|
||||
$this->submitted = false;
|
||||
$this->flash = null;
|
||||
}
|
||||
@@ -267,12 +298,12 @@ trait FormTrait
|
||||
|
||||
public function getButtons(): array
|
||||
{
|
||||
return $this->getBlueprint()['form']['buttons'] ?? [];
|
||||
return $this->getBlueprint()->get('form/buttons') ?? [];
|
||||
}
|
||||
|
||||
public function getTasks(): array
|
||||
{
|
||||
return $this->getBlueprint()['form']['tasks'] ?? [];
|
||||
return $this->getBlueprint()->get('form/tasks') ?? [];
|
||||
}
|
||||
|
||||
abstract public function getBlueprint(): Blueprint;
|
||||
@@ -300,7 +331,6 @@ trait FormTrait
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* Get form flash object.
|
||||
*
|
||||
* @return FormFlash
|
||||
@@ -308,48 +338,123 @@ trait FormTrait
|
||||
public function getFlash(): FormFlash
|
||||
{
|
||||
if (null === $this->flash) {
|
||||
/** @var Grav $grav */
|
||||
$grav = Grav::instance();
|
||||
$id = null;
|
||||
|
||||
$user = $grav['user'] ?? null;
|
||||
if (isset($user)) {
|
||||
$rememberState = $this->getBlueprint()->get('form/remember_state');
|
||||
if ($rememberState === 'user') {
|
||||
$id = $user->username;
|
||||
}
|
||||
}
|
||||
|
||||
// Session Required for flash form
|
||||
$session = $grav['session'] ?? null;
|
||||
if (isset($session)) {
|
||||
// By default store flash by the session id.
|
||||
if (null === $id) {
|
||||
$id = $session->getId();
|
||||
}
|
||||
$config = [
|
||||
'session_id' => $this->getSessionId(),
|
||||
'unique_id' => $this->getUniqueId(),
|
||||
'form_name' => $this->getName(),
|
||||
'folder' => $this->getFlashFolder()
|
||||
];
|
||||
|
||||
|
||||
$this->flash = new FormFlash($id, $this->getUniqueId(), $this->getName());
|
||||
$this->flash->setUrl($grav['uri']->url)->setUser($user);
|
||||
}
|
||||
$this->flash = new FormFlash($config);
|
||||
$this->flash->setUrl($grav['uri']->url)->setUser($grav['user'] ?? null);
|
||||
}
|
||||
|
||||
return $this->flash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available form flash objects for this form.
|
||||
*
|
||||
* @return FormFlash[]
|
||||
*/
|
||||
public function getAllFlashes(): array
|
||||
{
|
||||
$folder = $this->getFlashFolder();
|
||||
if (!$folder || !is_dir($folder)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$name = $this->getName();
|
||||
|
||||
$list = [];
|
||||
/** @var \SplFileInfo $file */
|
||||
foreach (new \FilesystemIterator($folder) as $file) {
|
||||
$uniqueId = $file->getFilename();
|
||||
$config = [
|
||||
'session_id' => $this->getSessionId(),
|
||||
'unique_id' => $uniqueId,
|
||||
'form_name' => $name,
|
||||
'folder' => $this->getFlashFolder()
|
||||
];
|
||||
$flash = new FormFlash($config);
|
||||
if ($flash->exists() && $flash->getFormName() === $name) {
|
||||
$list[] = $flash;
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @see FormInterface::render()
|
||||
*/
|
||||
public function render(string $layout = null, array $context = [])
|
||||
{
|
||||
if (null === $layout) {
|
||||
$layout = 'default';
|
||||
}
|
||||
|
||||
$grav = Grav::instance();
|
||||
|
||||
$block = HtmlBlock::create();
|
||||
$block->disableCache();
|
||||
|
||||
$output = $this->getTemplate($layout)->render(
|
||||
['grav' => $grav, 'config' => $grav['config'], 'block' => $block, 'form' => $this, 'layout' => $layout] + $context
|
||||
);
|
||||
|
||||
$block->setContent($output);
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
protected function getSessionId(): string
|
||||
{
|
||||
/** @var Grav $grav */
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var SessionInterface $session */
|
||||
$session = $grav['session'] ?? null;
|
||||
|
||||
return $session ? ($session->getId() ?? '') : '';
|
||||
}
|
||||
|
||||
protected function unsetFlash(): void
|
||||
{
|
||||
$this->flash = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all errors.
|
||||
*
|
||||
* @param array $errors
|
||||
*/
|
||||
protected function setErrors(array $errors): void
|
||||
protected function getFlashFolder(): ?string
|
||||
{
|
||||
$this->errors = array_merge($this->errors, $errors);
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var UserInterface $user */
|
||||
$user = $grav['user'] ?? null;
|
||||
$userExists = $user && $user->exists();
|
||||
$username = $userExists ? $user->username : null;
|
||||
$mediaFolder = $userExists ? $user->getMediaFolder() : null;
|
||||
$session = $grav['session'] ?? null;
|
||||
$sessionId = $session ? $session->getId() : null;
|
||||
|
||||
// Fill template token keys/value pairs.
|
||||
$dataMap = [
|
||||
'[FORM_NAME]' => $this->getName(),
|
||||
'[SESSIONID]' => $sessionId ?? '!!',
|
||||
'[USERNAME]' => $username ?? '!!',
|
||||
'[USERNAME_OR_SESSIONID]' => $username ?? $sessionId ?? '!!',
|
||||
'[ACCOUNT]' => $mediaFolder ?? '!!'
|
||||
];
|
||||
|
||||
$flashFolder = $this->getBlueprint()->get('form/flash_folder', 'tmp://forms/[SESSIONID]');
|
||||
|
||||
$path = str_replace(array_keys($dataMap), array_values($dataMap), $flashFolder);
|
||||
|
||||
// Make sure we only return valid paths.
|
||||
return strpos($path, '!!') === false ? rtrim($path, '/') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -359,7 +464,40 @@ trait FormTrait
|
||||
*/
|
||||
protected function setError(string $error): void
|
||||
{
|
||||
$this->errors[] = $error;
|
||||
$this->status = 'error';
|
||||
$this->message = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all errors.
|
||||
*
|
||||
* @param array $errors
|
||||
*/
|
||||
protected function setErrors(array $errors): void
|
||||
{
|
||||
$this->status = 'error';
|
||||
$this->messages = $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $layout
|
||||
* @return TemplateWrapper
|
||||
* @throws LoaderError
|
||||
* @throws SyntaxError
|
||||
*/
|
||||
protected function getTemplate($layout)
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Twig $twig */
|
||||
$twig = $grav['twig'];
|
||||
|
||||
return $twig->twig()->resolveTemplate(
|
||||
[
|
||||
"forms/{$layout}/form.html.twig",
|
||||
'forms/default/form.html.twig'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,7 +646,7 @@ trait FormTrait
|
||||
$value = json_decode($value, true);
|
||||
if ($value === null && json_last_error() !== JSON_ERROR_NONE) {
|
||||
unset($data[$key]);
|
||||
$this->errors[] = "Badly encoded JSON data (for {$key}) was sent to the form";
|
||||
$this->setError("Badly encoded JSON data (for {$key}) was sent to the form");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,7 +666,9 @@ trait FormTrait
|
||||
'id' => $this->id,
|
||||
'uniqueid' => $this->uniqueid,
|
||||
'submitted' => $this->submitted,
|
||||
'errors' => $this->errors,
|
||||
'status' => $this->status,
|
||||
'message' => $this->message,
|
||||
'messages' => $this->messages,
|
||||
'data' => $data,
|
||||
'files' => $this->files,
|
||||
];
|
||||
@@ -543,7 +683,9 @@ trait FormTrait
|
||||
$this->id = $data['id'];
|
||||
$this->uniqueid = $data['uniqueid'];
|
||||
$this->submitted = $data['submitted'] ?? false;
|
||||
$this->errors = $data['errors'] ?? [];
|
||||
$this->status = $data['status'] ?? 'success';
|
||||
$this->message = $data['message'] ?? null;
|
||||
$this->messages = $data['messages'] ?? [];
|
||||
$this->data = isset($data['data']) ? new Data($data['data'], $this->getBlueprint()) : null;
|
||||
$this->files = $data['files'] ?? [];
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ interface RenderInterface
|
||||
* @example {% render object layout 'custom' with { variable: 'value' } %}
|
||||
*
|
||||
* @param string|null $layout Layout to be used.
|
||||
* @param array|null $context Extra context given to the renderer.
|
||||
* @param array $context Extra context given to the renderer.
|
||||
*
|
||||
* @return ContentBlockInterface|HtmlBlock Returns `HtmlBlock` containing the rendered output.
|
||||
* @api
|
||||
|
||||
@@ -103,7 +103,7 @@ trait NestedPropertyTrait
|
||||
$current[$offset] = [];
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException('Cannot set nested property on non-array value');
|
||||
throw new \RuntimeException("Cannot set nested property {$property} on non-array value");
|
||||
}
|
||||
|
||||
$current = &$current[$offset];
|
||||
@@ -147,7 +147,7 @@ trait NestedPropertyTrait
|
||||
return $this;
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException('Cannot set nested property on non-array value');
|
||||
throw new \RuntimeException("Cannot unset nested property {$property} on non-array value");
|
||||
}
|
||||
|
||||
$current = &$current[$offset];
|
||||
|
||||
@@ -154,16 +154,11 @@ class Route
|
||||
* If the parameter exists in both, return Grav parameter.
|
||||
*
|
||||
* @param string $param
|
||||
* @return string|null
|
||||
* @return string|array|null
|
||||
*/
|
||||
public function getParam($param)
|
||||
{
|
||||
$value = $this->getGravParam($param);
|
||||
if ($value === null) {
|
||||
$value = $this->getQueryParam($param);
|
||||
}
|
||||
|
||||
return $value;
|
||||
return $this->getGravParam($param) ?? $this->getQueryParam($param);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,7 +172,7 @@ class Route
|
||||
|
||||
/**
|
||||
* @param string $param
|
||||
* @return string|null
|
||||
* @return string|array|null
|
||||
*/
|
||||
public function getQueryParam($param)
|
||||
{
|
||||
@@ -193,6 +188,7 @@ class Route
|
||||
public function withRoute($route)
|
||||
{
|
||||
$this->route = $route;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -205,6 +201,7 @@ class Route
|
||||
public function withRoot($root)
|
||||
{
|
||||
$this->root = $root;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -250,6 +247,25 @@ class Route
|
||||
return $this->withParam('queryParams', $param, $value);
|
||||
}
|
||||
|
||||
public function withoutParams()
|
||||
{
|
||||
return $this->withoutGravParams()->withoutQueryParams();
|
||||
}
|
||||
|
||||
public function withoutGravParams()
|
||||
{
|
||||
$this->gravParams = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withoutQueryParams()
|
||||
{
|
||||
$this->queryParams = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Grav\Framework\Uri\Uri
|
||||
*/
|
||||
@@ -292,22 +308,30 @@ class Route
|
||||
*/
|
||||
protected function withParam($type, $param, $value)
|
||||
{
|
||||
$oldValue = $this->{$type}[$param] ?? null;
|
||||
$values = $this->{$type} ?? [];
|
||||
$oldValue = $values[$param] ?? null;
|
||||
|
||||
if ($oldValue === $value) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new = $this->copy();
|
||||
if ($value === null) {
|
||||
unset($new->{$type}[$param]);
|
||||
unset($values[$param]);
|
||||
} else {
|
||||
$new->{$type}[$param] = $value;
|
||||
$values[$param] = $value;
|
||||
}
|
||||
|
||||
$new->{$type} = $values;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
protected function copy()
|
||||
{
|
||||
return clone $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $includeRoot
|
||||
* @return string
|
||||
@@ -350,8 +374,8 @@ class Route
|
||||
$this->language = $gravParts['language'];
|
||||
$this->route = $gravParts['route'];
|
||||
$this->extension = $gravParts['extension'] ?? '';
|
||||
$this->gravParams = $gravParts['params'];
|
||||
$this->queryParams = $parts['query_params'];
|
||||
$this->gravParams = $gravParts['params'] ?: [];
|
||||
$this->queryParams = $parts['query_params'] ?: [];
|
||||
|
||||
} else {
|
||||
$this->root = RouteFactory::getRoot();
|
||||
|
||||
@@ -84,11 +84,11 @@ class UriPartsFilter
|
||||
*/
|
||||
public static function filterPort($port = null)
|
||||
{
|
||||
if (null === $port || (\is_int($port) && ($port >= 1 && $port <= 65535))) {
|
||||
if (null === $port || (\is_int($port) && ($port >= 0 && $port <= 65535))) {
|
||||
return $port;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Uri port must be null or an integer between 1 and 65535');
|
||||
throw new \InvalidArgumentException('Uri port must be null or an integer between 0 and 65535');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,14 +41,15 @@ parameters:
|
||||
message: '#Grav\\Common\\GPM\\Remote\\GravCore::__construct\(\) does not call parent constructor from Grav\\Common\\GPM\\Remote\\AbstractPackageCollection#'
|
||||
path: 'system/src/Grav/Common/GPM/Remote/GravCore.php'
|
||||
|
||||
# PSR-16 Exception interfaces do not extend \Throwable
|
||||
- '#PHPDoc tag \@throws with type Psr\\SimpleCache\\(CacheException|InvalidArgumentException) is not subtype of Throwable#'
|
||||
- '#expects Exception, Psr\\SimpleCache\\InvalidArgumentException&Throwable given#'
|
||||
|
||||
# Needed: psr-17 (http-factories) support (through decorator or further investigations)
|
||||
-
|
||||
message: '#Call to an undefined static method Grav\\Framework\\Psr7\\Stream::create\(\)#'
|
||||
path: 'system/src/Grav/Framework/Form/FormFlashFile.php'
|
||||
|
||||
# PSR-16 Exception interfaces do not extend \Throwable
|
||||
- '#PHPDoc tag \@throws with type Psr\\SimpleCache\\(CacheException|InvalidArgumentException) is not subtype of Throwable#'
|
||||
|
||||
# Medium __call() methods
|
||||
- '#Call to an undefined method Grav\\Common\\Page\\Medium\\(\w*)Medium::#'
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use Codeception\Util\Fixtures;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Markdown\Excerpts;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Page\Pages;
|
||||
@@ -56,14 +57,19 @@ class ParsedownTest extends \Codeception\TestCase\Test
|
||||
$this->pages->init();
|
||||
|
||||
$defaults = [
|
||||
'extra' => false,
|
||||
'auto_line_breaks' => false,
|
||||
'auto_url_links' => false,
|
||||
'escape_markup' => false,
|
||||
'special_chars' => ['>' => 'gt', '<' => 'lt'],
|
||||
'markdown' => [
|
||||
'extra' => false,
|
||||
'auto_line_breaks' => false,
|
||||
'auto_url_links' => false,
|
||||
'escape_markup' => false,
|
||||
'special_chars' => ['>' => 'gt', '<' => 'lt'],
|
||||
],
|
||||
'images' => $this->config->get('system.images', [])
|
||||
];
|
||||
$page = $this->pages->dispatch('/item2/item2-2');
|
||||
$this->parsedown = new Parsedown($page, $defaults);
|
||||
|
||||
$excerpts = new Excerpts($page, $defaults);
|
||||
$this->parsedown = new Parsedown($excerpts);
|
||||
}
|
||||
|
||||
protected function _after()
|
||||
@@ -179,14 +185,18 @@ class ParsedownTest extends \Codeception\TestCase\Test
|
||||
$this->uri->initializeWithURL('http://testing.dev/')->init();
|
||||
|
||||
$defaults = [
|
||||
'extra' => false,
|
||||
'auto_line_breaks' => false,
|
||||
'auto_url_links' => false,
|
||||
'escape_markup' => false,
|
||||
'special_chars' => ['>' => 'gt', '<' => 'lt'],
|
||||
'markdown' => [
|
||||
'extra' => false,
|
||||
'auto_line_breaks' => false,
|
||||
'auto_url_links' => false,
|
||||
'escape_markup' => false,
|
||||
'special_chars' => ['>' => 'gt', '<' => 'lt'],
|
||||
],
|
||||
'images' => $this->config->get('system.images', [])
|
||||
];
|
||||
$page = $this->pages->dispatch('/');
|
||||
$this->parsedown = new Parsedown($page, $defaults);
|
||||
$excerpts = new Excerpts($page, $defaults);
|
||||
$this->parsedown = new Parsedown($excerpts);
|
||||
|
||||
$this->assertSame('<p><img alt="" src="/tests/fake/nested-site/user/pages/01.item1/home-sample-image.jpg" /></p>',
|
||||
$this->parsedown->text(''));
|
||||
@@ -230,15 +240,18 @@ class ParsedownTest extends \Codeception\TestCase\Test
|
||||
$this->uri->initializeWithURL('http://testing.dev/')->init();
|
||||
|
||||
$defaults = [
|
||||
'extra' => false,
|
||||
'auto_line_breaks' => false,
|
||||
'auto_url_links' => false,
|
||||
'escape_markup' => false,
|
||||
'special_chars' => ['>' => 'gt', '<' => 'lt'],
|
||||
'markdown' => [
|
||||
'extra' => false,
|
||||
'auto_line_breaks' => false,
|
||||
'auto_url_links' => false,
|
||||
'escape_markup' => false,
|
||||
'special_chars' => ['>' => 'gt', '<' => 'lt'],
|
||||
],
|
||||
'images' => $this->config->get('system.images', [])
|
||||
];
|
||||
$page = $this->pages->dispatch('/');
|
||||
$this->parsedown = new Parsedown($page, $defaults);
|
||||
|
||||
$excerpts = new Excerpts($page, $defaults);
|
||||
$this->parsedown = new Parsedown($excerpts);
|
||||
|
||||
$this->assertSame('<p><a href="/item1/item1-3">Down a Level</a></p>',
|
||||
$this->parsedown->text('[Down a Level](item1-3)'));
|
||||
|
||||
@@ -379,47 +379,135 @@ class UtilsTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
$this->uri->initializeWithUrl('http://testing.dev/path1/path2')->init();
|
||||
|
||||
$this->assertSame('http://testing.dev/', Utils::url('/', true));
|
||||
$this->assertSame('http://testing.dev/', Utils::url('', true));
|
||||
$this->assertSame('http://testing.dev/path1', Utils::url('/path1', true));
|
||||
// Fail hard
|
||||
$this->assertSame(false, Utils::url('', true));
|
||||
$this->assertSame(false, Utils::url(''));
|
||||
$this->assertSame(false, Utils::url(new stdClass()));
|
||||
$this->assertSame(false, Utils::url(['foo','bar','baz']));
|
||||
$this->assertSame(false, Utils::url('user://does/not/exist'));
|
||||
|
||||
// Fail Gracefully
|
||||
$this->assertSame('/', Utils::url('/', false, true));
|
||||
$this->assertSame('/', Utils::url('', false, true));
|
||||
$this->assertSame('/', Utils::url(new stdClass(), false, true));
|
||||
$this->assertSame('/', Utils::url(['foo','bar','baz'], false, true));
|
||||
$this->assertSame('/user/does/not/exist', Utils::url('user://does/not/exist', false, true));
|
||||
|
||||
// Simple paths
|
||||
$this->assertSame('/', Utils::url('/'));
|
||||
$this->assertSame('/', Utils::url(''));
|
||||
$this->assertSame('/path1', Utils::url('/path1'));
|
||||
$this->assertSame('/path1/path2', Utils::url('/path1/path2'));
|
||||
$this->assertSame('/random/path1/path2', Utils::url('/random/path1/path2'));
|
||||
$this->assertSame('/foobar.jpg', Utils::url('/foobar.jpg'));
|
||||
$this->assertSame('/path1/foobar.jpg', Utils::url('/path1/foobar.jpg'));
|
||||
$this->assertSame('/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg'));
|
||||
$this->assertSame('/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg'));
|
||||
|
||||
// Simple paths with domain
|
||||
$this->assertSame('http://testing.dev/', Utils::url('/', true));
|
||||
$this->assertSame('http://testing.dev/path1', Utils::url('/path1', true));
|
||||
$this->assertSame('http://testing.dev/path1/path2', Utils::url('/path1/path2', true));
|
||||
$this->assertSame('http://testing.dev/random/path1/path2', Utils::url('/random/path1/path2', true));
|
||||
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg', true));
|
||||
|
||||
// Relative paths from Grav root.
|
||||
$this->assertSame('/subdir', Utils::url('subdir'));
|
||||
$this->assertSame('/subdir/path1', Utils::url('subdir/path1'));
|
||||
$this->assertSame('/subdir/path1/path2', Utils::url('subdir/path1/path2'));
|
||||
$this->assertSame('/path1', Utils::url('path1'));
|
||||
$this->assertSame('/path1/path2', Utils::url('path1/path2'));
|
||||
$this->assertSame('/foobar.jpg', Utils::url('foobar.jpg'));
|
||||
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true));
|
||||
|
||||
// Relative paths from Grav root with domain.
|
||||
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
|
||||
$this->assertSame('/foobar.jpg', Utils::url('/foobar.jpg'));
|
||||
$this->assertSame('/foobar.jpg', Utils::url('foobar.jpg'));
|
||||
$this->assertSame('/path1/foobar.jpg', Utils::url('/path1/foobar.jpg'));
|
||||
$this->assertSame('/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg'));
|
||||
|
||||
// All Non-existing streams should be treated as external URI / protocol.
|
||||
$this->assertSame('http://domain.com/path', Utils::url('http://domain.com/path'));
|
||||
$this->assertSame('ftp://domain.com/path', Utils::url('ftp://domain.com/path'));
|
||||
$this->assertSame('sftp://domain.com/path', Utils::url('sftp://domain.com/path'));
|
||||
$this->assertSame('ssh://domain.com', Utils::url('ssh://domain.com'));
|
||||
$this->assertSame('pop://domain.com', Utils::url('pop://domain.com'));
|
||||
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz'));
|
||||
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', true));
|
||||
// $this->assertSame('mailto:joe@domain.com', Utils::url('mailto:joe@domain.com', true)); // FIXME <-
|
||||
}
|
||||
|
||||
public function testUrlWithRoot()
|
||||
{
|
||||
$this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/path1/path2', '/subdir')->init();
|
||||
|
||||
$this->assertSame('http://testing.dev/subdir/', Utils::url('/', true));
|
||||
$this->assertSame('http://testing.dev/subdir/', Utils::url('', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/path1', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true));
|
||||
// Fail hard
|
||||
$this->assertSame(false, Utils::url('', true));
|
||||
$this->assertSame(false, Utils::url(''));
|
||||
$this->assertSame(false, Utils::url(new stdClass()));
|
||||
$this->assertSame(false, Utils::url(['foo','bar','baz']));
|
||||
$this->assertSame(false, Utils::url('user://does/not/exist'));
|
||||
|
||||
// Fail Gracefully
|
||||
$this->assertSame('/subdir/', Utils::url('/', false, true));
|
||||
$this->assertSame('/subdir/', Utils::url('', false, true));
|
||||
$this->assertSame('/subdir/', Utils::url(new stdClass(), false, true));
|
||||
$this->assertSame('/subdir/', Utils::url(['foo','bar','baz'], false, true));
|
||||
$this->assertSame('/subdir/user/does/not/exist', Utils::url('user://does/not/exist', false, true));
|
||||
|
||||
// Simple paths
|
||||
$this->assertSame('/subdir/', Utils::url('/'));
|
||||
$this->assertSame('/subdir/', Utils::url(''));
|
||||
$this->assertSame('/subdir/path1', Utils::url('/path1'));
|
||||
$this->assertSame('/subdir/path1/path2', Utils::url('/path1/path2'));
|
||||
$this->assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2'));
|
||||
|
||||
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true));
|
||||
$this->assertSame('/subdir/random/path1/path2', Utils::url('/random/path1/path2'));
|
||||
$this->assertSame('/subdir/foobar.jpg', Utils::url('/foobar.jpg'));
|
||||
$this->assertSame('/subdir/foobar.jpg', Utils::url('foobar.jpg'));
|
||||
$this->assertSame('/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg'));
|
||||
$this->assertSame('/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg'));
|
||||
$this->assertSame('/subdir/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg'));
|
||||
$this->assertSame('/subdir/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg'));
|
||||
|
||||
// Simple paths with domain
|
||||
$this->assertSame('http://testing.dev/subdir/', Utils::url('/', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/path1', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1/path2', Utils::url('/path1/path2', true));
|
||||
$this->assertSame('http://testing.dev/subdir/random/path1/path2', Utils::url('/random/path1/path2', true));
|
||||
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/subdir/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg', true));
|
||||
|
||||
// Paths including the grav base.
|
||||
$this->assertSame('/subdir/', Utils::url('/subdir'));
|
||||
$this->assertSame('/subdir/path1', Utils::url('/subdir/path1'));
|
||||
$this->assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2'));
|
||||
$this->assertSame('/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg'));
|
||||
$this->assertSame('/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg'));
|
||||
|
||||
// Relative paths from Grav root with domain.
|
||||
$this->assertSame('http://testing.dev/subdir/', Utils::url('/subdir', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1/path2', Utils::url('/subdir/path1/path2', true));
|
||||
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true));
|
||||
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true));
|
||||
|
||||
// Relative paths from Grav root.
|
||||
$this->assertSame('/subdir/subdir', Utils::url('subdir'));
|
||||
$this->assertSame('/subdir/subdir/path1', Utils::url('subdir/path1'));
|
||||
$this->assertSame('/subdir/subdir/path1/path2', Utils::url('subdir/path1/path2'));
|
||||
$this->assertSame('/subdir/path1', Utils::url('path1'));
|
||||
$this->assertSame('/subdir/path1/path2', Utils::url('path1/path2'));
|
||||
$this->assertSame('/subdir/foobar.jpg', Utils::url('foobar.jpg'));
|
||||
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('foobar.jpg', true));
|
||||
|
||||
// All Non-existing streams should be treated as external URI / protocol.
|
||||
$this->assertSame('http://domain.com/path', Utils::url('http://domain.com/path'));
|
||||
$this->assertSame('ftp://domain.com/path', Utils::url('ftp://domain.com/path'));
|
||||
$this->assertSame('sftp://domain.com/path', Utils::url('sftp://domain.com/path'));
|
||||
$this->assertSame('ssh://domain.com', Utils::url('ssh://domain.com'));
|
||||
$this->assertSame('pop://domain.com', Utils::url('pop://domain.com'));
|
||||
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz'));
|
||||
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', true));
|
||||
// $this->assertSame('mailto:joe@domain.com', Utils::url('mailto:joe@domain.com', true)); // FIXME <-
|
||||
}
|
||||
|
||||
public function testUrlWithStreams()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user