diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..e84f52bf5
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02fb45e09..bb00c69ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,22 @@
+# 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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index da8896f69..112fe4d4d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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/).
@@ -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.
diff --git a/bin/plugin b/bin/plugin
index 6f8466d81..3c431e379 100755
--- a/bin/plugin
+++ b/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("$name plugin not found");
- die;
-}
-
-if (!$plugin->enabled) {
- $output->writeln('');
- $output->writeln("$name not enabled");
- die;
-}
-
-
if (!$name) {
$output->writeln('');
$output->writeln('Usage:');
@@ -123,6 +110,18 @@ if (!$name) {
}
exit;
+} else {
+ if (is_null($plugin)) {
+ $output->writeln('');
+ $output->writeln("$name plugin not found");
+ die;
+ }
+
+ if (!$plugin->enabled) {
+ $output->writeln('');
+ $output->writeln("$name not enabled");
+ die;
+ }
}
if ($plugin === null) {
diff --git a/composer.lock b/composer.lock
index 5c247be35..be6bf65b6 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1846,16 +1846,16 @@
},
{
"name": "symfony/contracts",
- "version": "v1.0.2",
+ "version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/contracts.git",
- "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf"
+ "reference": "d3636025e8253c6144358ec0a62773cae588395b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf",
- "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf",
+ "url": "https://api.github.com/repos/symfony/contracts/zipball/d3636025e8253c6144358ec0a62773cae588395b",
+ "reference": "d3636025e8253c6144358ec0a62773cae588395b",
"shasum": ""
},
"require": {
@@ -1863,19 +1863,22 @@
},
"require-dev": {
"psr/cache": "^1.0",
- "psr/container": "^1.0"
+ "psr/container": "^1.0",
+ "symfony/polyfill-intl-idn": "^1.10"
},
"suggest": {
"psr/cache": "When using the Cache contracts",
"psr/container": "When using the Service contracts",
"symfony/cache-contracts-implementation": "",
+ "symfony/event-dispatcher-implementation": "",
+ "symfony/http-client-contracts-implementation": "",
"symfony/service-contracts-implementation": "",
"symfony/translation-contracts-implementation": ""
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
@@ -1910,7 +1913,7 @@
"interoperability",
"standards"
],
- "time": "2018-12-05T08:06:11+00:00"
+ "time": "2019-04-27T14:29:50+00:00"
},
{
"name": "symfony/event-dispatcher",
@@ -2451,16 +2454,16 @@
},
{
"name": "twig/twig",
- "version": "v1.40.1",
+ "version": "v1.41.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "35889516bbd6bbe46a600c2c33b03515df4a076e"
+ "reference": "575cd5028362da591facde1ef5d7b94553c375c9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/35889516bbd6bbe46a600c2c33b03515df4a076e",
- "reference": "35889516bbd6bbe46a600c2c33b03515df4a076e",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/575cd5028362da591facde1ef5d7b94553c375c9",
+ "reference": "575cd5028362da591facde1ef5d7b94553c375c9",
"shasum": ""
},
"require": {
@@ -2475,7 +2478,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.40-dev"
+ "dev-master": "1.41-dev"
}
},
"autoload": {
@@ -2513,7 +2516,7 @@
"keywords": [
"templating"
],
- "time": "2019-04-29T14:12:28+00:00"
+ "time": "2019-05-14T11:59:08+00:00"
},
{
"name": "willdurand/negotiation",
@@ -4208,16 +4211,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "0.11.5",
+ "version": "0.11.6",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "24ce5a566a798b81343138ed5d41d6877554cf9a"
+ "reference": "7af8b9d02b3ab36444dbf4e1b9ca1c1bd5044d81"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/24ce5a566a798b81343138ed5d41d6877554cf9a",
- "reference": "24ce5a566a798b81343138ed5d41d6877554cf9a",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7af8b9d02b3ab36444dbf4e1b9ca1c1bd5044d81",
+ "reference": "7af8b9d02b3ab36444dbf4e1b9ca1c1bd5044d81",
"shasum": ""
},
"require": {
@@ -4277,7 +4280,7 @@
"MIT"
],
"description": "PHPStan - PHP Static Analysis Tool",
- "time": "2019-03-25T16:40:09+00:00"
+ "time": "2019-05-08T16:33:56+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -4579,16 +4582,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "7.5.9",
+ "version": "7.5.11",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "134669cf0eeac3f79bc7f0c793efbc158bffc160"
+ "reference": "64cb33f5b520da490a7b13149d39b43cf3c890c6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/134669cf0eeac3f79bc7f0c793efbc158bffc160",
- "reference": "134669cf0eeac3f79bc7f0c793efbc158bffc160",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/64cb33f5b520da490a7b13149d39b43cf3c890c6",
+ "reference": "64cb33f5b520da490a7b13149d39b43cf3c890c6",
"shasum": ""
},
"require": {
@@ -4659,7 +4662,7 @@
"testing",
"xunit"
],
- "time": "2019-04-19T15:50:46+00:00"
+ "time": "2019-05-14T04:53:02+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@@ -4828,16 +4831,16 @@
},
{
"name": "sebastian/environment",
- "version": "4.2.1",
+ "version": "4.2.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "3095910f0f0fb155ac4021fc51a4a7a39ac04e8a"
+ "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/3095910f0f0fb155ac4021fc51a4a7a39ac04e8a",
- "reference": "3095910f0f0fb155ac4021fc51a4a7a39ac04e8a",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
+ "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
"shasum": ""
},
"require": {
@@ -4877,7 +4880,7 @@
"environment",
"hhvm"
],
- "time": "2019-04-25T07:55:20+00:00"
+ "time": "2019-05-05T09:05:15+00:00"
},
{
"name": "sebastian/exporter",
diff --git a/now.json b/now.json
new file mode 100644
index 000000000..fe7b94b37
--- /dev/null
+++ b/now.json
@@ -0,0 +1,4 @@
+ {
+ "version": 2,
+ "builds": [{ "src": "*.php", "use": "@now/php" }]
+}
diff --git a/system/blueprints/config/site.yaml b/system/blueprints/config/site.yaml
index 57fd3c57a..660382347 100644
--- a/system/blueprints/config/site.yaml
+++ b/system/blueprints/config/site.yaml
@@ -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
diff --git a/system/defines.php b/system/defines.php
index 7a4df5cce..405b68062 100644
--- a/system/defines.php
+++ b/system/defines.php
@@ -8,7 +8,7 @@
// Some standard defines
define('GRAV', true);
-define('GRAV_VERSION', '1.6.9');
+define('GRAV_VERSION', '1.6.10');
define('GRAV_TESTING', false);
define('DS', '/');
diff --git a/system/src/Grav/Common/Data/Blueprints.php b/system/src/Grav/Common/Data/Blueprints.php
index 0b0719d09..1d0fcc689 100644
--- a/system/src/Grav/Common/Data/Blueprints.php
+++ b/system/src/Grav/Common/Data/Blueprints.php
@@ -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;
}
}
diff --git a/system/src/Grav/Common/GPM/Common/CachedCollection.php b/system/src/Grav/Common/GPM/Common/CachedCollection.php
index 7c769752e..a6fca4db0 100644
--- a/system/src/Grav/Common/GPM/Common/CachedCollection.php
+++ b/system/src/Grav/Common/GPM/Common/CachedCollection.php
@@ -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]);
}
}
diff --git a/system/src/Grav/Common/GPM/Common/Package.php b/system/src/Grav/Common/GPM/Common/Package.php
index d205f1ee1..40d375912 100644
--- a/system/src/Grav/Common/GPM/Common/Package.php
+++ b/system/src/Grav/Common/GPM/Common/Package.php
@@ -11,8 +11,8 @@ namespace Grav\Common\GPM\Common;
use Grav\Common\Data\Data;
-class Package {
-
+class Package
+{
/**
* @var Data
*/
diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php
index 94eec7d99..6c9e7b0e4 100644
--- a/system/src/Grav/Common/GPM/GPM.php
+++ b/system/src/Grav/Common/GPM/GPM.php
@@ -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 $slug is required in an older version by package $dependent_package. This package needs a newer version, and because of this it cannot be installed. The $dependent_package package must be updated to use a newer release of $slug.",
+ if (!in_array($dependent_package, $ignore_packages_list, true)) {
+ throw new \RuntimeException("Package $slug is required in an older version by package $dependent_package. This package needs a newer version, and because of this it cannot be installed. The $dependent_package package must be updated to use a newer release of $slug.",
2);
}
}
@@ -850,10 +850,10 @@ class GPM extends Iterator
) {
//Needs a Grav update first
throw new \RuntimeException("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("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);
diff --git a/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php
index 9acc08385..be565c9c9 100644
--- a/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php
+++ b/system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php
@@ -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);
diff --git a/system/src/Grav/Common/GPM/Local/Plugins.php b/system/src/Grav/Common/GPM/Local/Plugins.php
index 9c8e6dd13..19adfb88e 100644
--- a/system/src/Grav/Common/GPM/Local/Plugins.php
+++ b/system/src/Grav/Common/GPM/Local/Plugins.php
@@ -25,6 +25,7 @@ class Plugins extends AbstractPackageCollection
{
/** @var \Grav\Common\Plugins $plugins */
$plugins = Grav::instance()['plugins'];
+
parent::__construct($plugins->all());
}
}
diff --git a/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php b/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php
index b46dc04da..429c5b1dc 100644
--- a/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php
+++ b/system/src/Grav/Common/GPM/Remote/AbstractPackageCollection.php
@@ -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');
diff --git a/system/src/Grav/Common/GPM/Remote/GravCore.php b/system/src/Grav/Common/GPM/Remote/GravCore.php
index 168f82f8e..1d30120d0 100644
--- a/system/src/Grav/Common/GPM/Remote/GravCore.php
+++ b/system/src/Grav/Common/GPM/Remote/GravCore.php
@@ -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) {
diff --git a/system/src/Grav/Common/Helpers/Excerpts.php b/system/src/Grav/Common/Helpers/Excerpts.php
index 094324213..f07c3aa5a 100644
--- a/system/src/Grav/Common/Helpers/Excerpts.php
+++ b/system/src/Grav/Common/Helpers/Excerpts.php
@@ -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. `
`
- * @param PageInterface $page The current page object
- * @return string Returns final HTML string
+ * @param string $html HTML tag e.g. `
`
+ * @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);
}
}
diff --git a/system/src/Grav/Common/Helpers/YamlLinter.php b/system/src/Grav/Common/Helpers/YamlLinter.php
index 43bde820b..b7e608d00 100644
--- a/system/src/Grav/Common/Helpers/YamlLinter.php
+++ b/system/src/Grav/Common/Helpers/YamlLinter.php
@@ -20,7 +20,8 @@ class YamlLinter
{
$errors = static::lintConfig();
$errors = $errors + static::lintPages();
-
+ $errors = $errors + static::lintBlueprints();
+
return $errors;
}
@@ -34,6 +35,18 @@ class YamlLinter
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 = [];
diff --git a/system/src/Grav/Common/Markdown/Parsedown.php b/system/src/Grav/Common/Markdown/Parsedown.php
index d2f72fcb7..32033ddff 100644
--- a/system/src/Grav/Common/Markdown/Parsedown.php
+++ b/system/src/Grav/Common/Markdown/Parsedown.php
@@ -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);
}
}
diff --git a/system/src/Grav/Common/Markdown/ParsedownExtra.php b/system/src/Grav/Common/Markdown/ParsedownExtra.php
index ac0477150..a562d8d4a 100644
--- a/system/src/Grav/Common/Markdown/ParsedownExtra.php
+++ b/system/src/Grav/Common/Markdown/ParsedownExtra.php
@@ -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);
}
}
diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php
index fa8fa9156..aa76cdf0c 100644
--- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php
+++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php
@@ -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) {
diff --git a/system/src/Grav/Common/Page/Markdown/Excerpts.php b/system/src/Grav/Common/Page/Markdown/Excerpts.php
new file mode 100644
index 000000000..eaa13cbe8
--- /dev/null
+++ b/system/src/Grav/Common/Page/Markdown/Excerpts.php
@@ -0,0 +1,329 @@
+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;
+ }
+}
diff --git a/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
index 627c361ff..c6d75bc77 100644
--- a/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
+++ b/system/src/Grav/Common/Page/Medium/ParsedownHtmlTrait.php
@@ -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);
diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php
index 82ff71cb2..bcf7539bd 100644
--- a/system/src/Grav/Common/Page/Page.php
+++ b/system/src/Grav/Common/Page/Page.php
@@ -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,8 +1405,8 @@ 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();
diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php
index d83c1521d..358727f33 100644
--- a/system/src/Grav/Common/Plugins.php
+++ b/system/src/Grav/Common/Plugins.php
@@ -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;
}
}
diff --git a/system/src/Grav/Common/Service/SessionServiceProvider.php b/system/src/Grav/Common/Service/SessionServiceProvider.php
index 856ec3596..84d23d354 100644
--- a/system/src/Grav/Common/Service/SessionServiceProvider.php
+++ b/system/src/Grav/Common/Service/SessionServiceProvider.php
@@ -50,13 +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).
- if (Utils::startsWith($current_route, $base) || Utils::pathPrefixedByLangCode($current_route)) {
+ 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;
}
diff --git a/system/src/Grav/Common/Themes.php b/system/src/Grav/Common/Themes.php
index 8c0e08b6e..6720d462d 100644
--- a/system/src/Grav/Common/Themes.php
+++ b/system/src/Grav/Common/Themes.php
@@ -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;
}
}
diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php
index baced9abb..7e596d98a 100644
--- a/system/src/Grav/Common/Twig/TwigExtension.php
+++ b/system/src/Grav/Common/Twig/TwigExtension.php
@@ -1154,7 +1154,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);
diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php
index 811f2f433..a19373585 100644
--- a/system/src/Grav/Common/Utils.php
+++ b/system/src/Grav/Common/Utils.php
@@ -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;
@@ -29,19 +30,24 @@ abstract class Utils
*
* @param string $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;
}
- if (Grav::instance()['uri']->isExternal($input)) {
+ if (Uri::isExternal($input)) {
return $input;
}
@@ -56,13 +62,20 @@ abstract class Utils
if (Utils::contains((string)$input, '://')) {
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
-
$parts = Uri::parseUrl($input);
if ($parts) {
- $resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false);
+ try {
+ $resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false);
+ } catch (\Exception $e) {
+ if ($fail_gracefully) {
+ return $input;
+ } else {
+ return false;
+ }
+ }
- if (isset($parts['query'])) {
+ if ($resource && isset($parts['query'])) {
$resource = $resource . '?' . $parts['query'];
}
} else {
@@ -70,12 +83,13 @@ abstract class Utils
$resource = $locator->findResource($input, false);
}
-
} else {
$resource = $input;
}
-
+ if (!$fail_gracefully && $resource === false) {
+ return false;
+ }
return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
}
@@ -974,20 +988,16 @@ 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 true;
+ return $parts[0];
}
return false;
@@ -1396,7 +1406,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];
@@ -1460,14 +1470,21 @@ abstract class Utils
*/
public static function processMarkdown($string, $block = true)
{
- $page = Grav::instance()['page'] ?? null;
- $defaults = Grav::instance()['config']->get('system.pages.markdown');
+ $grav = Grav::instance();
+ $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) {
@@ -1486,12 +1503,11 @@ abstract class Utils
* @param int $prefix
*
* @return string
- * @throws \InvalidArgumentException if provided an invalid IP
*/
public static function getSubnet($ip, $prefix = 64)
{
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
- throw new \InvalidArgumentException('Invalid IP: ' . $ip);
+ return $ip;
}
// Packed representation of IP
diff --git a/system/src/Grav/Console/Cli/YamlLinterCommand.php b/system/src/Grav/Console/Cli/YamlLinterCommand.php
index 18cdb493f..894f1c02d 100644
--- a/system/src/Grav/Console/Cli/YamlLinterCommand.php
+++ b/system/src/Grav/Console/Cli/YamlLinterCommand.php
@@ -61,6 +61,15 @@ class YamlLinterCommand extends ConsoleCommand
$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)
diff --git a/system/src/Grav/Console/Gpm/InstallCommand.php b/system/src/Grav/Console/Gpm/InstallCommand.php
index 840ee5b2e..8d544d4c7 100644
--- a/system/src/Grav/Console/Gpm/InstallCommand.php
+++ b/system/src/Grav/Console/Gpm/InstallCommand.php
@@ -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(" '- Skipped! ");
diff --git a/system/src/Grav/Framework/Flex/FlexCollection.php b/system/src/Grav/Framework/Flex/FlexCollection.php
index 5f2cbd844..5e5431bbf 100644
--- a/system/src/Grav/Framework/Flex/FlexCollection.php
+++ b/system/src/Grav/Framework/Flex/FlexCollection.php
@@ -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;
@@ -125,7 +126,7 @@ class FlexCollection extends ObjectCollection implements FlexCollectionInterface
/**
* @param array $filters
- * @return FlexCollectionInterface
+ * @return FlexCollectionInterface|Collection
*/
public function filterBy(array $filters)
{
diff --git a/system/src/Grav/Framework/Flex/FlexDirectory.php b/system/src/Grav/Framework/Flex/FlexDirectory.php
index 7b50a7eed..c75a90152 100644
--- a/system/src/Grav/Framework/Flex/FlexDirectory.php
+++ b/system/src/Grav/Framework/Flex/FlexDirectory.php
@@ -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) {
diff --git a/system/src/Grav/Framework/Flex/FlexForm.php b/system/src/Grav/Framework/Flex/FlexForm.php
index 7faad5227..6ded34590 100644
--- a/system/src/Grav/Framework/Flex/FlexForm.php
+++ b/system/src/Grav/Framework/Flex/FlexForm.php
@@ -20,6 +20,7 @@ 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;
/**
@@ -82,7 +83,7 @@ class FlexForm implements FlexFormInterface
}
/**
- * @return Data|FlexObjectInterface
+ * @return Data|FlexObjectInterface|object
*/
public function getData()
{
@@ -103,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)
@@ -277,7 +278,7 @@ class FlexForm implements FlexFormInterface
/**
* @param string $layout
- * @return TemplateWrapper
+ * @return Template|TemplateWrapper
* @throws LoaderError
* @throws SyntaxError
*/
diff --git a/system/src/Grav/Framework/Flex/FlexIndex.php b/system/src/Grav/Framework/Flex/FlexIndex.php
index 29ac33bbc..8b96b4a9a 100644
--- a/system/src/Grav/Framework/Flex/FlexIndex.php
+++ b/system/src/Grav/Framework/Flex/FlexIndex.php
@@ -312,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 {
@@ -462,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 = [];
}
diff --git a/system/src/Grav/Framework/Flex/FlexObject.php b/system/src/Grav/Framework/Flex/FlexObject.php
index 51f622a7a..01f45d079 100644
--- a/system/src/Grav/Framework/Flex/FlexObject.php
+++ b/system/src/Grav/Framework/Flex/FlexObject.php
@@ -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;
/**
@@ -391,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) {
@@ -410,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();
@@ -434,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);
}
@@ -541,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()) {
@@ -558,7 +559,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);
@@ -586,7 +587,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);
@@ -736,7 +737,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
}
$grav = Grav::instance();
- /** @var Flex $flex */
+ /** @var Flex|null $flex */
$flex = $grav['flex_objects'] ?? null;
$directory = $flex ? $flex->getDirectory($type) : null;
if (!$directory) {
@@ -807,7 +808,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
/**
* @param string $layout
- * @return TemplateWrapper
+ * @return Template|TemplateWrapper
* @throws LoaderError
* @throws SyntaxError
*/
diff --git a/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php b/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php
index b547cc227..cc460ccd0 100644
--- a/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php
+++ b/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php
@@ -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));
}
/**
diff --git a/system/src/Grav/Framework/Flex/Storage/FolderStorage.php b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
index 2304e8013..76397c235 100644
--- a/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
+++ b/system/src/Grav/Framework/Flex/Storage/FolderStorage.php
@@ -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'];
diff --git a/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php b/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php
index 209ba3382..088b9527e 100644
--- a/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php
+++ b/system/src/Grav/Framework/Flex/Storage/SimpleStorage.php
@@ -227,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;
}
diff --git a/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php
index 0029d9fe0..643004e74 100644
--- a/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php
+++ b/system/src/Grav/Framework/Flex/Traits/FlexAuthorizeTrait.php
@@ -26,6 +26,7 @@ trait FlexAuthorizeTrait
public function isAuthorized(string $action, string $scope = null, UserInterface $user = null) : bool
{
if (null === $user) {
+ /** @var UserInterface $user */
$user = Grav::instance()['user'];
}
diff --git a/system/src/Grav/Framework/Interfaces/RenderInterface.php b/system/src/Grav/Framework/Interfaces/RenderInterface.php
index 9185c1712..ca6d9bca4 100644
--- a/system/src/Grav/Framework/Interfaces/RenderInterface.php
+++ b/system/src/Grav/Framework/Interfaces/RenderInterface.php
@@ -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
diff --git a/system/src/Grav/Framework/Uri/UriPartsFilter.php b/system/src/Grav/Framework/Uri/UriPartsFilter.php
index 88fe3e64e..51a971ab4 100644
--- a/system/src/Grav/Framework/Uri/UriPartsFilter.php
+++ b/system/src/Grav/Framework/Uri/UriPartsFilter.php
@@ -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');
}
/**
diff --git a/tests/phpstan/phpstan.neon b/tests/phpstan/phpstan.neon
index e8e24ff51..29781281b 100644
--- a/tests/phpstan/phpstan.neon
+++ b/tests/phpstan/phpstan.neon
@@ -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::#'
diff --git a/tests/unit/Grav/Common/Markdown/ParsedownTest.php b/tests/unit/Grav/Common/Markdown/ParsedownTest.php
index e73e11f01..58a1acf61 100644
--- a/tests/unit/Grav/Common/Markdown/ParsedownTest.php
+++ b/tests/unit/Grav/Common/Markdown/ParsedownTest.php
@@ -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('
',
$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('Down a Level
',
$this->parsedown->text('[Down a Level](item1-3)'));
diff --git a/tests/unit/Grav/Common/UtilsTest.php b/tests/unit/Grav/Common/UtilsTest.php
index f3b2a9707..e5be6394b 100644
--- a/tests/unit/Grav/Common/UtilsTest.php
+++ b/tests/unit/Grav/Common/UtilsTest.php
@@ -379,14 +379,25 @@ 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('foo://bar/baz'));
+ $this->assertSame(false, Utils::url(new stdClass()));
+ $this->assertSame(false, Utils::url(['foo','bar','baz']));
+
+ // Fail Gracefully
+ $this->assertSame('/', Utils::url('/', false, true));
+ $this->assertSame('/', Utils::url('', false, true));
+ $this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', false, true));
+ $this->assertSame('/', Utils::url(new stdClass(), false, true));
+ $this->assertSame('/', Utils::url(['foo','bar','baz'], false, true));
+
$this->assertSame('/', Utils::url('/'));
- $this->assertSame('/', Utils::url(''));
+ $this->assertSame('http://testing.dev/', Utils::url('/', true));
+ $this->assertSame('http://testing.dev/path1', Utils::url('/path1', true));
$this->assertSame('/path1', Utils::url('/path1'));
$this->assertSame('/path1/path2', Utils::url('/path1/path2'));
-
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true));
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true));
$this->assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
@@ -394,18 +405,27 @@ class UtilsTest extends \Codeception\TestCase\Test
$this->assertSame('/foobar.jpg', Utils::url('foobar.jpg'));
$this->assertSame('/path1/foobar.jpg', Utils::url('/path1/foobar.jpg'));
$this->assertSame('/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg'));
+
}
public function testUrlWithRoot()
{
$this->uri->initializeWithUrlAndRootPath('http://testing.dev/subdir/path1/path2', '/subdir')->init();
+ // Fail hard
+ $this->assertSame(false, Utils::url('', true));
+ $this->assertSame(false, Utils::url(''));
+ $this->assertSame(false, Utils::url('foo://bar/baz'));
+
+ // Fail Gracefully
+ $this->assertSame('/subdir/', Utils::url('/', false, true));
+ $this->assertSame('/subdir/', Utils::url('', false, true));
+ $this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', false, true));
+
$this->assertSame('http://testing.dev/subdir/', Utils::url('/', true));
- $this->assertSame('http://testing.dev/subdir/', Utils::url('', true));
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/path1', true));
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true));
$this->assertSame('/subdir/', Utils::url('/'));
- $this->assertSame('/subdir/', Utils::url(''));
$this->assertSame('/subdir/path1', Utils::url('/path1'));
$this->assertSame('/subdir/path1/path2', Utils::url('/path1/path2'));
$this->assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2'));