maj grav and plugin

This commit is contained in:
2020-09-08 12:12:59 +02:00
parent 3e5e731c2c
commit d9f2562300
46 changed files with 3418 additions and 755 deletions
+124
View File
@@ -1,3 +1,127 @@
# v1.6.27
## 09/01/2020
1. [](#improved)
* Right trim route for safety
* Use the proper ellipsis for summary [#2939](https://github.com/getgrav/grav/pull/2939)
* Left pad schedule times with zeros [#2921](https://github.com/getgrav/grav/pull/2921)
# v1.6.26
## 06/08/2020
1. [](#improved)
* Added new configuration option to control the supported attributes in markdown links [#2882](https://github.com/getgrav/grav/issues/2882)
1. [](#bugfix)
* Fixed blueprint for `system.pages.hide_empty_folders` [#1925](https://github.com/getgrav/grav/issues/2925)
* JSON Route of homepage with no route set is valid
* Fix case-insensitive search of location header [form#425](https://github.com/getgrav/grav-plugin-form/issues/425)
# v1.6.25
## 05/14/2020
1. [](#improved)
* Added system configuration support for `HTTP_X_Forwarded` headers (host disabled by default)
* Updated `PHPUserAgentParser` to 1.0.0
* Bump `Go` to version 1.13 in `travis.yaml`
# v1.6.24
## 04/27/2020
1. [](#improved)
* Added support for `X-Forwarded-Host` [#2891](https://github.com/getgrav/grav/pull/2891)
* Disable XDebug in Travis builds
# v1.6.23
## 03/19/2020
1. [](#new)
* Moved `Parsedown` 1.6 and `ParsedownExtra` 0.7 into `Grav\Framework\Parsedown` to allow fixes
* Added `aliases.php` with references to direct `\Parsedown` and `\ParsedownExtra` references
1. [](#improved)
* Upgraded `jQuery` to latest 3.4.1 version [#2859](https://github.com/getgrav/grav/issues/2859)
1. [](#bugfix)
* Fixed PHP 7.4 issue in ParsedownExtra [#2832](https://github.com/getgrav/grav/issues/2832)
* Fix for [user reported](https://twitter.com/OriginalSicksec) CVE path-based open redirect
* Fix for `stream_set_option` error with PHP 7.4 via Toolbox#28 [#2850](https://github.com/getgrav/grav/issues/2850)
# v1.6.22
## 03/05/2020
1. [](#new)
* Added `Pages::reset()` method
1. [](#improved)
* Updated Negotiation library to address issues [#2513](https://github.com/getgrav/grav/issues/2513)
1. [](#bugfix)
* Fixed issue with search plugins not being able to switch between page translations
* Fixed issues with `Pages::baseRoute()` not picking up active language reliably
* Reverted `validation: strict` fix as it breaks sites, see [#1273](https://github.com/getgrav/grav/issues/1273)
# v1.6.21
## 02/11/2020
1. [](#new)
* Added `ConsoleCommand::setLanguage()` method to set language to be used from CLI
* Added `ConsoleCommand::initializeGrav()` method to properly set up Grav instance to be used from CLI
* Added `ConsoleCommand::initializePlugins()`method to properly set up all plugins to be used from CLI
* Added `ConsoleCommand::initializeThemes()`method to properly set up current theme to be used from CLI
* Added `ConsoleCommand::initializePages()` method to properly set up pages to be used from CLI
1. [](#improved)
* Vendor updates
1. [](#bugfix)
* Fixed `bin/plugin` CLI calling `$themes->init()` way too early (removed it, use above methods instead)
* Fixed call to `$grav['page']` crashing CLI
* Fixed encoding problems when PHP INI setting `default_charset` is not `utf-8` [#2154](https://github.com/getgrav/grav/issues/2154)
# v1.6.20
## 02/03/2020
1. [](#bugfix)
* Fixed incorrect routing caused by `str_replace()` in `Uri::init()` [#2754](https://github.com/getgrav/grav/issues/2754)
* Fixed session cookie is being set twice in the HTTP header [#2745](https://github.com/getgrav/grav/issues/2745)
* Fixed session not restarting if user was invalid (downgrading from Grav 1.7)
* Fixed filesystem iterator calls with non-existing folders
* Fixed `checkbox` field not being saved, requires also Form v4.0.2 [#1225](https://github.com/getgrav/grav/issues/1225)
* Fixed `validation: strict` not working in blueprints [#1273](https://github.com/getgrav/grav/issues/1273)
* Fixed `Data::filter()` removing empty fields (such as empty list) by default [#2805](https://github.com/getgrav/grav/issues/2805)
* Fixed fatal error with non-integer page param value [#2803](https://github.com/getgrav/grav/issues/2803)
* Fixed `Assets::addInlineJs()` parameter type mismatch between v1.5 and v1.6 [#2659](https://github.com/getgrav/grav/issues/2659)
* Fixed `site.metadata` saving issues [#2615](https://github.com/getgrav/grav/issues/2615)
# v1.6.19
## 12/04/2019
1. [](#new)
* Catch PHP 7.4 deprecation messages and report them in debugbar instead of throwing fatal error
1. [](#bugfix)
* Fixed fatal error when calling `{{ grav.undefined }}`
* Fixed multiple issues when there are no pages in the site
* PHP 7.4 fix for [#2750](https://github.com/getgrav/grav/issues/2750)
# v1.6.18
## 12/02/2019
1. [](#bugfix)
* PHP 7.4 fix in `Pages::buildSort()`
* Updated vendor libraries for PHP 7.4 fixes in Twig and other libraries
* Fixed fatal error when `$page->id()` is null [#2731](https://github.com/getgrav/grav/pull/2731)
* Fixed cache conflicts on pages with no set id
* Fix rewrite rule for for `lighttpd` default config [#721](https://github.com/getgrav/grav/pull/2721)
# v1.6.17
## 11/06/2019
1. [](#new)
* Added working ETag (304 Not Modified) support based on the final rendered HTML
1. [](#improved)
* Safer file handling + customizable null char replacement in `CsvFormatter::decode()`
* Change of Behavior: `Inflector::hyphenize` will now automatically trim dashes at beginning and end of a string.
* Change in Behavior for `Folder::all()` so no longer fails if trying to copy non-existent dot file [#2581](https://github.com/getgrav/grav/pull/2581)
* renamed composer `test-plugins` script to `phpstan-plugins` to be more explicit [#2637](https://github.com/getgrav/grav/pull/2637)
1. [](#bugfix)
* Fixed PHP 7.1 bug in FlexMedia
* Fix cache image generation when using cropResize [#2639](https://github.com/getgrav/grav/pull/2639)
* Fix `array_merge()` exception with non-array page header metadata [#2701](https://github.com/getgrav/grav/pull/2701)
# v1.6.16 # v1.6.16
## 09/19/2019 ## 09/19/2019
+8 -1
View File
@@ -28,6 +28,13 @@ if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
} }
// Set internal encoding.
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
@ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8');
if (!file_exists(GRAV_ROOT . '/index.php')) { if (!file_exists(GRAV_ROOT . '/index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!'); exit('FATAL: Must be run from ROOT directory of Grav!');
} }
@@ -55,7 +62,7 @@ $grav->setup($environment);
$grav['config']->init(); $grav['config']->init();
$grav['uri']->init(); $grav['uri']->init();
$grav['users']; $grav['accounts'];
$app = new Application('Grav Package Manager', GRAV_VERSION); $app = new Application('Grav Package Manager', GRAV_VERSION);
$app->addCommands(array( $app->addCommands(array(
+11 -4
View File
@@ -25,6 +25,17 @@ 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)); exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
} }
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
// Set internal encoding.
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
@ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8');
$climate = new League\CLImate\CLImate; $climate = new League\CLImate\CLImate;
$climate->arguments->add([ $climate->arguments->add([
'environment' => [ 'environment' => [
@@ -42,10 +53,6 @@ $environment = $climate->arguments->get('environment');
$grav = Grav::instance(array('loader' => $autoload)); $grav = Grav::instance(array('loader' => $autoload));
$grav->setup($environment); $grav->setup($environment);
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
if (!file_exists(GRAV_ROOT . '/index.php')) { if (!file_exists(GRAV_ROOT . '/index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!'); exit('FATAL: Must be run from ROOT directory of Grav!');
} }
+8 -6
View File
@@ -32,6 +32,13 @@ if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
} }
// Set internal encoding.
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
@ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8');
if (!file_exists(GRAV_ROOT . '/index.php')) { if (!file_exists(GRAV_ROOT . '/index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!'); exit('FATAL: Must be run from ROOT directory of Grav!');
} }
@@ -51,12 +58,7 @@ $environment = $climate->arguments->get('environment');
$grav = Grav::instance(array('loader' => $autoload)); $grav = Grav::instance(array('loader' => $autoload));
$grav->setup($environment); $grav->setup($environment);
$grav->initializeCli();
$grav['config']->init();
$grav['uri']->init();
$grav['users'];
$grav['plugins']->init();
$grav['themes']->init();
$app = new Application('Grav Plugins Commands', GRAV_VERSION); $app = new Application('Grav Plugins Commands', GRAV_VERSION);
$pattern = '([A-Z]\w+Command\.php)'; $pattern = '([A-Z]\w+Command\.php)';
+18 -13
View File
@@ -2,7 +2,13 @@
"name": "getgrav/grav", "name": "getgrav/grav",
"type": "project", "type": "project",
"description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS", "description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS",
"keywords": ["cms","flat-file cms","flat cms","flatfile cms","php"], "keywords": [
"cms",
"flat-file cms",
"flat cms",
"flatfile cms",
"php"
],
"homepage": "https://getgrav.org", "homepage": "https://getgrav.org",
"license": "MIT", "license": "MIT",
"require": { "require": {
@@ -16,17 +22,12 @@
"symfony/polyfill-iconv": "^1.9", "symfony/polyfill-iconv": "^1.9",
"symfony/polyfill-php72": "^1.9", "symfony/polyfill-php72": "^1.9",
"symfony/polyfill-php73": "^1.9", "symfony/polyfill-php73": "^1.9",
"psr/simple-cache": "^1.0", "psr/simple-cache": "^1.0",
"psr/http-message": "^1.0", "psr/http-message": "^1.0",
"psr/http-server-middleware": "^1.0", "psr/http-server-middleware": "^1.0",
"kodus/psr7-server": "*", "kodus/psr7-server": "*",
"nyholm/psr7": "^1.0", "nyholm/psr7": "^1.0",
"twig/twig": "~1.40", "twig/twig": "~1.40",
"erusev/parsedown": "1.6.4",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~4.2.0", "symfony/yaml": "~4.2.0",
"symfony/console": "~4.2.0", "symfony/console": "~4.2.0",
"symfony/event-dispatcher": "~4.2.0", "symfony/event-dispatcher": "~4.2.0",
@@ -36,13 +37,12 @@
"doctrine/collections": "^1.5", "doctrine/collections": "^1.5",
"guzzlehttp/psr7": "^1.4", "guzzlehttp/psr7": "^1.4",
"filp/whoops": "~2.2", "filp/whoops": "~2.2",
"matthiasmullie/minify": "^1.3", "matthiasmullie/minify": "^1.3",
"monolog/monolog": "~1.0", "monolog/monolog": "~1.0",
"gregwar/image": "2.*", "gregwar/image": "2.*",
"donatj/phpuseragentparser": "~0.10", "donatj/phpuseragentparser": "~1.0",
"pimple/pimple": "~3.2", "pimple/pimple": "~3.2",
"rockettheme/toolbox": "~1.4", "rockettheme/toolbox": "~1.4.0",
"maximebf/debugbar": "~1.15", "maximebf/debugbar": "~1.15",
"league/climate": "^3.4", "league/climate": "^3.4",
"antoligy/dom-string-iterators": "^1.0", "antoligy/dom-string-iterators": "^1.0",
@@ -50,7 +50,7 @@
"composer/ca-bundle": "^1.0", "composer/ca-bundle": "^1.0",
"dragonmantank/cron-expression": "^1.2", "dragonmantank/cron-expression": "^1.2",
"phive/twig-extensions-deferred": "^1.0", "phive/twig-extensions-deferred": "^1.0",
"willdurand/negotiation": "^2.3" "willdurand/negotiation": "2.x-dev"
}, },
"require-dev": { "require-dev": {
"codeception/codeception": "^2.4", "codeception/codeception": "^2.4",
@@ -83,10 +83,15 @@
"psr-4": { "psr-4": {
"Grav\\": "system/src/Grav" "Grav\\": "system/src/Grav"
}, },
"files": ["system/defines.php"] "files": [
"system/defines.php",
"system/aliases.php"
]
}, },
"archive": { "archive": {
"exclude": ["VERSION"] "exclude": [
"VERSION"
]
}, },
"scripts": { "scripts": {
"api-16": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.16.md", "api-16": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.16.md",
@@ -94,7 +99,7 @@
"post-create-project-cmd": "bin/grav install", "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": "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", "phpstan-framework": "vendor/bin/phpstan analyse -l 5 -c ./tests/phpstan/phpstan.neon system/src/Grav/Framework --memory-limit=256M",
"test-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=256M", "phpstan-plugins": "vendor/bin/phpstan analyse -l 0 -c ./tests/phpstan/plugins.neon user/plugins --memory-limit=256M",
"test": "vendor/bin/codecept run unit", "test": "vendor/bin/codecept run unit",
"test-windows": "vendor\\bin\\codecept run unit" "test-windows": "vendor\\bin\\codecept run unit"
}, },
Generated
+500 -529
View File
File diff suppressed because it is too large Load Diff
+10 -9
View File
@@ -20,6 +20,16 @@ if (PHP_SAPI === 'cli-server' && !isset($_SERVER['PHP_CLI_ROUTER'])) {
die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>"); die("PHP webserver requires a router to run Grav, please use: <pre>php -S {$_SERVER['SERVER_NAME']}:{$_SERVER['SERVER_PORT']} system/router.php</pre>");
} }
// Set timezone to default, falls back to system if php.ini not set
date_default_timezone_set(@date_default_timezone_get());
// Set internal encoding.
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
@ini_set('default_charset', 'UTF-8');
mb_internal_encoding('UTF-8');
// Ensure vendor libraries exist // Ensure vendor libraries exist
$autoload = __DIR__ . '/vendor/autoload.php'; $autoload = __DIR__ . '/vendor/autoload.php';
if (!is_file($autoload)) { if (!is_file($autoload)) {
@@ -32,15 +42,6 @@ $loader = require $autoload;
use Grav\Common\Grav; use Grav\Common\Grav;
use RocketTheme\Toolbox\Event\Event; use RocketTheme\Toolbox\Event\Event;
// Set timezone to default, falls back to system if php.ini not set
date_default_timezone_set(@date_default_timezone_get());
// Set internal encoding if mbstring loaded
if (!\extension_loaded('mbstring')) {
die("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
mb_internal_encoding('UTF-8');
// Get the Grav instance // Get the Grav instance
$grav = Grav::instance( $grav = Grav::instance(
array( array(
+5
View File
@@ -0,0 +1,5 @@
<?php
/** Moved from non-namespaced classes to Grav Framework */
class_alias(Grav\Framework\Parsedown\Parsedown::class, '\Parsedown');
class_alias(Grav\Framework\Parsedown\ParsedownExtra::class, '\ParsedownExtra');
File diff suppressed because one or more lines are too long
+61 -4
View File
@@ -241,13 +241,15 @@ form:
type: commalist type: commalist
pages.hide_empty_folders: pages.hide_empty_folders:
type: selectize type: toggle
size: large
label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS label: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS
help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP help: PLUGIN_ADMIN.HIDE_EMPTY_FOLDERS_HELP
classes: fancy highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate: validate:
type: commalist type: bool
pages.url_taxonomy_filters: pages.url_taxonomy_filters:
type: toggle type: toggle
@@ -513,6 +515,16 @@ form:
validate: validate:
type: bool type: bool
pages.markdown.valid_link_attributes:
type: selectize
size: large
label: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES
help: PLUGIN_ADMIN.VALID_LINK_ATTRIBUTES_HELP
placeholder: "rel, target, id, class, classes"
classes: fancy
validate:
type: commalist
caching: caching:
type: tab type: tab
title: PLUGIN_ADMIN.CACHING title: PLUGIN_ADMIN.CACHING
@@ -1379,6 +1391,51 @@ form:
label: PLUGIN_ADMIN.CUSTOM_BASE_URL label: PLUGIN_ADMIN.CUSTOM_BASE_URL
help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP help: PLUGIN_ADMIN.CUSTOM_BASE_URL_HELP
http_x_forwarded.protocol:
type: toggle
label: HTTP_X_FORWARDED_PROTO Enabled
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
http_x_forwarded.host:
type: toggle
label: HTTP_X_FORWARDED_HOST Enabled
highlight: 0
default: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
http_x_forwarded.port:
type: toggle
label: HTTP_X_FORWARDED_PORT Enabled
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
http_x_forwarded.ip:
type: toggle
label: HTTP_X_FORWARDED IP Enabled
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
accounts.type: accounts.type:
type: hidden type: hidden
+11
View File
@@ -10,6 +10,11 @@ custom_base_url: '' # Set the base_url manually, e.
username_regex: '^[a-z0-9_-]{3,16}$' # Only lowercase chars, digits, dashes, underscores. 3 - 16 chars username_regex: '^[a-z0-9_-]{3,16}$' # Only lowercase chars, digits, dashes, underscores. 3 - 16 chars
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}' # At least one number, one uppercase and lowercase letter, and be at least 8+ chars pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}' # At least one number, one uppercase and lowercase letter, and be at least 8+ chars
intl_enabled: true # Special logic for PHP International Extension (mod_intl) intl_enabled: true # Special logic for PHP International Extension (mod_intl)
http_x_forwarded: # Configuration options for the various HTTP_X_FORWARD headers
protocol: true
host: false
port: true
ip: true
languages: languages:
supported: [] # List of languages supported. eg: [en, fr, de] supported: [] # List of languages supported. eg: [en, fr, de]
@@ -54,6 +59,12 @@ pages:
special_chars: # List of special characters to automatically convert to entities special_chars: # List of special characters to automatically convert to entities
'>': 'gt' '>': 'gt'
'<': 'lt' '<': 'lt'
valid_link_attributes: # Valid attributes to pass through via markdown links
- rel
- target
- id
- class
- classes
types: [html,htm,xml,txt,json,rss,atom] # list of valid page types types: [html,htm,xml,txt,json,rss,atom] # list of valid page types
append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html) append_url_extension: '' # Append page's extension in Page urls (e.g. '.html' results in /path/page.html)
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days) expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
+1 -1
View File
@@ -8,7 +8,7 @@
// Some standard defines // Some standard defines
define('GRAV', true); define('GRAV', true);
define('GRAV_VERSION', '1.6.16'); define('GRAV_VERSION', '1.6.27');
define('GRAV_TESTING', false); define('GRAV_TESTING', false);
define('DS', '/'); define('DS', '/');
@@ -51,7 +51,11 @@ trait LegacyAssetsTrait
// special case to handle old attributes being passed in // special case to handle old attributes being passed in
if (isset($arguments['attributes'])) { if (isset($arguments['attributes'])) {
$old_attributes = $arguments['attributes']; $old_attributes = $arguments['attributes'];
if (is_array($old_attributes)) {
$arguments = array_merge($arguments, $old_attributes); $arguments = array_merge($arguments, $old_attributes);
} else {
$arguments['type'] = $old_attributes;
}
} }
unset($arguments['attributes']); unset($arguments['attributes']);
+2
View File
@@ -9,6 +9,8 @@
namespace Grav\Common; namespace Grav\Common;
use function donatj\UserAgent\parse_user_agent;
/** /**
* Internally uses the PhpUserAgent package https://github.com/donatj/PhpUserAgent * Internally uses the PhpUserAgent package https://github.com/donatj/PhpUserAgent
*/ */
@@ -136,7 +136,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
{ {
$messages = $this->checkRequired($data, $rules); $messages = $this->checkRequired($data, $rules);
foreach ($data as $key => $field) { foreach ($data as $key => $child) {
$val = $rules[$key] ?? $rules['*'] ?? null; $val = $rules[$key] ?? $rules['*'] ?? null;
$rule = \is_string($val) ? $this->items[$val] : null; $rule = \is_string($val) ? $this->items[$val] : null;
@@ -147,10 +147,10 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
continue; continue;
} }
$messages += Validation::validate($field, $rule); $messages += Validation::validate($child, $rule);
} elseif (\is_array($field) && \is_array($val)) { } elseif (\is_array($child) && \is_array($val)) {
// Array has been defined in blueprints. // Array has been defined in blueprints.
$messages += $this->validateArray($field, $val); $messages += $this->validateArray($child, $val);
} elseif (isset($rules['validation']) && $rules['validation'] === 'strict') { } elseif (isset($rules['validation']) && $rules['validation'] === 'strict') {
// Undefined/extra item. // Undefined/extra item.
throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key)); throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));
+30 -2
View File
@@ -32,6 +32,12 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
/** @var File */ /** @var File */
protected $storage; protected $storage;
/** @var bool */
private $missingValuesAsNull = false;
/** @var bool */
private $keepEmptyValues = true;
/** /**
* @param array $items * @param array $items
* @param Blueprint|callable $blueprints * @param Blueprint|callable $blueprints
@@ -42,6 +48,28 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
$this->blueprints = $blueprints; $this->blueprints = $blueprints;
} }
/**
* @param bool $value
* @return $this
*/
public function setKeepEmptyValues(bool $value)
{
$this->keepEmptyValues = $value;
return $this;
}
/**
* @param bool $value
* @return $this
*/
public function setMissingValuesAsNull(bool $value)
{
$this->missingValuesAsNull = $value;
return $this;
}
/** /**
* Get value by using dot notation for nested arrays/objects. * Get value by using dot notation for nested arrays/objects.
* *
@@ -202,8 +230,8 @@ class Data implements DataInterface, \ArrayAccess, \Countable, \JsonSerializable
public function filter() public function filter()
{ {
$args = func_get_args(); $args = func_get_args();
$missingValuesAsNull = (bool)(array_shift($args) ?: false); $missingValuesAsNull = (bool)(array_shift($args) ?? $this->missingValuesAsNull);
$keepEmptyValues = (bool)(array_shift($args) ?: false); $keepEmptyValues = (bool)(array_shift($args) ?? $this->keepEmptyValues);
$this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues); $this->items = $this->blueprints()->filter($this->items, $missingValuesAsNull, $keepEmptyValues);
+10 -1
View File
@@ -166,9 +166,18 @@ class Validation
return (string) $value; return (string) $value;
} }
/**
* @param mixed $value
* @param array $params
* @param array $field
* @return string|null
*/
protected static function filterCheckbox($value, array $params, array $field) protected static function filterCheckbox($value, array $params, array $field)
{ {
return (bool) $value; $value = (string)$value;
$field_value = (string)($field['value'] ?? '1');
return $value === $field_value ? $value : null;
} }
protected static function filterCommaList($value, array $params, array $field) protected static function filterCommaList($value, array $params, array $field)
+1 -1
View File
@@ -347,7 +347,7 @@ class Debugger
*/ */
public function deprecatedErrorHandler($errno, $errstr, $errfile, $errline) public function deprecatedErrorHandler($errno, $errstr, $errfile, $errline)
{ {
if ($errno !== E_USER_DEPRECATED) { if ($errno !== E_USER_DEPRECATED && $errno !== E_DEPRECATED) {
if ($this->errorHandler) { if ($this->errorHandler) {
return \call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline); return \call_user_func($this->errorHandler, $errno, $errstr, $errfile, $errline);
} }
+16 -2
View File
@@ -22,6 +22,10 @@ abstract class Folder
*/ */
public static function lastModifiedFolder($path) public static function lastModifiedFolder($path)
{ {
if (!file_exists($path)) {
return 0;
}
$last_modified = 0; $last_modified = 0;
/** @var UniformResourceLocator $locator */ /** @var UniformResourceLocator $locator */
@@ -56,6 +60,10 @@ abstract class Folder
*/ */
public static function lastModifiedFile($path, $extensions = 'md|yaml') public static function lastModifiedFile($path, $extensions = 'md|yaml')
{ {
if (!file_exists($path)) {
return 0;
}
$last_modified = 0; $last_modified = 0;
/** @var UniformResourceLocator $locator */ /** @var UniformResourceLocator $locator */
@@ -92,9 +100,11 @@ abstract class Folder
*/ */
public static function hashAllFiles($path) public static function hashAllFiles($path)
{ {
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
$files = []; $files = [];
if (file_exists($path)) {
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
/** @var UniformResourceLocator $locator */ /** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator']; $locator = Grav::instance()['locator'];
if ($locator->isStream($path)) { if ($locator->isStream($path)) {
@@ -108,6 +118,7 @@ abstract class Folder
foreach ($iterator as $file) { foreach ($iterator as $file) {
$files[] = $file->getPathname() . '?'. $file->getMTime(); $files[] = $file->getPathname() . '?'. $file->getMTime();
} }
}
return md5(serialize($files)); return md5(serialize($files));
} }
@@ -199,6 +210,9 @@ abstract class Folder
if ($path === false) { if ($path === false) {
throw new \RuntimeException("Path doesn't exist."); throw new \RuntimeException("Path doesn't exist.");
} }
if (!file_exists($path)) {
return [];
}
$compare = isset($params['compare']) ? 'get' . $params['compare'] : null; $compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
$pattern = $params['pattern'] ?? null; $pattern = $params['pattern'] ?? null;
@@ -235,7 +249,7 @@ abstract class Folder
/** @var \RecursiveDirectoryIterator $file */ /** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) { foreach ($iterator as $file) {
// Ignore hidden files. // Ignore hidden files.
if (strpos($file->getFilename(), '.') === 0) { if (strpos($file->getFilename(), '.') === 0 && $file->isFile()) {
continue; continue;
} }
if (!$folders && $file->isDir()) { if (!$folders && $file->isDir()) {
+2 -1
View File
@@ -11,6 +11,7 @@ namespace Grav\Common\GPM\Local;
use Grav\Common\Data\Data; use Grav\Common\Data\Data;
use Grav\Common\GPM\Common\Package as BasePackage; use Grav\Common\GPM\Common\Package as BasePackage;
use Grav\Framework\Parsedown\Parsedown;
class Package extends BasePackage class Package extends BasePackage
{ {
@@ -23,7 +24,7 @@ class Package extends BasePackage
$this->settings = $package->toArray(); $this->settings = $package->toArray();
$html_description = \Parsedown::instance()->line($this->__get('description')); $html_description = Parsedown::instance()->line($this->__get('description'));
$this->data->set('slug', $package->__get('slug')); $this->data->set('slug', $package->__get('slug'));
$this->data->set('description_html', $html_description); $this->data->set('description_html', $html_description);
$this->data->set('description_plain', strip_tags($html_description)); $this->data->set('description_plain', strip_tags($html_description));
+1 -1
View File
@@ -409,7 +409,7 @@ class Response
} else { } else {
$code = (int)curl_getinfo($rch, CURLINFO_HTTP_CODE); $code = (int)curl_getinfo($rch, CURLINFO_HTTP_CODE);
if ($code === 301 || $code === 302 || $code === 303) { if ($code === 301 || $code === 302 || $code === 303) {
preg_match('/Location:(.*?)\n/', $header, $matches); preg_match('/(?:^|\n)Location:(.*?)\n/i', $header, $matches);
$uri = trim(array_pop($matches)); $uri = trim(array_pop($matches));
} else { } else {
$code = 0; $code = 0;
+47 -4
View File
@@ -170,6 +170,29 @@ class Grav extends Container
return $this; return $this;
} }
/**
* Initialize CLI environment.
*
* Call after `$grav->setup($environment)`
*
* - Load configuration
* - Disable debugger
* - Set timezone, locale
* - Load plugins
* - Set Users type to be used in the site
*
* This method WILL NOT initialize assets, twig or pages.
*
* @param string|null $environment
* @return $this
*/
public function initializeCli()
{
InitializeProcessor::initializeCli($this);
return $this;
}
/** /**
* Process a request * Process a request
*/ */
@@ -247,9 +270,21 @@ class Grav extends Container
$collection = new RequestHandler($this->middleware, $default, $container); $collection = new RequestHandler($this->middleware, $default, $container);
$response = $collection->handle($this['request']); $response = $collection->handle($this['request']);
$body = $response->getBody();
// Handle ETag and If-None-Match headers.
if ($response->getHeaderLine('ETag') === '1') {
$etag = md5($body);
$response = $response->withHeader('ETag', $etag);
if ($this['request']->getHeaderLine('If-None-Match') === $etag) {
$response = $response->withStatus(304);
$body = '';
}
}
$this->header($response); $this->header($response);
echo $response->getBody(); echo $body;
$debugger->render(); $debugger->render();
@@ -281,7 +316,10 @@ class Grav extends Container
/** @var Uri $uri */ /** @var Uri $uri */
$uri = $this['uri']; $uri = $this['uri'];
//Check for code in route // Clean route for redirect
$route = preg_replace("#^\/[\\\/]+\/#", '/', $route);
// Check for code in route
$regex = '/.*(\[(30[1-7])\])$/'; $regex = '/.*(\[(30[1-7])\])$/';
preg_match($regex, $route, $matches); preg_match($regex, $route, $matches);
if ($matches) { if ($matches) {
@@ -427,11 +465,16 @@ class Grav extends Container
* Used to call closures. * Used to call closures.
* *
* Source: http://stackoverflow.com/questions/419804/closures-as-class-members * Source: http://stackoverflow.com/questions/419804/closures-as-class-members
*
* @param string $method
* @param array $args
* @return
*/ */
public function __call($method, $args) public function __call($method, $args)
{ {
$closure = $this->{$method}; $closure = $this->{$method} ?? null;
\call_user_func_array($closure, $args);
return is_callable($closure) ? $closure(...$args) : null;
} }
/** /**
+2
View File
@@ -193,6 +193,8 @@ class Inflector
$regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2); $regex3 = preg_replace('/([0-9])([A-Z])/', '\1-\2', $regex2);
$regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3); $regex4 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex3);
$regex4 = trim($regex4, '-');
return strtolower($regex4); return strtolower($regex4);
} }
@@ -11,8 +11,9 @@ namespace Grav\Common\Markdown;
use Grav\Common\Page\Interfaces\PageInterface; use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Markdown\Excerpts; use Grav\Common\Page\Markdown\Excerpts;
use Grav\Framework\Parsedown\Parsedown as ParsedownLib;
class Parsedown extends \Parsedown class Parsedown extends ParsedownLib
{ {
use ParsedownGravTrait; use ParsedownGravTrait;
@@ -11,8 +11,9 @@ namespace Grav\Common\Markdown;
use Grav\Common\Page\Interfaces\PageInterface; use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Markdown\Excerpts; use Grav\Common\Page\Markdown\Excerpts;
use Grav\Framework\Parsedown\ParsedownExtra as ParsedownExtraLib;
class ParsedownExtra extends \ParsedownExtra class ParsedownExtra extends ParsedownExtraLib
{ {
use ParsedownGravTrait; use ParsedownGravTrait;
@@ -125,5 +125,5 @@ trait MediaTrait
/** /**
* @return string * @return string
*/ */
abstract protected function getCacheKey(); abstract protected function getCacheKey(): string;
} }
@@ -87,7 +87,7 @@ class Excerpts
); );
// Valid attributes supported. // Valid attributes supported.
$valid_attributes = ['rel', 'target', 'id', 'class', 'classes']; $valid_attributes = Grav::instance()['config']->get('system.pages.markdown.valid_link_attributes');
// Unless told to not process, go through actions. // Unless told to not process, go through actions.
if (array_key_exists('noprocess', $actions)) { if (array_key_exists('noprocess', $actions)) {
@@ -232,6 +232,7 @@ class Excerpts
$url_parts = is_string($url) ? $this->parseUrl($url) : $url; $url_parts = is_string($url) ? $this->parseUrl($url) : $url;
$actions = []; $actions = [];
// if there is a query, then parse it and build action calls // if there is a query, then parse it and build action calls
if (isset($url_parts['query'])) { if (isset($url_parts['query'])) {
$actions = array_reduce( $actions = array_reduce(
@@ -516,6 +516,15 @@ class ImageMedium extends Medium
return $this; return $this;
} }
/**
* Handle this commonly used variant
*/
public function cropZoom()
{
$this->__call('zoomCrop', func_get_args());
return $this;
}
/** /**
* Forward the call to the image processing method. * Forward the call to the image processing method.
* *
@@ -525,10 +534,6 @@ class ImageMedium extends Medium
*/ */
public function __call($method, $args) public function __call($method, $args)
{ {
if ($method === 'cropZoom') {
$method = 'zoomCrop';
}
if (!\in_array($method, self::$magic_actions, true)) { if (!\in_array($method, self::$magic_actions, true)) {
return parent::__call($method, $args); return parent::__call($method, $args);
} }
+14 -10
View File
@@ -529,9 +529,9 @@ class Page implements PageInterface
$headers['Last-Modified'] = $last_modified_date; $headers['Last-Modified'] = $last_modified_date;
} }
// Calculate ETag based on the raw file // Ask Grav to calculate ETag from the final content.
if ($this->eTag()) { if ($this->eTag()) {
$headers['ETag'] = '"' . md5($this->raw() . $this->modified()).'"'; $headers['ETag'] = '1';
} }
// Set Vary: Accept-Encoding header // Set Vary: Accept-Encoding header
@@ -608,12 +608,12 @@ class Page implements PageInterface
return $content; return $content;
} }
return mb_strimwidth($content, 0, $size, '...', 'utf-8'); return mb_strimwidth($content, 0, $size, '', 'UTF-8');
} }
$summary = Utils::truncateHtml($content, $size); $summary = Utils::truncateHtml($content, $size);
return html_entity_decode($summary); return html_entity_decode($summary, ENT_COMPAT | ENT_HTML401, 'UTF-8');
} }
/** /**
@@ -659,7 +659,7 @@ class Page implements PageInterface
// Load cached content // Load cached content
/** @var Cache $cache */ /** @var Cache $cache */
$cache = Grav::instance()['cache']; $cache = Grav::instance()['cache'];
$cache_id = md5('page' . $this->id()); $cache_id = md5('page' . $this->getCacheKey());
$content_obj = $cache->fetch($cache_id); $content_obj = $cache->fetch($cache_id);
if (is_array($content_obj)) { if (is_array($content_obj)) {
@@ -865,7 +865,7 @@ class Page implements PageInterface
public function cachePageContent() public function cachePageContent()
{ {
$cache = Grav::instance()['cache']; $cache = Grav::instance()['cache'];
$cache_id = md5('page' . $this->id()); $cache_id = md5('page' . $this->getCacheKey());
$cache->save($cache_id, ['content' => $this->content, 'content_meta' => $this->content_meta]); $cache->save($cache_id, ['content' => $this->content, 'content_meta' => $this->content_meta]);
} }
@@ -1200,7 +1200,7 @@ class Page implements PageInterface
/** /**
* @return string * @return string
*/ */
protected function getCacheKey() public function getCacheKey(): string
{ {
return $this->id(); return $this->id();
} }
@@ -1694,9 +1694,9 @@ class Page implements PageInterface
$metadata['generator'] = 'GravCMS'; $metadata['generator'] = 'GravCMS';
// Get initial metadata for the page // Get initial metadata for the page
$metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata')); $metadata = array_merge($metadata, Grav::instance()['config']->get('site.metadata', []));
if (isset($this->header->metadata)) { if (isset($this->header->metadata) && is_array($this->header->metadata)) {
// Merge any site.metadata settings in with page metadata // Merge any site.metadata settings in with page metadata
$metadata = array_merge($metadata, $this->header->metadata); $metadata = array_merge($metadata, $this->header->metadata);
} }
@@ -2009,6 +2009,10 @@ class Page implements PageInterface
*/ */
public function id($var = null) public function id($var = null)
{ {
if (null === $this->id) {
// We need to set unique id to avoid potential cache conflicts between pages.
$var = time() . md5($this->filePath());
}
if ($var !== null) { if ($var !== null) {
// store unique per language // store unique per language
$active_lang = Grav::instance()['language']->getLanguage() ?: ''; $active_lang = Grav::instance()['language']->getLanguage() ?: '';
@@ -2824,7 +2828,7 @@ class Page implements PageInterface
if ($pagination) { if ($pagination) {
$params = $collection->params(); $params = $collection->params();
$limit = $params['limit'] ?? 0; $limit = (int)($params['limit'] ?? 0);
$start = !empty($params['pagination']) ? ($uri->currentPage() - 1) * $limit : 0; $start = !empty($params['pagination']) ? ($uri->currentPage() - 1) * $limit : 0;
if ($limit && $collection->count() > $limit) { if ($limit && $collection->count() > $limit) {
+40 -20
View File
@@ -95,6 +95,8 @@ class Pages
protected $initialized = false; protected $initialized = false;
protected $active_lang;
/** /**
* @var Types * @var Types
*/ */
@@ -143,7 +145,7 @@ class Pages
*/ */
public function baseRoute($lang = null) public function baseRoute($lang = null)
{ {
$key = $lang ?: 'default'; $key = $lang ?: $this->active_lang ?: 'default';
if (!isset($this->baseRoute[$key])) { if (!isset($this->baseRoute[$key])) {
/** @var Language $language */ /** @var Language $language */
@@ -236,6 +238,16 @@ class Pages
$this->check_method = strtolower($method); $this->check_method = strtolower($method);
} }
/**
* Reset pages (used in search indexing etc).
*/
public function reset()
{
$this->initialized = false;
$this->init();
}
/** /**
* Class initialization. Must be called before using this class. * Class initialization. Must be called before using this class.
*/ */
@@ -958,6 +970,9 @@ class Pages
$pages_dir = $locator->findResource('page://'); $pages_dir = $locator->findResource('page://');
// Set active language
$this->active_lang = $language->getActive();
if ($config->get('system.cache.enabled')) { if ($config->get('system.cache.enabled')) {
/** @var Cache $cache */ /** @var Cache $cache */
$cache = $this->grav['cache']; $cache = $this->grav['cache'];
@@ -982,17 +997,19 @@ class Pages
$this->pages_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum()); $this->pages_cache_id = md5($pages_dir . $hash . $language->getActive() . $config->checksum());
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($this->pages_cache_id); $cached = $cache->fetch($this->pages_cache_id);
if (!$this->instances) { if ($cached) {
$this->grav['debugger']->addMessage('Page cache hit.');
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cached;
// If pages was found in cache, set the taxonomy
$taxonomy->taxonomy($taxonomy_map);
} else {
$this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..'); $this->grav['debugger']->addMessage('Page cache missed, rebuilding pages..');
// recurse pages and cache result // recurse pages and cache result
$this->resetPages($pages_dir); $this->resetPages($pages_dir);
} else {
// If pages was found in cache, set the taxonomy
$this->grav['debugger']->addMessage('Page cache hit.');
$taxonomy->taxonomy($taxonomy_map);
} }
} else { } else {
$this->recurse($pages_dir); $this->recurse($pages_dir);
@@ -1261,14 +1278,13 @@ class Pages
{ {
$list = []; $list = [];
$header_default = null; $header_default = null;
$header_query = null; $header_query = [];
// do this header query work only once // do this header query work only once
if (strpos($order_by, 'header.') === 0) { if (strpos($order_by, 'header.') === 0) {
$header_query = explode('|', str_replace('header.', '', $order_by)); $query = explode('|', str_replace('header.', '', $order_by), 2);
if (isset($header_query[1])) { $header_query = array_shift($query) ?? '';
$header_default = $header_query[1]; $header_default = array_shift($query);
}
} }
foreach ($pages as $key => $info) { foreach ($pages as $key => $info) {
@@ -1306,11 +1322,17 @@ class Pages
case 'folder': case 'folder':
$list[$key] = $child->folder(); $list[$key] = $child->folder();
break; break;
case (is_string($header_query[0])): case 'manual':
$child_header = new Header((array)$child->header()); case 'default':
$header_value = $child_header->get($header_query[0]); default:
if (is_string($header_query)) {
$child_header = $child->header();
if (!$child_header instanceof Header) {
$child_header = new Header((array)$child_header);
}
$header_value = $child_header->get($header_query);
if (is_array($header_value)) { if (is_array($header_value)) {
$list[$key] = implode(',',$header_value); $list[$key] = implode(',', $header_value);
} elseif ($header_value) { } elseif ($header_value) {
$list[$key] = $header_value; $list[$key] = $header_value;
} else { } else {
@@ -1318,9 +1340,7 @@ class Pages
} }
$sort_flags = $sort_flags ?: SORT_REGULAR; $sort_flags = $sort_flags ?: SORT_REGULAR;
break; break;
case 'manual': }
case 'default':
default:
$list[$key] = $key; $list[$key] = $key;
$sort_flags = $sort_flags ?: SORT_REGULAR; $sort_flags = $sort_flags ?: SORT_REGULAR;
} }
+1 -1
View File
@@ -305,7 +305,7 @@ class Plugin implements EventSubscriberInterface, \ArrayAccess
// Create new config object and set it on the page object so it's cached for next time // Create new config object and set it on the page object so it's cached for next time
$page->modifyHeader($class_name_merged, new Data($header)); $page->modifyHeader($class_name_merged, new Data($header));
} else if (isset($page_header->{$class_name_merged})) { } elseif (isset($page_header->{$class_name_merged})) {
$merged = $page_header->{$class_name_merged}; $merged = $page_header->{$class_name_merged};
$header = $merged->toArray(); $header = $merged->toArray();
} }
@@ -10,6 +10,7 @@
namespace Grav\Common\Processors; namespace Grav\Common\Processors;
use Grav\Common\Config\Config; use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Uri; use Grav\Common\Uri;
use Grav\Common\Utils; use Grav\Common\Utils;
use Grav\Framework\Session\Exceptions\SessionException; use Grav\Framework\Session\Exceptions\SessionException;
@@ -22,6 +23,22 @@ class InitializeProcessor extends ProcessorBase
public $id = 'init'; public $id = 'init';
public $title = 'Initialize'; public $title = 'Initialize';
/** @var bool */
private static $cli_initialized = false;
/**
* @param Grav $grav
*/
public static function initializeCli(Grav $grav)
{
if (!static::$cli_initialized) {
static::$cli_initialized = true;
$instance = new static($grav);
$instance->processCli();
}
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{ {
$this->startTimer(); $this->startTimer();
@@ -77,4 +94,35 @@ class InitializeProcessor extends ProcessorBase
return $handler->handle($request); return $handler->handle($request);
} }
public function processCli(): void
{
// Load configuration.
$this->container['config']->init();
$this->container['plugins']->setup();
// Disable debugger.
$this->container['debugger']->enabled(false);
// Set timezone, locale.
/** @var Config $config */
$config = $this->container['config'];
$timezone = $config->get('system.timezone');
if ($timezone) {
date_default_timezone_set($timezone);
}
$this->container->setLocale();
// Load plugins.
$this->container['plugins']->init();
// Initialize URI.
/** @var Uri $uri */
$uri = $this->container['uri'];
$uri->init();
// Load accounts.
// TODO: remove in 2.0.
$this->container['accounts'];
}
} }
+2 -2
View File
@@ -69,7 +69,7 @@ class Cron
'name_year' => 'année', 'name_year' => 'année',
'text_period' => 'Chaque %s', 'text_period' => 'Chaque %s',
'text_mins' => 'à %s minutes', 'text_mins' => 'à %s minutes',
'text_time' => 'à %s:%s', 'text_time' => 'à %02s:%02s',
'text_dow' => 'le %s', 'text_dow' => 'le %s',
'text_month' => 'de %s', 'text_month' => 'de %s',
'text_dom' => 'le %s', 'text_dom' => 'le %s',
@@ -86,7 +86,7 @@ class Cron
'name_year' => 'year', 'name_year' => 'year',
'text_period' => 'Every %s', 'text_period' => 'Every %s',
'text_mins' => 'at %s minutes past the hour', 'text_mins' => 'at %s minutes past the hour',
'text_time' => 'at %s:%s', 'text_time' => 'at %02s:%02s',
'text_dow' => 'on %s', 'text_dow' => 'on %s',
'text_month' => 'of %s', 'text_month' => 'of %s',
'text_dom' => 'on the %s', 'text_dom' => 'on the %s',
@@ -26,6 +26,19 @@ class PagesServiceProvider implements ServiceProviderInterface
return new Pages($c); return new Pages($c);
}; };
if (\defined('GRAV_CLI')) {
$container['page'] = static function ($c) {
$path = $c['locator']->findResource('system://pages/notfound.md');
$page = new Page();
$page->init(new \SplFileInfo($path));
$page->routable(false);
return $page;
};
return;
}
$container['page'] = function ($c) { $container['page'] = function ($c) {
/** @var Grav $c */ /** @var Grav $c */
@@ -1055,7 +1055,7 @@ class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsIn
*/ */
public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0) public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0)
{ {
return json_decode(html_entity_decode($str), $assoc, $depth, $options); return json_decode(html_entity_decode($str, ENT_COMPAT | ENT_HTML401, 'UTF-8'), $assoc, $depth, $options);
} }
/** /**
+22 -17
View File
@@ -151,7 +151,7 @@ class Uri
$this->url = $this->base . $this->uri; $this->url = $this->base . $this->uri;
$uri = str_replace(static::filterPath($this->root), '', $this->url); $uri = Utils::replaceFirstOccurrence(static::filterPath($this->root), '', $this->url);
// remove the setup.php based base if set: // remove the setup.php based base if set:
$setup_base = $grav['pages']->base(); $setup_base = $grav['pages']->base();
@@ -195,7 +195,7 @@ class Uri
// set the new url // set the new url
$this->url = $this->root . $path; $this->url = $this->root . $path;
$this->path = static::cleanPath($path); $this->path = static::cleanPath($path);
$this->content_path = trim(str_replace($this->base, '', $this->path), '/'); $this->content_path = trim(Utils::replaceFirstOccurrence($this->base, '', $this->path), '/');
if ($this->content_path !== '') { if ($this->content_path !== '') {
$this->paths = explode('/', $this->content_path); $this->paths = explode('/', $this->content_path);
} }
@@ -306,7 +306,7 @@ class Uri
public function param($id) public function param($id)
{ {
if (isset($this->params[$id])) { if (isset($this->params[$id])) {
return html_entity_decode(rawurldecode($this->params[$id])); return html_entity_decode(rawurldecode($this->params[$id]), ENT_COMPAT | ENT_HTML401, 'UTF-8');
} }
return false; return false;
@@ -340,7 +340,7 @@ class Uri
return $this->url; return $this->url;
} }
$url = str_replace($this->base, '', rtrim($this->url, '/')); $url = Utils::replaceFirstOccurrence($this->base, '', rtrim($this->url, '/'));
return $url ?: '/'; return $url ?: '/';
} }
@@ -489,7 +489,7 @@ class Uri
return $this->uri; return $this->uri;
} }
return str_replace($this->root_path, '', $this->uri); return Utils::replaceFirstOccurrence($this->root_path, '', $this->uri);
} }
/** /**
@@ -531,7 +531,7 @@ class Uri
return $this->root; return $this->root;
} }
return str_replace($this->base, '', $this->root); return Utils::replaceFirstOccurrence($this->base, '', $this->root);
} }
/** /**
@@ -541,7 +541,9 @@ class Uri
*/ */
public function currentPage() public function currentPage()
{ {
return $this->params['page'] ?? 1; $page = (int)($this->params['page'] ?? 1);
return max(1, $page);
} }
/** /**
@@ -629,9 +631,9 @@ class Uri
{ {
if (getenv('HTTP_CLIENT_IP')) { if (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP'); $ip = getenv('HTTP_CLIENT_IP');
} elseif (getenv('HTTP_X_FORWARDED_FOR')) { } elseif (getenv('HTTP_X_FORWARDED_FOR') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
$ip = getenv('HTTP_X_FORWARDED_FOR'); $ip = getenv('HTTP_X_FORWARDED_FOR');
} elseif (getenv('HTTP_X_FORWARDED')) { } elseif (getenv('HTTP_X_FORWARDED') && Grav::instance()['config']->get('system.http_x_forwarded.ip')) {
$ip = getenv('HTTP_X_FORWARDED'); $ip = getenv('HTTP_X_FORWARDED');
} elseif (getenv('HTTP_FORWARDED_FOR')) { } elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip = getenv('HTTP_FORWARDED_FOR'); $ip = getenv('HTTP_FORWARDED_FOR');
@@ -783,7 +785,7 @@ class Uri
} }
// special check to see if path checking is required. // special check to see if path checking is required.
$just_path = str_replace($normalized_url, '', $normalized_path); $just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
if ($normalized_url === '/' || $just_path === $page->path()) { if ($normalized_url === '/' || $just_path === $page->path()) {
$url_path = $normalized_url; $url_path = $normalized_url;
} else { } else {
@@ -852,7 +854,7 @@ class Uri
} }
// strip base from this path // strip base from this path
$target_path = str_replace($uri->rootUrl(), '', $target_path); $target_path = Utils::replaceFirstOccurrence($uri->rootUrl(), '', $target_path);
// set to / if root // set to / if root
if (empty($target_path)) { if (empty($target_path)) {
@@ -877,7 +879,7 @@ class Uri
// Handle route only // Handle route only
if ($route_only) { if ($route_only) {
$url_path = str_replace(static::filterPath($base_url), '', $url_path); $url_path = Utils::replaceFirstOccurrence(static::filterPath($base_url), '', $url_path);
} }
// transform back to string/array as needed // transform back to string/array as needed
@@ -998,7 +1000,7 @@ class Uri
} }
// special check to see if path checking is required. // special check to see if path checking is required.
$just_path = str_replace($normalized_url, '', $normalized_path); $just_path = Utils::replaceFirstOccurrence($normalized_url, '', $normalized_path);
if ($just_path === $page->path()) { if ($just_path === $page->path()) {
return $normalized_url; return $normalized_url;
} }
@@ -1148,7 +1150,7 @@ class Uri
protected function createFromEnvironment(array $env) protected function createFromEnvironment(array $env)
{ {
// Build scheme. // Build scheme.
if (isset($env['HTTP_X_FORWARDED_PROTO'])) { if (isset($env['HTTP_X_FORWARDED_PROTO']) && Grav::instance()['config']->get('system.http_x_forwarded.protocol')) {
$this->scheme = $env['HTTP_X_FORWARDED_PROTO']; $this->scheme = $env['HTTP_X_FORWARDED_PROTO'];
} elseif (isset($env['X-FORWARDED-PROTO'])) { } elseif (isset($env['X-FORWARDED-PROTO'])) {
$this->scheme = $env['X-FORWARDED-PROTO']; $this->scheme = $env['X-FORWARDED-PROTO'];
@@ -1166,11 +1168,14 @@ class Uri
$this->password = $env['PHP_AUTH_PW'] ?? null; $this->password = $env['PHP_AUTH_PW'] ?? null;
// Build host. // Build host.
$hostname = 'localhost'; if (isset($env['HTTP_X_FORWARDED_HOST']) && Grav::instance()['config']->get('system.http_x_forwarded.host')) {
if (isset($env['HTTP_HOST'])) { $hostname = $env['HTTP_X_FORWARDED_HOST'];
} else if (isset($env['HTTP_HOST'])) {
$hostname = $env['HTTP_HOST']; $hostname = $env['HTTP_HOST'];
} elseif (isset($env['SERVER_NAME'])) { } elseif (isset($env['SERVER_NAME'])) {
$hostname = $env['SERVER_NAME']; $hostname = $env['SERVER_NAME'];
} else {
$hostname = 'localhost';
} }
// Remove port from HTTP_HOST generated $hostname // Remove port from HTTP_HOST generated $hostname
$hostname = Utils::substrToString($hostname, ':'); $hostname = Utils::substrToString($hostname, ':');
@@ -1178,7 +1183,7 @@ class Uri
$this->host = $this->validateHostname($hostname) ? $hostname : 'unknown'; $this->host = $this->validateHostname($hostname) ? $hostname : 'unknown';
// Build port. // Build port.
if (isset($env['HTTP_X_FORWARDED_PORT'])) { if (isset($env['HTTP_X_FORWARDED_PORT']) && Grav::instance()['config']->get('system.http_x_forwarded.port')) {
$this->port = (int)$env['HTTP_X_FORWARDED_PORT']; $this->port = (int)$env['HTTP_X_FORWARDED_PORT'];
} elseif (isset($env['X-FORWARDED-PORT'])) { } elseif (isset($env['X-FORWARDED-PORT'])) {
$this->port = (int)$env['X-FORWARDED-PORT']; $this->port = (int)$env['X-FORWARDED-PORT'];
+140 -1
View File
@@ -10,6 +10,10 @@
namespace Grav\Console; namespace Grav\Console;
use Grav\Common\Grav; use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Page;
use Grav\Common\Processors\InitializeProcessor;
use RocketTheme\Toolbox\Event\Event;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@@ -18,6 +22,13 @@ class ConsoleCommand extends Command
{ {
use ConsoleTrait; use ConsoleTrait;
/** @var bool */
private $plugins_initialized = false;
/** @var bool */
private $themes_initialized = false;
/** @var bool */
private $pages_initialized = false;
/** /**
* @param InputInterface $input * @param InputInterface $input
* @param OutputInterface $output * @param OutputInterface $output
@@ -31,12 +42,140 @@ class ConsoleCommand extends Command
} }
/** /**
* * Override with your implementation.
*/ */
protected function serve() protected function serve()
{ {
} }
/**
* Initialize Grav.
*
* - Load configuration
* - Disable debugger
* - Set timezone, locale
* - Load plugins
* - Set Users type to be used in the site
*
* Safe to be called multiple times.
*
* @return $this
*/
final protected function initializeGrav()
{
InitializeProcessor::initializeCli(Grav::instance());
return $this;
}
/**
* Set language to be used in CLI.
*
* @param string|null $code
*/
final protected function setLanguage(string $code = null)
{
$this->initializeGrav();
$grav = Grav::instance();
/** @var Language $language */
$language = $grav['language'];
if ($language->enabled()) {
if ($code && $language->validate($code)) {
$language->setActive($code);
} else {
$language->setActive($language->getDefault());
}
}
}
/**
* Properly initialize plugins.
*
* - call $this->initializeGrav()
* - call onPluginsInitialized event
*
* Safe to be called multiple times.
*
* @return $this
*/
final protected function initializePlugins()
{
if (!$this->plugins_initialized) {
$this->plugins_initialized = true;
$this->initializeGrav();
// Initialize plugins.
$grav = Grav::instance();
$grav->fireEvent('onPluginsInitialized');
}
return $this;
}
/**
* Properly initialize themes.
*
* - call $this->initializePlugins()
* - initialize theme (call onThemeInitialized event)
*
* Safe to be called multiple times.
*
* @return $this
*/
final protected function initializeThemes()
{
if (!$this->themes_initialized) {
$this->themes_initialized = true;
$this->initializePlugins();
// Initialize themes.
$grav = Grav::instance();
$grav['themes']->init();
}
return $this;
}
/**
* Properly initialize pages.
*
* - call $this->initializeThemes()
* - initialize assets (call onAssetsInitialized event)
* - initialize twig (calls the twig events)
* - initialize pages (calls onPagesInitialized event)
*
* Safe to be called multiple times.
*
* @return $this
*/
final protected function initializePages()
{
if (!$this->pages_initialized) {
$this->pages_initialized = true;
$this->initializeThemes();
$grav = Grav::instance();
// Initialize assets.
$grav['assets']->init();
$grav->fireEvent('onAssetsInitialized');
// Initialize twig.
$grav['twig']->init();
// Initialize pages.
$pages = $grav['pages'];
$pages->init();
$grav->fireEvent('onPagesInitialized', new Event(['pages' => $pages]));
}
return $this;
}
protected function displayGPMRelease() protected function displayGPMRelease()
{ {
$this->output->writeln(''); $this->output->writeln('');
@@ -177,7 +177,7 @@ class SelfupgradeCommand extends ConsoleCommand
} }
/** /**
* @param Package $package * @param array $package
* *
* @return string * @return string
*/ */
@@ -79,10 +79,28 @@ class CsvFormatter extends AbstractFormatter
// Get the field names // Get the field names
$header = str_getcsv(array_shift($lines), $delimiter); $header = str_getcsv(array_shift($lines), $delimiter);
// Allow for replacing a null string with null/empty value
$null_replace = $this->getConfig('null');
// Get the data // Get the data
$list = []; $list = [];
$line = null;
try {
foreach ($lines as $line) { foreach ($lines as $line) {
$list[] = array_combine($header, str_getcsv($line, $delimiter)); if (!empty($line)) {
$csv_line = str_getcsv($line, $delimiter);
if ($null_replace) {
array_walk($csv_line, function(&$el) use ($null_replace) {
$el = str_replace($null_replace, "\0", $el);
});
}
$list[] = array_combine($header, $csv_line);
}
}
} catch (\Exception $e) {
throw new \Exception('Badly formatted CSV line: ' . $line);
} }
return $list; return $list;
@@ -342,5 +342,5 @@ trait FlexMediaTrait
abstract public function getFlexDirectory(): FlexDirectory; abstract public function getFlexDirectory(): FlexDirectory;
abstract public function getStorageKey(); abstract public function getStorageKey(): string;
} }
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,532 @@
<?php
/**
* @package Grav\Framework\Parsedown
*
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Parsedown;
/*
* Parsedown Extra
* http://parsedown.org
*
* (c) Emanuil Rusev
* http://erusev.com
*
* This file ported from officiall ParsedownExtra repo and kept for compatibility.
*/
class ParsedownExtra extends Parsedown
{
# ~
const version = '0.7.0';
# ~
function __construct()
{
if (parent::version < '1.5.0')
{
throw new Exception('ParsedownExtra requires a later version of Parsedown');
}
$this->BlockTypes[':'] []= 'DefinitionList';
$this->BlockTypes['*'] []= 'Abbreviation';
# identify footnote definitions before reference definitions
array_unshift($this->BlockTypes['['], 'Footnote');
# identify footnote markers before before links
array_unshift($this->InlineTypes['['], 'FootnoteMarker');
}
#
# ~
function text($text)
{
$markup = parent::text($text);
# merge consecutive dl elements
$markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
# add footnotes
if (isset($this->DefinitionData['Footnote']))
{
$Element = $this->buildFootnoteElement();
$markup .= "\n" . $this->element($Element);
}
return $markup;
}
#
# Blocks
#
#
# Abbreviation
protected function blockAbbreviation($Line)
{
if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
{
$this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
$Block = array(
'hidden' => true,
);
return $Block;
}
}
#
# Footnote
protected function blockFootnote($Line)
{
if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
{
$Block = array(
'label' => $matches[1],
'text' => $matches[2],
'hidden' => true,
);
return $Block;
}
}
protected function blockFootnoteContinue($Line, $Block)
{
if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
{
return;
}
if (isset($Block['interrupted']))
{
if ($Line['indent'] >= 4)
{
$Block['text'] .= "\n\n" . $Line['text'];
return $Block;
}
}
else
{
$Block['text'] .= "\n" . $Line['text'];
return $Block;
}
}
protected function blockFootnoteComplete($Block)
{
$this->DefinitionData['Footnote'][$Block['label']] = array(
'text' => $Block['text'],
'count' => null,
'number' => null,
);
return $Block;
}
#
# Definition List
protected function blockDefinitionList($Line, $Block)
{
if ( ! isset($Block) or isset($Block['type']))
{
return;
}
$Element = array(
'name' => 'dl',
'handler' => 'elements',
'text' => array(),
);
$terms = explode("\n", $Block['element']['text']);
foreach ($terms as $term)
{
$Element['text'] []= array(
'name' => 'dt',
'handler' => 'line',
'text' => $term,
);
}
$Block['element'] = $Element;
$Block = $this->addDdElement($Line, $Block);
return $Block;
}
protected function blockDefinitionListContinue($Line, array $Block)
{
if ($Line['text'][0] === ':')
{
$Block = $this->addDdElement($Line, $Block);
return $Block;
}
else
{
if (isset($Block['interrupted']) and $Line['indent'] === 0)
{
return;
}
if (isset($Block['interrupted']))
{
$Block['dd']['handler'] = 'text';
$Block['dd']['text'] .= "\n\n";
unset($Block['interrupted']);
}
$text = substr($Line['body'], min($Line['indent'], 4));
$Block['dd']['text'] .= "\n" . $text;
return $Block;
}
}
#
# Header
protected function blockHeader($Line)
{
$Block = parent::blockHeader($Line);
if ($Block !== null && preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
}
return $Block;
}
#
# Markup
protected function blockMarkupComplete($Block)
{
if ( ! isset($Block['void']))
{
$Block['markup'] = $this->processTag($Block['markup']);
}
return $Block;
}
#
# Setext
protected function blockSetextHeader($Line, array $Block = null)
{
$Block = parent::blockSetextHeader($Line, $Block);
if ($Block !== null && preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['text'], $matches, PREG_OFFSET_CAPTURE))
{
$attributeString = $matches[1][0];
$Block['element']['attributes'] = $this->parseAttributeData($attributeString);
$Block['element']['text'] = substr($Block['element']['text'], 0, $matches[0][1]);
}
return $Block;
}
#
# Inline Elements
#
#
# Footnote Marker
protected function inlineFootnoteMarker($Excerpt)
{
if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
{
$name = $matches[1];
if ( ! isset($this->DefinitionData['Footnote'][$name]))
{
return;
}
$this->DefinitionData['Footnote'][$name]['count'] ++;
if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
{
$this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
}
$Element = array(
'name' => 'sup',
'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
'handler' => 'element',
'text' => array(
'name' => 'a',
'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
'text' => $this->DefinitionData['Footnote'][$name]['number'],
),
);
return array(
'extent' => strlen($matches[0]),
'element' => $Element,
);
}
}
private $footnoteCount = 0;
#
# Link
protected function inlineLink($Excerpt)
{
$Link = parent::inlineLink($Excerpt);
$remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : '';
if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
{
$Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
$Link['extent'] += strlen($matches[0]);
}
return $Link;
}
#
# ~
#
protected function unmarkedText($text)
{
$text = parent::unmarkedText($text);
if (isset($this->DefinitionData['Abbreviation']))
{
foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
{
$pattern = '/\b'.preg_quote($abbreviation, '/').'\b/';
$text = preg_replace($pattern, '<abbr title="'.$meaning.'">'.$abbreviation.'</abbr>', $text);
}
}
return $text;
}
#
# Util Methods
#
protected function addDdElement(array $Line, array $Block)
{
$text = substr($Line['text'], 1);
$text = trim($text);
unset($Block['dd']);
$Block['dd'] = array(
'name' => 'dd',
'handler' => 'line',
'text' => $text,
);
if (isset($Block['interrupted']))
{
$Block['dd']['handler'] = 'text';
unset($Block['interrupted']);
}
$Block['element']['text'] []= & $Block['dd'];
return $Block;
}
protected function buildFootnoteElement()
{
$Element = array(
'name' => 'div',
'attributes' => array('class' => 'footnotes'),
'handler' => 'elements',
'text' => array(
array(
'name' => 'hr',
),
array(
'name' => 'ol',
'handler' => 'elements',
'text' => array(),
),
),
);
uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
{
if ( ! isset($DefinitionData['number']))
{
continue;
}
$text = $DefinitionData['text'];
$text = parent::text($text);
$numbers = range(1, $DefinitionData['count']);
$backLinksMarkup = '';
foreach ($numbers as $number)
{
$backLinksMarkup .= ' <a href="#fnref'.$number.':'.$definitionId.'" rev="footnote" class="footnote-backref">&#8617;</a>';
}
$backLinksMarkup = substr($backLinksMarkup, 1);
if (substr($text, - 4) === '</p>')
{
$backLinksMarkup = '&#160;'.$backLinksMarkup;
$text = substr_replace($text, $backLinksMarkup.'</p>', - 4);
}
else
{
$text .= "\n".'<p>'.$backLinksMarkup.'</p>';
}
$Element['text'][1]['text'] []= array(
'name' => 'li',
'attributes' => array('id' => 'fn:'.$definitionId),
'text' => "\n".$text."\n",
);
}
return $Element;
}
# ~
protected function parseAttributeData($attributeString)
{
$Data = array();
$attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
foreach ($attributes as $attribute)
{
if ($attribute[0] === '#')
{
$Data['id'] = substr($attribute, 1);
}
else # "."
{
$classes []= substr($attribute, 1);
}
}
if (isset($classes))
{
$Data['class'] = implode(' ', $classes);
}
return $Data;
}
# ~
protected function processTag($elementMarkup) # recursive
{
# http://stackoverflow.com/q/1148928/200145
libxml_use_internal_errors(true);
$DOMDocument = new \DOMDocument;
# http://stackoverflow.com/q/11309194/200145
$elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
# http://stackoverflow.com/q/4879946/200145
$DOMDocument->loadHTML($elementMarkup);
$DOMDocument->removeChild($DOMDocument->doctype);
$DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
$elementText = '';
if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
{
foreach ($DOMDocument->documentElement->childNodes as $Node)
{
$elementText .= $DOMDocument->saveHTML($Node);
}
$DOMDocument->documentElement->removeAttribute('markdown');
$elementText = "\n".$this->text($elementText)."\n";
}
else
{
foreach ($DOMDocument->documentElement->childNodes as $Node)
{
$nodeMarkup = $DOMDocument->saveHTML($Node);
if ($Node instanceof \DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
{
$elementText .= $this->processTag($nodeMarkup);
}
else
{
$elementText .= $nodeMarkup;
}
}
}
# because we don't want for markup to get encoded
$DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
$markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
$markup = str_replace('placeholder\x1A', $elementText, $markup);
return $markup;
}
# ~
protected function sortFootnotes($A, $B) # callback
{
return $A['number'] - $B['number'];
}
#
# Fields
#
protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
}
+2 -3
View File
@@ -286,7 +286,7 @@ class Route
$url .= '?' . $this->getUriQuery(); $url .= '?' . $this->getUriQuery();
} }
return $url; return rtrim($url,'/');
} }
/** /**
@@ -344,9 +344,8 @@ class Route
$parts[] = $this->language; $parts[] = $this->language;
} }
if ($this->route !== '') {
$parts[] = $this->extension ? $this->route . '.' . $this->extension : $this->route; $parts[] = $this->extension ? $this->route . '.' . $this->extension : $this->route;
}
if ($this->gravParams) { if ($this->gravParams) {
$parts[] = RouteFactory::buildParams($this->gravParams); $parts[] = RouteFactory::buildParams($this->gravParams);
+21 -15
View File
@@ -9,6 +9,7 @@
namespace Grav\Framework\Session; namespace Grav\Framework\Session;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Framework\Session\Exceptions\SessionException; use Grav\Framework\Session\Exceptions\SessionException;
/** /**
@@ -17,16 +18,13 @@ use Grav\Framework\Session\Exceptions\SessionException;
*/ */
class Session implements SessionInterface class Session implements SessionInterface
{ {
protected $options; /** @var array */
protected $options = [];
/** /** @var bool */
* @var bool
*/
protected $started = false; protected $started = false;
/** /** @var Session */
* @var Session
*/
protected static $instance; protected static $instance;
/** /**
@@ -178,9 +176,13 @@ class Session implements SessionInterface
return $this; return $this;
} }
$sessionName = session_name();
$sessionExists = isset($_COOKIE[$sessionName]);
// Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836 // Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836
if (isset($_COOKIE[session_name()]) && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[session_name()])) { if ($sessionExists && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[$sessionName])) {
unset($_COOKIE[session_name()]); unset($_COOKIE[$sessionName]);
$sessionExists = false;
} }
$options = $this->options; $options = $this->options;
@@ -197,15 +199,20 @@ class Session implements SessionInterface
throw new SessionException('Failed to start session: ' . $error, 500); throw new SessionException('Failed to start session: ' . $error, 500);
} }
if ($user && !$user->isValid()) { $this->started = true;
$this->clear();
throw new SessionException('User Invalid', 500); if ($user && (!$user instanceof UserInterface || !$user->isValid())) {
$this->invalidate();
throw new SessionException('Invalid User object, session destroyed.', 500);
} }
// Extend the lifetime of the session.
if ($sessionExists) {
$params = session_get_cookie_params(); $params = session_get_cookie_params();
setcookie( setcookie(
session_name(), $sessionName,
session_id(), session_id(),
time() + $params['lifetime'], time() + $params['lifetime'],
$params['path'], $params['path'],
@@ -213,8 +220,7 @@ class Session implements SessionInterface
$params['secure'], $params['secure'],
$params['httponly'] $params['httponly']
); );
}
$this->started = true;
return $this; return $this;
} }
+1 -1
View File
@@ -23,7 +23,7 @@ $HTTP["querystring"] =~ "_REQUEST(=|\[|\%[0-9A-Z])" {
#REROUTING TO THE INDEX PAGE #REROUTING TO THE INDEX PAGE
url.rewrite-if-not-file = ( url.rewrite-if-not-file = (
"^/grav_path/(.*)$" => "/grav_path/index.php$1" "^/grav_path/(.*)$" => "/grav_path/index.php?$1"
) )
#IMPROVING SECURITY #IMPROVING SECURITY
+81 -57
View File
@@ -2,7 +2,7 @@
# You can override ddev's configuration by placing an edited copy # You can override ddev's configuration by placing an edited copy
# of this config (or one of the other ones) in .ddev/nginx-site.conf # of this config (or one of the other ones) in .ddev/nginx-site.conf
# See https://ddev.readthedocs.io/en/latest/users/extend/customization-extendibility/#providing-custom-nginx-configuration # See https://ddev.readthedocs.io/en/stable/users/extend/customization-extendibility/#providing-custom-nginx-configuration
# Set https to 'on' if x-forwarded-proto is https # Set https to 'on' if x-forwarded-proto is https
map $http_x_forwarded_proto $fcgi_https { map $http_x_forwarded_proto $fcgi_https {
@@ -11,11 +11,16 @@ map $http_x_forwarded_proto $fcgi_https {
} }
server { server {
listen 80; ## listen for ipv4; this line is default and implied listen 80;
listen [::]:80 default ipv6only=on; ## listen for ipv6 listen [::]:80 default ipv6only=on;
# The NGINX_DOCROOT variable is substituted with
# The WEBSERVER_DOCROOT variable is substituted with
# its value when the container is started. # its value when the container is started.
root $NGINX_DOCROOT; root $WEBSERVER_DOCROOT;
include /etc/nginx/monitoring.conf;
index index.php index.htm index.html; index index.php index.htm index.html;
# Make site accessible from http://localhost/ # Make site accessible from http://localhost/
@@ -23,13 +28,18 @@ server {
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html # Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
sendfile off; sendfile off;
error_log /var/log/nginx/error.log info; error_log /dev/stdout info;
access_log /var/log/nginx/access.log; access_log /var/log/nginx/access.log;
## Begin - Index
# for subfolders, simply adjust:
# `location /subfolder {`
# and the rewrite to use `/subfolder/index.php`
location / { location / {
absolute_redirect off;
try_files $uri $uri/ /index.php?$query_string; try_files $uri $uri/ /index.php?$query_string;
} }
## End - Index
# pass the PHP scripts to FastCGI server listening on socket # pass the PHP scripts to FastCGI server listening on socket
location ~ \.php$ { location ~ \.php$ {
@@ -42,38 +52,78 @@ server {
fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php; fastcgi_index index.php;
include fastcgi_params; include fastcgi_params;
fastcgi_intercept_errors on; fastcgi_intercept_errors off;
# fastcgi_read_timeout should match max_execution_time in php.ini # fastcgi_read_timeout should match max_execution_time in php.ini
fastcgi_read_timeout 10m; fastcgi_read_timeout 10m;
fastcgi_param SERVER_NAME $host; fastcgi_param SERVER_NAME $host;
fastcgi_param HTTPS $fcgi_https; fastcgi_param HTTPS $fcgi_https;
} }
# Expire rules for static content ## Begin - Security
# Feed # deny all direct access for these folders
location ~* \.(?:rss|atom|cache)$ { location ~* /(\.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
expires 1h; # deny running scripts inside core system folders
} location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny running scripts inside user folder
location ~* /user/.*\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny access to specific files in the root folder
location ~ /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess) { return 403; }
## End - Security
# Media: images, icons, video, audio, HTC include /mnt/ddev_config/nginx/*.conf;
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ { }
expires 1M;
access_log off;
add_header Cache-Control "public";
}
# Prevent clients from accessing hidden files (starting with a dot)
# This is particularly important if you store .htpasswd files in the site hierarchy
# Access to `/.well-known/` is allowed.
# https://www.mnot.net/blog/2010/04/07/well-known
# https://tools.ietf.org/html/rfc5785
location ~* /\.(?!well-known\/) {
deny all;
}
# Prevent clients from accessing to backup/config/source files server {
location ~* (?:\.(?:bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$ { listen 443 ssl;
deny all; listen [::]:443 default ipv6only=on;
# The WEBSERVER_DOCROOT variable is substituted with
# its value when the container is started.
root $WEBSERVER_DOCROOT;
ssl_certificate /etc/ssl/certs/master.crt;
ssl_certificate_key /etc/ssl/certs/master.key;
include /etc/nginx/monitoring.conf;
index index.php index.htm index.html;
# Make site accessible from http://localhost/
server_name _;
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
sendfile off;
error_log /dev/stdout info;
access_log /var/log/nginx/access.log;
## Begin - Index
# for subfolders, simply adjust:
# `location /subfolder {`
# and the rewrite to use `/subfolder/index.php`
location / {
try_files $uri $uri/ /index.php?$query_string;
}
## End - Index
# pass the PHP scripts to FastCGI server listening on socket
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm.sock;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_intercept_errors off;
# fastcgi_read_timeout should match max_execution_time in php.ini
fastcgi_read_timeout 10m;
fastcgi_param SERVER_NAME $host;
fastcgi_param HTTPS $fcgi_https;
} }
## Begin - Security ## Begin - Security
@@ -88,31 +138,5 @@ server {
## End - Security ## End - Security
## provide a health check endpoint include /mnt/ddev_config/nginx/*.conf;
location /healthcheck {
access_log off;
stub_status on;
keepalive_timeout 0; # Disable HTTP keepalive
return 200;
}
error_page 400 401 /40x.html;
location = /40x.html {
root /usr/share/nginx/html;
}
location ~ ^/(fpmstatus|ping)$ {
access_log off;
stub_status on;
keepalive_timeout 0; # Disable HTTP keepalive
allow 127.0.0.1;
allow all;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm.sock;
}
} }