maj
This commit is contained in:
		
							
								
								
									
										113
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,115 @@ | |||||||
|  | # v1.7.42.3 | ||||||
|  | ## 07/18/2023 | ||||||
|  |  | ||||||
|  | 2. [](#improved) | ||||||
|  |    * Fixed a typo in `Utils::isDangerousFunction` | ||||||
|  |  | ||||||
|  | # v1.7.42.2 | ||||||
|  | ## 07/18/2023 | ||||||
|  |  | ||||||
|  | 2. [](#improved) | ||||||
|  |    * In `Utils::isDangerousFunction`, handle double `\\` in `|map` twig filter to mitigate SSTI attack | ||||||
|  |    * Better handle empty email in `Validatoin::typeEmail()` | ||||||
|  |  | ||||||
|  | # v1.7.42.1 | ||||||
|  | ## 06/15/2023 | ||||||
|  |  | ||||||
|  | 2. [](#improved) | ||||||
|  |    * Quick fix for `isDangerousFunction` when `$name` was a closure [#3727](https://github.com/getgrav/grav/issues/3727) | ||||||
|  |  | ||||||
|  | # v1.7.42 | ||||||
|  | ## 06/14/2023 | ||||||
|  |  | ||||||
|  | 1. [](#new) | ||||||
|  |    * Added a new `system.languages.debug` option that adds a `<span class="translate-debug"></span>` around strings translated with `|t`. This can be styled by the theme as needed. | ||||||
|  | 1. [](#improved) | ||||||
|  |    * More robust SSTI handling in `filter`, `map`, and `reduce` Twig filters and functions | ||||||
|  |    * Various SSTI improvements `Utils::isDangerousFunction()` | ||||||
|  | 1. [](#bugfix) | ||||||
|  |    * Fixed Twig `|map()` allowing code execution | ||||||
|  |    * Fixed Twig `|reduce()` allowing code execution | ||||||
|  |  | ||||||
|  | # v1.7.41.2 | ||||||
|  | ## 06/01/2023 | ||||||
|  |  | ||||||
|  | 1. [](#improved) | ||||||
|  |    * Added the ability to set a configurable 'key' for the Twig Cache Tag: `{% cache 'my-key' 600 %}` | ||||||
|  | 1. [](#bugfix) | ||||||
|  |    * Fixed an issue with special characters in slug's would cause redirect loops | ||||||
|  |  | ||||||
|  | # v1.7.41.1 | ||||||
|  | ## 05/10/2023 | ||||||
|  |  | ||||||
|  | 1. [](#bugfix) | ||||||
|  |    * Fixed certain UTF-8 characters breaking `Truncator` class [#3716](https://github.com/getgrav/grav/issues/3716) | ||||||
|  |  | ||||||
|  | # v1.7.41 | ||||||
|  | ## 05/09/2023 | ||||||
|  |  | ||||||
|  | 1. [](#improved) | ||||||
|  |    * Removed `FILTER_SANITIZE_STRING` input filter in favor of `htmlspecialchars(strip_tags())` for PHP 8.2+ | ||||||
|  |    * Added `GRAV_SANITIZE_STRING` constant to replace `FILTER_SANITIZE_STRING` for PHP 8.2+ | ||||||
|  |    * Support non-deprecated style dynamic properties in `Parsedown` class via `ParseDownGravTrait` for PHP 8.2+ | ||||||
|  |    * Modified `Truncator` to not use deprecated `mb_convert_encoding()` for PHP 8.2+ | ||||||
|  |    * Fixed passing null into `mb_strpos()` deprecated for PHP 8.2+ | ||||||
|  |    * Updated internal `TwigDeferredExtension` to be PHP 8.2+ compatible | ||||||
|  |    * Upgraded `getgrav/image` fork to take advantage of various PHP 8.2+ fixes | ||||||
|  |    * Use `UserGroupObject::groupNames` method in blueprints for PHP 8.2+ | ||||||
|  |    * Comment out `files-upload` deprecated message as this is not going to be removed | ||||||
|  |    * Added various public `Twig` class variables used by admin to address deprecated messages for PHP 8.2+ | ||||||
|  |    * Added `parse_url` to list of PHP functions supported in Twig Extension | ||||||
|  |    * Added support for dynamic functions in `Parsedown` to stop deprecation messages in PHP 8.2+ | ||||||
|  |   | ||||||
|  | # v1.7.40 | ||||||
|  | ## 03/22/2023 | ||||||
|  |  | ||||||
|  | 1. [](#new) | ||||||
|  |     * Added a new `timestamp: true|false` option for individual assets | ||||||
|  | 1. [](#improved) | ||||||
|  |     * Removed outdated `xcache` setting [#3615](https://github.com/getgrav/grav/pull/3615) | ||||||
|  |     * Updated `robots.txt` [#3625](https://github.com/getgrav/grav/pull/3625) | ||||||
|  | 1. [](#bugfix) | ||||||
|  |     * Fixed `force_ssl` redirect in case of undefined hostname [#3702](https://github.com/getgrav/grav/pull/3702) | ||||||
|  |     * Fixed an issue with duplicate identical page paths | ||||||
|  |     * Fixed `BlueprintSchema:flattenData` to properly handle ignored fields | ||||||
|  |     * Fixed LogViewer regex greediness [#3684](https://github.com/getgrav/grav/pull/3684) | ||||||
|  |     * Fixed `whoami` command [#3695](https://github.com/getgrav/grav/pull/3695) | ||||||
|  |  | ||||||
|  | # v1.7.39.4 | ||||||
|  | ## 02/22/2023 | ||||||
|  |  | ||||||
|  | 1. [](#bugfix) | ||||||
|  |     * Reverted a reorganization of `account.yaml` that caused username to be disabled [admin#2344](https://github.com/getgrav/grav-plugin-admin/issues/2344) | ||||||
|  |  | ||||||
|  | # v1.7.39.3 | ||||||
|  | ## 02/21/2023 | ||||||
|  |  | ||||||
|  | 1. [](#bugfix) | ||||||
|  |     * Fix for overzealous modular page template rendering fix in 1.7.39 causing Feed plugin to break [#3689](https://github.com/getgrav/grav/issues/3689) | ||||||
|  |  | ||||||
|  | # v1.7.39.2 | ||||||
|  | ## 02/20/2023 | ||||||
|  |  | ||||||
|  | 1. [](#bugfix) | ||||||
|  |     * Fix for invalid session breaking Flex Accounts (when switching from Regular to Flex) | ||||||
|  |  | ||||||
|  | # v1.7.39.1 | ||||||
|  | ## 02/20/2023 | ||||||
|  |  | ||||||
|  | 1. [](#bugfix) | ||||||
|  |     * Fix for broken image CSS with the latest version of DebugBar | ||||||
|  |  | ||||||
|  | # v1.7.39 | ||||||
|  | ## 02/19/2023 | ||||||
|  |  | ||||||
|  | 1. [](#improved) | ||||||
|  |     * Vendor library updates to latest versions | ||||||
|  | 1. [](#bugfix) | ||||||
|  |     * Various PHP 8.2 fixes | ||||||
|  |     * Fixed an issue with modular pages rendering thew wrong template when dynamically changing the page | ||||||
|  |     * Fixed an issue with `email` validation that was failing on UTF-8 characters. Following best practices and now only check for `@` and length. | ||||||
|  |     * Fixed PHPUnit tests to remove deprecation warnings | ||||||
|  |  | ||||||
| # v1.7.38 | # v1.7.38 | ||||||
| ## 01/02/2023 | ## 01/02/2023 | ||||||
|  |  | ||||||
| @@ -7,7 +119,6 @@ | |||||||
|    * Vendor library updates to latest versions |    * Vendor library updates to latest versions | ||||||
|    * Updated `bin/composer.phar` to latest `2.4.4` version [#3627](https://github.com/getgrav/grav/issues/3627) |    * Updated `bin/composer.phar` to latest `2.4.4` version [#3627](https://github.com/getgrav/grav/issues/3627) | ||||||
| 1. [](#bugfix) | 1. [](#bugfix) | ||||||
|  |  | ||||||
|    * Don't fail hard if pages recurse with same path |    * Don't fail hard if pages recurse with same path | ||||||
|    * Github workflows security hardening [#3624](https://github.com/getgrav/grav/pull/3624) |    * Github workflows security hardening [#3624](https://github.com/getgrav/grav/pull/3624) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										510
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										510
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -14,11 +14,8 @@ div.phpdebugbar { | |||||||
|     padding: 5px 8px; |     padding: 5px 8px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn { |  | ||||||
|     background-image: url(data:image/png;base64,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); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .phpdebugbar a.phpdebugbar-restore-btn { | .phpdebugbar a.phpdebugbar-restore-btn { | ||||||
|  |     background-image: url(data:image/png;base64,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); | ||||||
|     width: 13px; |     width: 13px; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -448,6 +448,17 @@ form: | |||||||
|               validate: |               validate: | ||||||
|                 type: bool |                 type: bool | ||||||
|  |  | ||||||
|  |             languages.debug: | ||||||
|  |               type: toggle | ||||||
|  |               label: PLUGIN_ADMIN.LANGUAGE_DEBUG | ||||||
|  |               help: PLUGIN_ADMIN.LANGUAGE_DEBUG_HELP | ||||||
|  |               highlight: 0 | ||||||
|  |               options: | ||||||
|  |                 1: PLUGIN_ADMIN.YES | ||||||
|  |                 0: PLUGIN_ADMIN.NO | ||||||
|  |               validate: | ||||||
|  |                 type: bool | ||||||
|  |  | ||||||
|         http_headers: |         http_headers: | ||||||
|           type: tab |           type: tab | ||||||
|           title: PLUGIN_ADMIN.HTTP_HEADERS |           title: PLUGIN_ADMIN.HTTP_HEADERS | ||||||
| @@ -608,7 +619,6 @@ form: | |||||||
|                 file: File |                 file: File | ||||||
|                 apc: APC |                 apc: APC | ||||||
|                 apcu: APCu |                 apcu: APCu | ||||||
|                 xcache: Xcache |  | ||||||
|                 memcache: Memcache |                 memcache: Memcache | ||||||
|                 memcached: Memcached |                 memcached: Memcached | ||||||
|                 wincache: WinCache |                 wincache: WinCache | ||||||
|   | |||||||
| @@ -140,7 +140,7 @@ form: | |||||||
|                     multiple: true |                     multiple: true | ||||||
|                     size: large |                     size: large | ||||||
|                     label: PLUGIN_ADMIN.GROUPS |                     label: PLUGIN_ADMIN.GROUPS | ||||||
|                     data-options@: '\Grav\Common\User\Group::groupNames' |                     data-options@: 'Grav\Common\Flex\Types\UserGroups\UserGroupObject::groupNames' | ||||||
|                     classes: fancy |                     classes: fancy | ||||||
|                     help: PLUGIN_ADMIN.GROUPS_HELP |                     help: PLUGIN_ADMIN.GROUPS_HELP | ||||||
|                     validate: |                     validate: | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ languages: | |||||||
|   override_locale: false                         # Override the default or system locale with language specific one |   override_locale: false                         # Override the default or system locale with language specific one | ||||||
|   content_fallback: {}                           # Custom language fallbacks. eg: {fr: ['fr', 'en']} |   content_fallback: {}                           # Custom language fallbacks. eg: {fr: ['fr', 'en']} | ||||||
|   pages_fallback_only: false                     # DEPRECATED: Use `content_fallback` instead |   pages_fallback_only: false                     # DEPRECATED: Use `content_fallback` instead | ||||||
|  |   debug: false                                   # Debug language detection | ||||||
|  |  | ||||||
| home: | home: | ||||||
|   alias: '/home'                                 # Default path for home, ie / |   alias: '/home'                                 # Default path for home, ie / | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ | |||||||
|  |  | ||||||
| // Some standard defines | // Some standard defines | ||||||
| define('GRAV', true); | define('GRAV', true); | ||||||
| define('GRAV_VERSION', '1.7.38'); | define('GRAV_VERSION', '1.7.42.3'); | ||||||
| define('GRAV_SCHEMA', '1.7.0_2020-11-20_1'); | define('GRAV_SCHEMA', '1.7.0_2020-11-20_1'); | ||||||
| define('GRAV_TESTING', false); | define('GRAV_TESTING', false); | ||||||
|  |  | ||||||
| @@ -99,3 +99,6 @@ define('RAW_CONTENT', 1); | |||||||
| define('TWIG_CONTENT', 2); | define('TWIG_CONTENT', 2); | ||||||
| define('TWIG_CONTENT_LIST', 3); | define('TWIG_CONTENT_LIST', 3); | ||||||
| define('TWIG_TEMPLATES', 4); | define('TWIG_TEMPLATES', 4); | ||||||
|  |  | ||||||
|  | // Filters | ||||||
|  | define('GRAV_SANITIZE_STRING', 5001); | ||||||
|   | |||||||
| @@ -268,7 +268,13 @@ class Assets extends PropertyObject | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Add timestamp |         // Add timestamp | ||||||
|  |         $timestamp_override = $options['timestamp'] ?? true; | ||||||
|  |  | ||||||
|  |         if (filter_var($timestamp_override, FILTER_VALIDATE_BOOLEAN)) { | ||||||
|             $options['timestamp'] = $this->timestamp; |             $options['timestamp'] = $this->timestamp; | ||||||
|  |         } else { | ||||||
|  |             $options['timestamp'] = null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // Set order |         // Set order | ||||||
|         $group = $options['group'] ?? 'head'; |         $group = $options['group'] ?? 'head'; | ||||||
|   | |||||||
| @@ -192,6 +192,7 @@ trait AssetUtilsTrait | |||||||
|         $querystring = ''; |         $querystring = ''; | ||||||
|  |  | ||||||
|         $asset = $asset ?? $this->asset; |         $asset = $asset ?? $this->asset; | ||||||
|  |         $attributes = $this->attributes; | ||||||
|  |  | ||||||
|         if (!empty($this->query)) { |         if (!empty($this->query)) { | ||||||
|             if (Utils::contains($asset, '?')) { |             if (Utils::contains($asset, '?')) { | ||||||
|   | |||||||
| @@ -129,7 +129,8 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface | |||||||
|             $items = $name !== '' ? $this->getProperty($name)['fields'] ?? [] : $this->items; |             $items = $name !== '' ? $this->getProperty($name)['fields'] ?? [] : $this->items; | ||||||
|             foreach ($items as $key => $rules) { |             foreach ($items as $key => $rules) { | ||||||
|                 $type = $rules['type'] ?? ''; |                 $type = $rules['type'] ?? ''; | ||||||
|                 if (!str_starts_with($type, '_') && !str_contains($key, '*')) { |                 $ignore = (bool) array_filter((array)($rules['validate']['ignore'] ?? [])) ?? false; | ||||||
|  |                 if (!str_starts_with($type, '_') && !str_contains($key, '*') && $ignore !== true) { | ||||||
|                     $list[$prefix . $key] = null; |                     $list[$prefix . $key] = null; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -631,6 +631,10 @@ class Validation | |||||||
|      */ |      */ | ||||||
|     public static function typeEmail($value, array $params, array $field) |     public static function typeEmail($value, array $params, array $field) | ||||||
|     { |     { | ||||||
|  |         if (empty($value)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (!isset($params['max'])) { |         if (!isset($params['max'])) { | ||||||
|             $params['max'] = 320; |             $params['max'] = 320; | ||||||
|         } |         } | ||||||
| @@ -638,7 +642,7 @@ class Validation | |||||||
|         $values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value; |         $values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value; | ||||||
|  |  | ||||||
|         foreach ($values as $val) { |         foreach ($values as $val) { | ||||||
|             if (!(self::typeText($val, $params, $field) && filter_var($val, FILTER_VALIDATE_EMAIL))) { |             if (!(self::typeText($val, $params, $field) && strpos($val, '@', 1))) { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ class SimplePageHandler extends Handler | |||||||
|         $vars = array( |         $vars = array( | ||||||
|             'stylesheet' => file_get_contents($cssFile), |             'stylesheet' => file_get_contents($cssFile), | ||||||
|             'code'        => $code, |             'code'        => $code, | ||||||
|             'message'     => filter_var(rawurldecode($message), FILTER_SANITIZE_STRING), |             'message'     => htmlspecialchars(strip_tags(rawurldecode($message)), ENT_QUOTES, 'UTF-8'), | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         $helper->setVariables($vars); |         $helper->setVariables($vars); | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ declare(strict_types=1); | |||||||
| namespace Grav\Common\Flex\Types\UserGroups; | namespace Grav\Common\Flex\Types\UserGroups; | ||||||
|  |  | ||||||
| use Grav\Common\Flex\FlexObject; | use Grav\Common\Flex\FlexObject; | ||||||
|  | use Grav\Common\Grav; | ||||||
| use Grav\Common\User\Access; | use Grav\Common\User\Access; | ||||||
| use Grav\Common\User\Interfaces\UserGroupInterface; | use Grav\Common\User\Interfaces\UserGroupInterface; | ||||||
| use function is_bool; | use function is_bool; | ||||||
| @@ -74,6 +75,18 @@ class UserGroupObject extends FlexObject implements UserGroupInterface | |||||||
|         return $access->authorize('admin.super') ? true : null; |         return $access->authorize('admin.super') ? true : null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static function groupNames(): array | ||||||
|  |     { | ||||||
|  |         $groups = []; | ||||||
|  |         $user_groups = Grav::instance()['user_groups']; | ||||||
|  |  | ||||||
|  |         foreach ($user_groups as $key => $group) { | ||||||
|  |             $groups[$key] = $group->readableName; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $groups; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @return Access |      * @return Access | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ use function is_string; | |||||||
| class LogViewer | class LogViewer | ||||||
| { | { | ||||||
|     /** @var string */ |     /** @var string */ | ||||||
|     protected $pattern = '/\[(?P<date>.*)\] (?P<logger>\w+).(?P<level>\w+): (?P<message>.*[^ ]+) (?P<context>[^ ]+) (?P<extra>[^ ]+)/'; |     protected $pattern = '/\[(?P<date>.*?)\] (?P<logger>\w+)\.(?P<level>\w+): (?P<message>.*[^ ]+) (?P<context>[^ ]+) (?P<extra>[^ ]+)/'; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Get the objects of a tailed file |      * Get the objects of a tailed file | ||||||
|   | |||||||
| @@ -144,7 +144,7 @@ class Truncator | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Transform multibyte entities which otherwise display incorrectly. |         // Transform multibyte entities which otherwise display incorrectly. | ||||||
|         $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'); |         $html = mb_encode_numericentity($html, [0x80, 0x10FFFF, 0, ~0], 'UTF-8'); | ||||||
|  |  | ||||||
|         // Internal errors enabled as HTML5 not fully supported. |         // Internal errors enabled as HTML5 not fully supported. | ||||||
|         libxml_use_internal_errors(true); |         libxml_use_internal_errors(true); | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ use Grav\Common\Page\Markdown\Excerpts; | |||||||
|  */ |  */ | ||||||
| class Parsedown extends \Parsedown | class Parsedown extends \Parsedown | ||||||
| { | { | ||||||
|  |  | ||||||
|     use ParsedownGravTrait; |     use ParsedownGravTrait; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ trait ParsedownGravTrait | |||||||
|     public $completable_blocks = []; |     public $completable_blocks = []; | ||||||
|     /** @var array */ |     /** @var array */ | ||||||
|     public $continuable_blocks = []; |     public $continuable_blocks = []; | ||||||
|  |     public $plugins = []; | ||||||
|  |  | ||||||
|     /** @var Excerpts */ |     /** @var Excerpts */ | ||||||
|     protected $excerpts; |     protected $excerpts; | ||||||
| @@ -292,7 +293,12 @@ trait ParsedownGravTrait | |||||||
|     #[\ReturnTypeWillChange] |     #[\ReturnTypeWillChange] | ||||||
|     public function __call($method, $args) |     public function __call($method, $args) | ||||||
|     { |     { | ||||||
|         if (isset($this->{$method}) === true) { |  | ||||||
|  |         if (isset($this->plugins[$method]) === true) { | ||||||
|  |             $func = $this->plugins[$method]; | ||||||
|  |  | ||||||
|  |             return call_user_func_array($func, $args); | ||||||
|  |         } elseif (isset($this->{$method}) === true) { | ||||||
|             $func = $this->{$method}; |             $func = $this->{$method}; | ||||||
|  |  | ||||||
|             return call_user_func_array($func, $args); |             return call_user_func_array($func, $args); | ||||||
| @@ -300,4 +306,14 @@ trait ParsedownGravTrait | |||||||
|  |  | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function __set($name, $value) | ||||||
|  |     { | ||||||
|  |         if (is_callable($value)) { | ||||||
|  |             $this->plugins[$name] = $value; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -62,8 +62,8 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate | |||||||
|         if (!($this->offsetExists('width') && $this->offsetExists('height') && $this->offsetExists('mime'))) { |         if (!($this->offsetExists('width') && $this->offsetExists('height') && $this->offsetExists('mime'))) { | ||||||
|             $image_info = getimagesize($path); |             $image_info = getimagesize($path); | ||||||
|             if ($image_info) { |             if ($image_info) { | ||||||
|                 $this->def('width', $image_info[0]); |                 $this->def('width', (int) $image_info[0]); | ||||||
|                 $this->def('height', $image_info[1]); |                 $this->def('height', (int) $image_info[1]); | ||||||
|                 $this->def('mime', $image_info['mime']); |                 $this->def('mime', $image_info['mime']); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -299,7 +299,7 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if ($width && $height) { |         if ($width && $height) { | ||||||
|             $this->__call('cropResize', [$width, $height]); |             $this->__call('cropResize', [(int) $width, (int) $height]); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return parent::lightbox($width, $height, $reset); |         return parent::lightbox($width, $height, $reset); | ||||||
| @@ -361,8 +361,8 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate | |||||||
|  |  | ||||||
|         // Scaling operations |         // Scaling operations | ||||||
|         $scale     = ($scale ?? $config->get('system.images.watermark.scale', 100)) / 100; |         $scale     = ($scale ?? $config->get('system.images.watermark.scale', 100)) / 100; | ||||||
|         $wwidth    = (int)$this->get('width')  * $scale; |         $wwidth    = (int) ($this->get('width')  * $scale); | ||||||
|         $wheight   = (int)$this->get('height') * $scale; |         $wheight   = (int) ($this->get('height') * $scale); | ||||||
|         $watermark->resize($wwidth, $wheight); |         $watermark->resize($wwidth, $wheight); | ||||||
|  |  | ||||||
|         // Position operations |         // Position operations | ||||||
| @@ -392,11 +392,11 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate | |||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case 'right': |             case 'right': | ||||||
|                 $positionX = (int)$this->get('width')-$wwidth; |                 $positionX = (int) ($this->get('width')-$wwidth); | ||||||
|                 break; |                 break; | ||||||
|  |  | ||||||
|             case 'center': |             case 'center': | ||||||
|                 $positionX = ((int)$this->get('width')/2) - ($wwidth/2); |                 $positionX = (int) (($this->get('width')/2) - ($wwidth/2)); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -431,8 +431,8 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate | |||||||
|         return $this; |         return $this; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       $dst_width = $image->width()+2*$border; |       $dst_width = (int) ($image->width()+2*$border); | ||||||
|       $dst_height = $image->height()+2*$border; |       $dst_height = (int) ($image->height()+2*$border); | ||||||
|  |  | ||||||
|       $frame = ImageFile::create($dst_width, $dst_height); |       $frame = ImageFile::create($dst_width, $dst_height); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1270,9 +1270,14 @@ class Page implements PageInterface | |||||||
|      */ |      */ | ||||||
|     public function blueprintName() |     public function blueprintName() | ||||||
|     { |     { | ||||||
|         $blueprint_name = filter_input(INPUT_POST, 'blueprint', FILTER_SANITIZE_STRING) ?: $this->template(); |         if (!isset($_POST['blueprint'])) { | ||||||
|  |             return $this->template(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return $blueprint_name; |         $post_value = $_POST['blueprint']; | ||||||
|  |         $sanitized_value = htmlspecialchars(strip_tags($post_value), ENT_QUOTES, 'UTF-8'); | ||||||
|  |  | ||||||
|  |         return $sanitized_value ?: $this->template(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -1802,7 +1807,7 @@ class Page implements PageInterface | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (empty($this->slug)) { |         if (empty($this->slug)) { | ||||||
|             $this->slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder)) ?: null; |             $this->slug = $this->adjustRouteCase(preg_replace(PAGE_ORDER_PREFIX_REGEX, '', (string) $this->folder)) ?: null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return $this->slug; |         return $this->slug; | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ use Grav\Common\Page\Interfaces\PageInterface; | |||||||
| use Grav\Common\Taxonomy; | use Grav\Common\Taxonomy; | ||||||
| use Grav\Common\Uri; | use Grav\Common\Uri; | ||||||
| use Grav\Common\Utils; | use Grav\Common\Utils; | ||||||
|  | use Grav\Events\TypesEvent; | ||||||
| use Grav\Framework\Flex\Flex; | use Grav\Framework\Flex\Flex; | ||||||
| use Grav\Framework\Flex\FlexDirectory; | use Grav\Framework\Flex\FlexDirectory; | ||||||
| use Grav\Framework\Flex\Interfaces\FlexTranslateInterface; | use Grav\Framework\Flex\Interfaces\FlexTranslateInterface; | ||||||
| @@ -1289,7 +1290,7 @@ class Pages | |||||||
|  |  | ||||||
|             $scanBlueprintsAndTemplates = static function (Types $types) use ($grav) { |             $scanBlueprintsAndTemplates = static function (Types $types) use ($grav) { | ||||||
|                 // Scan blueprints |                 // Scan blueprints | ||||||
|                 $event = new Event(); |                 $event = new TypesEvent(); | ||||||
|                 $event->types = $types; |                 $event->types = $types; | ||||||
|                 $grav->fireEvent('onGetPageBlueprints', $event); |                 $grav->fireEvent('onGetPageBlueprints', $event); | ||||||
|  |  | ||||||
| @@ -1303,7 +1304,7 @@ class Pages | |||||||
|                 $types->scanBlueprints($lookup); |                 $types->scanBlueprints($lookup); | ||||||
|  |  | ||||||
|                 // Scan templates |                 // Scan templates | ||||||
|                 $event = new Event(); |                 $event = new TypesEvent(); | ||||||
|                 $event->types = $types; |                 $event->types = $types; | ||||||
|                 $grav->fireEvent('onGetPageTemplates', $event); |                 $grav->fireEvent('onGetPageTemplates', $event); | ||||||
|  |  | ||||||
| @@ -1773,7 +1774,7 @@ class Pages | |||||||
|         $dirs = (array) $grav['config']->get('system.pages.dirs', ['page://']); |         $dirs = (array) $grav['config']->get('system.pages.dirs', ['page://']); | ||||||
|         foreach ($dirs as $dir) { |         foreach ($dirs as $dir) { | ||||||
|             $path = $locator->findResource($dir); |             $path = $locator->findResource($dir); | ||||||
|             if (file_exists($path)) { |             if (file_exists($path) && !in_array($path, $paths, true)) { | ||||||
|                 $paths[] = $path; |                 $paths[] = $path; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
| namespace Grav\Common\Processors; | namespace Grav\Common\Processors; | ||||||
|  |  | ||||||
| use Grav\Common\Page\Interfaces\PageInterface; | use Grav\Common\Page\Interfaces\PageInterface; | ||||||
|  | use Grav\Events\PageEvent; | ||||||
| use Grav\Framework\RequestHandler\Exception\RequestException; | use Grav\Framework\RequestHandler\Exception\RequestException; | ||||||
| use Grav\Plugin\Form\Forms; | use Grav\Plugin\Form\Forms; | ||||||
| use RocketTheme\Toolbox\Event\Event; | use RocketTheme\Toolbox\Event\Event; | ||||||
| @@ -66,7 +67,7 @@ class PagesProcessor extends ProcessorBase | |||||||
|         if (!$page->routable()) { |         if (!$page->routable()) { | ||||||
|             $exception = new RequestException($request, 'Page Not Found', 404); |             $exception = new RequestException($request, 'Page Not Found', 404); | ||||||
|             // If no page found, fire event |             // If no page found, fire event | ||||||
|             $event = new Event([ |             $event = new PageEvent([ | ||||||
|                 'page' => $page, |                 'page' => $page, | ||||||
|                 'code' => $exception->getCode(), |                 'code' => $exception->getCode(), | ||||||
|                 'message' => $exception->getMessage(), |                 'message' => $exception->getMessage(), | ||||||
|   | |||||||
| @@ -357,7 +357,7 @@ class Scheduler | |||||||
|      */ |      */ | ||||||
|     public function whoami() |     public function whoami() | ||||||
|     { |     { | ||||||
|         $process = new Process('whoami'); |         $process = new Process(['whoami']); | ||||||
|         $process->run(); |         $process->run(); | ||||||
|  |  | ||||||
|         if ($process->isSuccessful()) { |         if ($process->isSuccessful()) { | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ class PagesServiceProvider implements ServiceProviderInterface | |||||||
|             /** @var Uri $uri */ |             /** @var Uri $uri */ | ||||||
|             $uri = $grav['uri']; |             $uri = $grav['uri']; | ||||||
|  |  | ||||||
|             $path = $uri->path() ?: '/'; // Don't trim to support trailing slash default routes |             $path = $uri->path() ? urldecode($uri->path()) : '/'; // Don't trim to support trailing slash default routes | ||||||
|             $page = $pages->dispatch($path); |             $page = $pages->dispatch($path); | ||||||
|  |  | ||||||
|             // Redirection tests |             // Redirection tests | ||||||
| @@ -72,7 +72,7 @@ class PagesServiceProvider implements ServiceProviderInterface | |||||||
|                 if ($config->get('system.force_ssl')) { |                 if ($config->get('system.force_ssl')) { | ||||||
|                     $scheme = $uri->scheme(true); |                     $scheme = $uri->scheme(true); | ||||||
|                     if ($scheme !== 'https') { |                     if ($scheme !== 'https') { | ||||||
|                         $url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; |                         $url = 'https://' . $uri->host() . $uri->uri(); | ||||||
|                         $grav->redirect($url); |                         $grav->redirect($url); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ class TaskServiceProvider implements ServiceProviderInterface | |||||||
|  |  | ||||||
|             $task = $body['task'] ?? $c['uri']->param('task'); |             $task = $body['task'] ?? $c['uri']->param('task'); | ||||||
|             if (null !== $task) { |             if (null !== $task) { | ||||||
|                 $task = filter_var($task, FILTER_SANITIZE_STRING); |                 $task = htmlspecialchars(strip_tags($task), ENT_QUOTES, 'UTF-8'); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return $task ?: null; |             return $task ?: null; | ||||||
| @@ -46,7 +46,7 @@ class TaskServiceProvider implements ServiceProviderInterface | |||||||
|  |  | ||||||
|             $action = $body['action'] ?? $c['uri']->param('action'); |             $action = $body['action'] ?? $c['uri']->param('action'); | ||||||
|             if (null !== $action) { |             if (null !== $action) { | ||||||
|                 $action = filter_var($action, FILTER_SANITIZE_STRING); |                 $action = htmlspecialchars(strip_tags($action), ENT_QUOTES, 'UTF-8'); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return $action ?: null; |             return $action ?: null; | ||||||
|   | |||||||
| @@ -122,10 +122,10 @@ class Session extends \Grav\Framework\Session\Session | |||||||
|  |  | ||||||
|             // Make sure that Forms 3.0+ has been installed. |             // Make sure that Forms 3.0+ has been installed. | ||||||
|             if (null === $object && isset($grav['forms'])) { |             if (null === $object && isset($grav['forms'])) { | ||||||
|                 user_error( | //                user_error( | ||||||
|                     __CLASS__ . '::' . __FUNCTION__ . '(\'files-upload\') is deprecated since Grav 1.6, use $form->getFlash()->getLegacyFiles() instead', | //                    __CLASS__ . '::' . __FUNCTION__ . '(\'files-upload\') is deprecated since Grav 1.6, use $form->getFlash()->getLegacyFiles() instead', | ||||||
|                     E_USER_DEPRECATED | //                    E_USER_DEPRECATED | ||||||
|                 ); | //                ); | ||||||
|  |  | ||||||
|                 /** @var Uri $uri */ |                 /** @var Uri $uri */ | ||||||
|                 $uri = $grav['uri']; |                 $uri = $grav['uri']; | ||||||
|   | |||||||
| @@ -46,6 +46,7 @@ use Twig\Error\RuntimeError; | |||||||
| use Twig\Extension\AbstractExtension; | use Twig\Extension\AbstractExtension; | ||||||
| use Twig\Extension\GlobalsInterface; | use Twig\Extension\GlobalsInterface; | ||||||
| use Twig\Loader\FilesystemLoader; | use Twig\Loader\FilesystemLoader; | ||||||
|  | use Twig\Markup; | ||||||
| use Twig\TwigFilter; | use Twig\TwigFilter; | ||||||
| use Twig\TwigFunction; | use Twig\TwigFunction; | ||||||
| use function array_slice; | use function array_slice; | ||||||
| @@ -170,8 +171,10 @@ class GravExtension extends AbstractExtension implements GlobalsInterface | |||||||
|             new TwigFilter('count', 'count'), |             new TwigFilter('count', 'count'), | ||||||
|             new TwigFilter('array_diff', 'array_diff'), |             new TwigFilter('array_diff', 'array_diff'), | ||||||
|  |  | ||||||
|             // Security fix |             // Security fixes | ||||||
|             new TwigFilter('filter', [$this, 'filterFilter'], ['needs_environment' => true]), |             new TwigFilter('filter', [$this, 'filterFunc'], ['needs_environment' => true]), | ||||||
|  |             new TwigFilter('map', [$this, 'mapFunc'], ['needs_environment' => true]), | ||||||
|  |             new TwigFilter('reduce', [$this, 'reduceFunc'], ['needs_environment' => true]), | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -247,6 +250,12 @@ class GravExtension extends AbstractExtension implements GlobalsInterface | |||||||
|             new TwigFunction('is_object', 'is_object'), |             new TwigFunction('is_object', 'is_object'), | ||||||
|             new TwigFunction('count', 'count'), |             new TwigFunction('count', 'count'), | ||||||
|             new TwigFunction('array_diff', 'array_diff'), |             new TwigFunction('array_diff', 'array_diff'), | ||||||
|  |             new TwigFunction('parse_url', 'parse_url'), | ||||||
|  |  | ||||||
|  |             // Security fixes | ||||||
|  |             new TwigFunction('filter', [$this, 'filterFunc'], ['needs_environment' => true]), | ||||||
|  |             new TwigFunction('map', [$this, 'mapFunc'], ['needs_environment' => true]), | ||||||
|  |             new TwigFunction('reduce', [$this, 'reduceFunc'], ['needs_environment' => true]), | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -468,7 +477,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface | |||||||
|      */ |      */ | ||||||
|     public function base64EncodeFilter($str) |     public function base64EncodeFilter($str) | ||||||
|     { |     { | ||||||
|         return base64_encode($str); |         return base64_encode((string) $str); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -904,8 +913,13 @@ class GravExtension extends AbstractExtension implements GlobalsInterface | |||||||
|             return $this->grav['admin']->translate($args, $lang); |             return $this->grav['admin']->translate($args, $lang); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // else use the default grav translate functionality |         $translation = $this->grav['language']->translate($args); | ||||||
|         return $this->grav['language']->translate($args); |  | ||||||
|  |         if ($this->config->get('system.languages.debug', false)) { | ||||||
|  |             return new Markup("<span class=\"translate-debug\" data-toggle=\"tooltip\" title=\"" . $args[0] . "\">$translation</span>", 'UTF-8'); | ||||||
|  |         } else { | ||||||
|  |             return $translation; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -949,7 +963,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface | |||||||
|      */ |      */ | ||||||
|     public function repeatFunc($input, $multiplier) |     public function repeatFunc($input, $multiplier) | ||||||
|     { |     { | ||||||
|         return str_repeat($input, $multiplier); |         return str_repeat($input, (int) $multiplier); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -1203,6 +1217,9 @@ class GravExtension extends AbstractExtension implements GlobalsInterface | |||||||
|      */ |      */ | ||||||
|     public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0) |     public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0) | ||||||
|     { |     { | ||||||
|  |         if ($str === null) { | ||||||
|  |             $str = ''; | ||||||
|  |         } | ||||||
|         return json_decode(html_entity_decode($str, ENT_COMPAT | ENT_HTML401, 'UTF-8'), $assoc, $depth, $options); |         return json_decode(html_entity_decode($str, ENT_COMPAT | ENT_HTML401, 'UTF-8'), $assoc, $depth, $options); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -1214,7 +1231,13 @@ class GravExtension extends AbstractExtension implements GlobalsInterface | |||||||
|      */ |      */ | ||||||
|     public function getCookie($key) |     public function getCookie($key) | ||||||
|     { |     { | ||||||
|         return filter_input(INPUT_COOKIE, $key, FILTER_SANITIZE_STRING); |         $cookie_value = filter_input(INPUT_COOKIE, $key); | ||||||
|  |  | ||||||
|  |         if ($cookie_value === null) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return htmlspecialchars(strip_tags($cookie_value), ENT_QUOTES, 'UTF-8'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -1689,12 +1712,44 @@ class GravExtension extends AbstractExtension implements GlobalsInterface | |||||||
|      * @return array|CallbackFilterIterator |      * @return array|CallbackFilterIterator | ||||||
|      * @throws RuntimeError |      * @throws RuntimeError | ||||||
|      */ |      */ | ||||||
|     function filterFilter(Environment $env, $array, $arrow) |     function filterFunc(Environment $env, $array, $arrow) | ||||||
|     { |     { | ||||||
|         if (is_string($arrow) && Utils::isDangerousFunction($arrow)) { |         if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) { | ||||||
|             throw new RuntimeError('Twig |filter("' . $arrow . '") is not allowed.'); |             throw new RuntimeError('Twig |filter("' . $arrow . '") is not allowed.'); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return twig_array_filter($env, $array, $arrow); |         return twig_array_filter($env, $array, $arrow); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param Environment $env | ||||||
|  |      * @param array $array | ||||||
|  |      * @param callable|string $arrow | ||||||
|  |      * @return array|CallbackFilterIterator | ||||||
|  |      * @throws RuntimeError | ||||||
|  |      */ | ||||||
|  |     function mapFunc(Environment $env, $array, $arrow) | ||||||
|  |     { | ||||||
|  |         if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) { | ||||||
|  |             throw new RuntimeError('Twig |map("' . $arrow . '") is not allowed.'); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return twig_array_map($env, $array, $arrow); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param Environment $env | ||||||
|  |      * @param array $array | ||||||
|  |      * @param callable|string $arrow | ||||||
|  |      * @return array|CallbackFilterIterator | ||||||
|  |      * @throws RuntimeError | ||||||
|  |      */ | ||||||
|  |     function reduceFunc(Environment $env, $array, $arrow) | ||||||
|  |     { | ||||||
|  |         if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) { | ||||||
|  |             throw new RuntimeError('Twig |reduce("' . $arrow . '") is not allowed.'); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return twig_array_map($env, $array, $arrow); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,13 +10,15 @@ | |||||||
| namespace Grav\Common\Twig\Node; | namespace Grav\Common\Twig\Node; | ||||||
|  |  | ||||||
| use Twig\Compiler; | use Twig\Compiler; | ||||||
|  | use Twig\Node\Expression\AbstractExpression; | ||||||
| use Twig\Node\Node; | use Twig\Node\Node; | ||||||
|  | use Twig\Node\NodeOutputInterface; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Class TwigNodeCache |  * Class TwigNodeCache | ||||||
|  * @package Grav\Common\Twig\Node |  * @package Grav\Common\Twig\Node | ||||||
|  */ |  */ | ||||||
| class TwigNodeCache extends Node | class TwigNodeCache extends Node implements NodeOutputInterface | ||||||
| { | { | ||||||
|     /** |     /** | ||||||
|      * @param string    $key       unique name for key |      * @param string    $key       unique name for key | ||||||
| @@ -25,25 +27,58 @@ class TwigNodeCache extends Node | |||||||
|      * @param integer   $lineno |      * @param integer   $lineno | ||||||
|      * @param string|null $tag |      * @param string|null $tag | ||||||
|      */ |      */ | ||||||
|     public function __construct(string $key, int $lifetime, Node $body, $lineno, $tag = null) |     public function __construct(Node $body, ?AbstractExpression $key, ?AbstractExpression $lifetime, array $defaults, int $lineno, string $tag) | ||||||
|     { |     { | ||||||
|         parent::__construct(array('body' => $body), array( 'key' => $key, 'lifetime' => $lifetime), $lineno, $tag); |         $nodes = ['body' => $body]; | ||||||
|  |  | ||||||
|  |         if ($key !== null) { | ||||||
|  |             $nodes['key'] = $key; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($lifetime !== null) { | ||||||
|  |             $nodes['lifetime'] = $lifetime; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         parent::__construct($nodes, $defaults, $lineno, $tag); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * {@inheritDoc} |  | ||||||
|      */ |  | ||||||
|     public function compile(Compiler $compiler): void |     public function compile(Compiler $compiler): void | ||||||
|     { |     { | ||||||
|         $boo = $this->getAttribute('key'); |         $compiler->addDebugInfo($this); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // Generate the cache key | ||||||
|  |         if ($this->hasNode('key')) { | ||||||
|  |             $compiler | ||||||
|  |                 ->write('$key = "twigcache-" . ') | ||||||
|  |                 ->subcompile($this->getNode('key')) | ||||||
|  |                 ->raw(";\n"); | ||||||
|  |         } else { | ||||||
|  |             $compiler | ||||||
|  |                 ->write('$key = ') | ||||||
|  |                 ->string($this->getAttribute('key')) | ||||||
|  |                 ->raw(";\n"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Set the cache timeout | ||||||
|  |         if ($this->hasNode('lifetime')) { | ||||||
|  |             $compiler | ||||||
|  |                 ->write('$lifetime = ') | ||||||
|  |                 ->subcompile($this->getNode('lifetime')) | ||||||
|  |                 ->raw(";\n"); | ||||||
|  |         } else { | ||||||
|  |             $compiler | ||||||
|  |                 ->write('$lifetime = ') | ||||||
|  |                 ->write($this->getAttribute('lifetime')) | ||||||
|  |                 ->raw(";\n"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         $compiler |         $compiler | ||||||
|             ->addDebugInfo($this) |  | ||||||
|             ->write("\$cache = \\Grav\\Common\\Grav::instance()['cache'];\n") |             ->write("\$cache = \\Grav\\Common\\Grav::instance()['cache'];\n") | ||||||
|             ->write("\$key = \"twigcache-\" . \"" . $this->getAttribute('key') . "\";\n") |  | ||||||
|             ->write("\$lifetime = " . $this->getAttribute('lifetime') . ";\n") |  | ||||||
|             ->write("\$cache_body = \$cache->fetch(\$key);\n") |             ->write("\$cache_body = \$cache->fetch(\$key);\n") | ||||||
|             ->write("if (\$cache_body === false) {\n") |             ->write("if (\$cache_body === false) {\n") | ||||||
|             ->indent() |             ->indent() | ||||||
|  |                 ->write("\\Grav\\Common\\Grav::instance()['debugger']->addMessage(\"Cache Key: \$key, Lifetime: \$lifetime\");\n") | ||||||
|                 ->write("ob_start();\n") |                 ->write("ob_start();\n") | ||||||
|                     ->indent() |                     ->indent() | ||||||
|                         ->subcompile($this->getNode('body')) |                         ->subcompile($this->getNode('body')) | ||||||
| @@ -53,6 +88,6 @@ class TwigNodeCache extends Node | |||||||
|                 ->write("\$cache->save(\$key, \$cache_body, \$lifetime);\n") |                 ->write("\$cache->save(\$key, \$cache_body, \$lifetime);\n") | ||||||
|             ->outdent() |             ->outdent() | ||||||
|             ->write("}\n") |             ->write("}\n") | ||||||
|             ->write("echo \$cache_body;\n"); |             ->write("echo '' === \$cache_body ? '' : new Markup(\$cache_body, \$this->env->getCharset());\n"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -11,7 +11,6 @@ namespace Grav\Common\Twig\TokenParser; | |||||||
|  |  | ||||||
| use Grav\Common\Grav; | use Grav\Common\Grav; | ||||||
| use Grav\Common\Twig\Node\TwigNodeCache; | use Grav\Common\Twig\Node\TwigNodeCache; | ||||||
| use Twig\Error\SyntaxError; |  | ||||||
| use Twig\Token; | use Twig\Token; | ||||||
| use Twig\TokenParser\AbstractTokenParser; | use Twig\TokenParser\AbstractTokenParser; | ||||||
|  |  | ||||||
| @@ -22,48 +21,52 @@ use Twig\TokenParser\AbstractTokenParser; | |||||||
|  * {{ some_complex_work() }} |  * {{ some_complex_work() }} | ||||||
|  * {% endcache %} |  * {% endcache %} | ||||||
|  * |  * | ||||||
|  * Where the `600` is an optional lifetime in seconds |  * Also can provide a unique key for the cache: | ||||||
|  |  * | ||||||
|  |  * {% cache "prefix-"~lang 600 %} | ||||||
|  |  * | ||||||
|  |  * Where the "prefix-"~lang will use a unique key based on the current language. "prefix-en" for example | ||||||
|  */ |  */ | ||||||
| class TwigTokenParserCache extends AbstractTokenParser | class TwigTokenParserCache extends AbstractTokenParser | ||||||
| { | { | ||||||
|     /** |  | ||||||
|      * @param Token $token |  | ||||||
|      * @return TwigNodeCache |  | ||||||
|      * @throws SyntaxError |  | ||||||
|      */ |  | ||||||
|     public function parse(Token $token) |     public function parse(Token $token) | ||||||
|     { |     { | ||||||
|         $lineno = $token->getLine(); |  | ||||||
|         $stream = $this->parser->getStream(); |         $stream = $this->parser->getStream(); | ||||||
|         $key = $this->parser->getVarName() . $lineno; |         $lineno = $token->getLine(); | ||||||
|         $lifetime = Grav::instance()['cache']->getLifetime(); |  | ||||||
|  |  | ||||||
|         // Check for optional lifetime override |         // Parse the optional key and timeout parameters | ||||||
|         if (!$stream->test(Token::BLOCK_END_TYPE)) { |         $defaults = [ | ||||||
|             $lifetime_expr = $this->parser->getExpressionParser()->parseExpression(); |             'key' => $this->parser->getVarName() . $lineno, | ||||||
|             $lifetime = $lifetime_expr->getAttribute('value'); |             'lifetime' => Grav::instance()['cache']->getLifetime() | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         $key = null; | ||||||
|  |         $lifetime = null; | ||||||
|  |         while (!$stream->test(Token::BLOCK_END_TYPE)) { | ||||||
|  |             if ($stream->test(Token::STRING_TYPE)) { | ||||||
|  |                 $key = $this->parser->getExpressionParser()->parseExpression(); | ||||||
|  |             } elseif ($stream->test(Token::NUMBER_TYPE)) { | ||||||
|  |                 $lifetime = $this->parser->getExpressionParser()->parseExpression(); | ||||||
|  |             } else { | ||||||
|  |                 throw new \Twig\Error\SyntaxError("Unexpected token type in cache tag.", $token->getLine(), $stream->getSourceContext()); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $stream->expect(Token::BLOCK_END_TYPE); |         $stream->expect(Token::BLOCK_END_TYPE); | ||||||
|         $body = $this->parser->subparse(array($this, 'decideCacheEnd'), true); |  | ||||||
|  |         // Parse the content inside the cache block | ||||||
|  |         $body = $this->parser->subparse([$this, 'decideCacheEnd'], true); | ||||||
|  |  | ||||||
|         $stream->expect(Token::BLOCK_END_TYPE); |         $stream->expect(Token::BLOCK_END_TYPE); | ||||||
|  |  | ||||||
|         return new TwigNodeCache($key, $lifetime, $body, $lineno, $this->getTag()); |         return new TwigNodeCache($body, $key, $lifetime, $defaults, $lineno, $this->getTag()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Decide if current token marks end of cache block. |  | ||||||
|      * |  | ||||||
|      * @param Token $token |  | ||||||
|      * @return bool |  | ||||||
|      */ |  | ||||||
|     public function decideCacheEnd(Token $token): bool |     public function decideCacheEnd(Token $token): bool | ||||||
|     { |     { | ||||||
|         return $token->test('endcache'); |         return $token->test('endcache'); | ||||||
|     } |     } | ||||||
|     /** |  | ||||||
|      * {@inheritDoc} |  | ||||||
|      */ |  | ||||||
|     public function getTag(): string |     public function getTag(): string | ||||||
|     { |     { | ||||||
|         return 'cache'; |         return 'cache'; | ||||||
|   | |||||||
| @@ -57,6 +57,15 @@ class Twig | |||||||
|     /** @var string */ |     /** @var string */ | ||||||
|     public $template; |     public $template; | ||||||
|  |  | ||||||
|  |     /** @var array */ | ||||||
|  |     public $plugins_hooked_nav = []; | ||||||
|  |     /** @var array */ | ||||||
|  |     public $plugins_quick_tray = []; | ||||||
|  |     /** @var array */ | ||||||
|  |     public $plugins_hooked_dashboard_widgets_top = []; | ||||||
|  |     /** @var array */ | ||||||
|  |     public $plugins_hooked_dashboard_widgets_main = []; | ||||||
|  |  | ||||||
|     /** @var Grav */ |     /** @var Grav */ | ||||||
|     protected $grav; |     protected $grav; | ||||||
|     /** @var FilesystemLoader */ |     /** @var FilesystemLoader */ | ||||||
| @@ -493,13 +502,19 @@ class Twig | |||||||
|     /** |     /** | ||||||
|      * Simple helper method to get the twig template if it has already been set, else return |      * Simple helper method to get the twig template if it has already been set, else return | ||||||
|      * the one being passed in |      * the one being passed in | ||||||
|  |      * NOTE: Modular pages that are injected should not use this pre-set template as it's usually set at the page level | ||||||
|      * |      * | ||||||
|      * @param  string $template the template name |      * @param  string $template the template name | ||||||
|      * @return string           the template name |      * @return string           the template name | ||||||
|      */ |      */ | ||||||
|     public function template($template) |     public function template(string $template): string | ||||||
|     { |     { | ||||||
|         return $this->template ?? $template; |         if (isset($this->template)) { | ||||||
|  |             $template = $this->template; | ||||||
|  |             unset($this->template); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return $template; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -513,7 +528,7 @@ class Twig | |||||||
|         $default = $page->isModule() ? 'modular/default' : 'default'; |         $default = $page->isModule() ? 'modular/default' : 'default'; | ||||||
|         $extension = $format ?: $page->templateFormat(); |         $extension = $format ?: $page->templateFormat(); | ||||||
|         $twig_extension = $extension ? '.'. $extension .TWIG_EXT : TEMPLATE_EXT; |         $twig_extension = $extension ? '.'. $extension .TWIG_EXT : TEMPLATE_EXT; | ||||||
|         $template_file = $this->template($page->template() . $twig_extension); |         $template_file = $this->template($template . $twig_extension); | ||||||
|  |  | ||||||
|         // TODO: no longer needed in Twig 3. |         // TODO: no longer needed in Twig 3. | ||||||
|         /** @var ExistsLoaderInterface $loader */ |         /** @var ExistsLoaderInterface $loader */ | ||||||
|   | |||||||
| @@ -1005,7 +1005,7 @@ class Uri | |||||||
|             foreach ($matches as $match) { |             foreach ($matches as $match) { | ||||||
|                 $param = explode($delimiter, $match[1]); |                 $param = explode($delimiter, $match[1]); | ||||||
|                 if (count($param) === 2) { |                 if (count($param) === 2) { | ||||||
|                     $plain_var = filter_var(rawurldecode($param[1]), FILTER_SANITIZE_STRING); |                     $plain_var = htmlspecialchars(strip_tags(rawurldecode($param[1])), ENT_QUOTES, 'UTF-8'); | ||||||
|                     $params[$param[0]] = $plain_var; |                     $params[$param[0]] = $plain_var; | ||||||
|                     $uri = str_replace($match[0], '', $uri); |                     $uri = str_replace($match[0], '', $uri); | ||||||
|                 } |                 } | ||||||
| @@ -1388,8 +1388,12 @@ class Uri | |||||||
|         if ($this->post && null !== $element) { |         if ($this->post && null !== $element) { | ||||||
|             $item = Utils::getDotNotation($this->post, $element); |             $item = Utils::getDotNotation($this->post, $element); | ||||||
|             if ($filter_type) { |             if ($filter_type) { | ||||||
|  |                 if ($filter_type === FILTER_SANITIZE_STRING || $filter_type === GRAV_SANITIZE_STRING) { | ||||||
|  |                     $item = htmlspecialchars(strip_tags($item), ENT_QUOTES, 'UTF-8'); | ||||||
|  |                 } else { | ||||||
|                     $item = filter_var($item, $filter_type); |                     $item = filter_var($item, $filter_type); | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|             return $item; |             return $item; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -1514,7 +1518,7 @@ class Uri | |||||||
|             foreach ($matches as $match) { |             foreach ($matches as $match) { | ||||||
|                 $param = explode($delimiter, $match[1]); |                 $param = explode($delimiter, $match[1]); | ||||||
|                 if (count($param) === 2) { |                 if (count($param) === 2) { | ||||||
|                     $plain_var = filter_var($param[1], FILTER_SANITIZE_STRING); |                     $plain_var = htmlspecialchars(strip_tags($param[1]), ENT_QUOTES, 'UTF-8'); | ||||||
|                     $this->params[$param[0]] = $plain_var; |                     $this->params[$param[0]] = $plain_var; | ||||||
|                     $uri = str_replace($match[0], '', $uri); |                     $uri = str_replace($match[0], '', $uri); | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -201,7 +201,7 @@ abstract class Utils | |||||||
|         $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos'; |         $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos'; | ||||||
|  |  | ||||||
|         foreach ((array)$needle as $each_needle) { |         foreach ((array)$needle as $each_needle) { | ||||||
|             $status = $each_needle === '' || $compare_func($haystack, $each_needle) === 0; |             $status = $each_needle === '' || $compare_func((string) $haystack, $each_needle) === 0; | ||||||
|             if ($status) { |             if ($status) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| @@ -225,8 +225,8 @@ abstract class Utils | |||||||
|         $compare_func = $case_sensitive ? 'mb_strrpos' : 'mb_strripos'; |         $compare_func = $case_sensitive ? 'mb_strrpos' : 'mb_strripos'; | ||||||
|  |  | ||||||
|         foreach ((array)$needle as $each_needle) { |         foreach ((array)$needle as $each_needle) { | ||||||
|             $expectedPosition = mb_strlen($haystack) - mb_strlen($each_needle); |             $expectedPosition = mb_strlen((string) $haystack) - mb_strlen($each_needle); | ||||||
|             $status = $each_needle === '' || $compare_func($haystack, $each_needle, 0) === $expectedPosition; |             $status = $each_needle === '' || $compare_func((string) $haystack, $each_needle, 0) === $expectedPosition; | ||||||
|             if ($status) { |             if ($status) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| @@ -250,7 +250,7 @@ abstract class Utils | |||||||
|         $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos'; |         $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos'; | ||||||
|  |  | ||||||
|         foreach ((array)$needle as $each_needle) { |         foreach ((array)$needle as $each_needle) { | ||||||
|             $status = $each_needle === '' || $compare_func($haystack, $each_needle) !== false; |             $status = $each_needle === '' || $compare_func((string) $haystack, $each_needle) !== false; | ||||||
|             if ($status) { |             if ($status) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
| @@ -1145,9 +1145,9 @@ abstract class Utils | |||||||
|             $offset_prefix = $offset < 0 ? '-' : '+'; |             $offset_prefix = $offset < 0 ? '-' : '+'; | ||||||
|             $offset_formatted = gmdate('H:i', abs($offset)); |             $offset_formatted = gmdate('H:i', abs($offset)); | ||||||
|  |  | ||||||
|             $pretty_offset = "UTC${offset_prefix}${offset_formatted}"; |             $pretty_offset = "UTC{$offset_prefix}{$offset_formatted}"; | ||||||
|  |  | ||||||
|             $timezone_list[$timezone] = "(${pretty_offset}) " . str_replace('_', ' ', $timezone); |             $timezone_list[$timezone] = "({$pretty_offset}) " . str_replace('_', ' ', $timezone); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return $timezone_list; |         return $timezone_list; | ||||||
| @@ -1874,9 +1874,9 @@ abstract class Utils | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if ($block) { |         if ($block) { | ||||||
|             $string = $parsedown->text($string); |             $string = $parsedown->text((string) $string); | ||||||
|         } else { |         } else { | ||||||
|             $string = $parsedown->line($string); |             $string = $parsedown->line((string) $string); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return $string; |         return $string; | ||||||
| @@ -1950,10 +1950,10 @@ abstract class Utils | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param string $name |      * @param string|array|Closure $name | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public static function isDangerousFunction(string $name): bool |     public static function isDangerousFunction($name): bool | ||||||
|     { |     { | ||||||
|         static $commandExecutionFunctions = [ |         static $commandExecutionFunctions = [ | ||||||
|             'exec', |             'exec', | ||||||
| @@ -2048,8 +2048,30 @@ abstract class Utils | |||||||
|             'posix_setpgid', |             'posix_setpgid', | ||||||
|             'posix_setsid', |             'posix_setsid', | ||||||
|             'posix_setuid', |             'posix_setuid', | ||||||
|  |             'unserialize', | ||||||
|  |             'ini_alter', | ||||||
|  |             'simplexml_load_file', | ||||||
|  |             'simplexml_load_string', | ||||||
|  |             'forward_static_call', | ||||||
|  |             'forward_static_call_array', | ||||||
|         ]; |         ]; | ||||||
|  |  | ||||||
|  |         if (is_string($name)) { | ||||||
|  |             $name = strtolower($name); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($name instanceof \Closure) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (is_array($name) || strpos($name, ":") !== false) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (strpos($name, "\\") !== false) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (in_array($name, $commandExecutionFunctions)) { |         if (in_array($name, $commandExecutionFunctions)) { | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ class LogViewerCommand extends GravCommand | |||||||
|                 if ($log['trace'] && $verbose) { |                 if ($log['trace'] && $verbose) { | ||||||
|                     $output .= " <white>{$log['message']}</white>\n"; |                     $output .= " <white>{$log['message']}</white>\n"; | ||||||
|                     foreach ((array) $log['trace'] as $index => $tracerow) { |                     foreach ((array) $log['trace'] as $index => $tracerow) { | ||||||
|                         $output .= "<white>{$index}</white>${tracerow}\n"; |                         $output .= "<white>{$index}</white>{$tracerow}\n"; | ||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     $output .= " {$log['message']}"; |                     $output .= " {$log['message']}"; | ||||||
|   | |||||||
| @@ -317,7 +317,7 @@ class InstallCommand extends GpmCommand | |||||||
|                 $questionNoun = 'packages'; |                 $questionNoun = 'packages'; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             $question = new ConfirmationQuestion("${questionAction} {$questionArticle} {$questionNoun}? [Y|n] ", true); |             $question = new ConfirmationQuestion("{$questionAction} {$questionArticle} {$questionNoun}? [Y|n] ", true); | ||||||
|             $answer = $this->all_yes ? true : $io->askQuestion($question); |             $answer = $this->all_yes ? true : $io->askQuestion($question); | ||||||
|  |  | ||||||
|             if ($answer) { |             if ($answer) { | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								system/src/Grav/Events/PageEvent.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								system/src/Grav/Events/PageEvent.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @package    Grav\Events | ||||||
|  |  * | ||||||
|  |  * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved. | ||||||
|  |  * @license    MIT License; see LICENSE file for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | namespace Grav\Events; | ||||||
|  |  | ||||||
|  | use Grav\Framework\Flex\Flex; | ||||||
|  | use RocketTheme\Toolbox\Event\Event; | ||||||
|  |  | ||||||
|  | class PageEvent extends Event | ||||||
|  | { | ||||||
|  |     public $page; | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								system/src/Grav/Events/TypesEvent.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								system/src/Grav/Events/TypesEvent.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @package    Grav\Events | ||||||
|  |  * | ||||||
|  |  * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved. | ||||||
|  |  * @license    MIT License; see LICENSE file for details. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | namespace Grav\Events; | ||||||
|  |  | ||||||
|  | use Grav\Framework\Flex\Flex; | ||||||
|  | use RocketTheme\Toolbox\Event\Event; | ||||||
|  |  | ||||||
|  | class TypesEvent extends Event | ||||||
|  | { | ||||||
|  |     public $types; | ||||||
|  | } | ||||||
| @@ -23,6 +23,8 @@ class RecursiveActionIterator implements RecursiveIterator, \Countable | |||||||
| { | { | ||||||
|     use Constructor, Iterator, Countable; |     use Constructor, Iterator, Countable; | ||||||
|  |  | ||||||
|  |     public $items; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @see \Iterator::key() |      * @see \Iterator::key() | ||||||
|      * @return string |      * @return string | ||||||
|   | |||||||
| @@ -366,9 +366,14 @@ trait PageLegacyTrait | |||||||
|      */ |      */ | ||||||
|     public function blueprintName(): string |     public function blueprintName(): string | ||||||
|     { |     { | ||||||
|         $blueprint_name = filter_input(INPUT_POST, 'blueprint', FILTER_SANITIZE_STRING) ?: $this->template(); |         if (!isset($_POST['blueprint'])) { | ||||||
|  |             return $this->template(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return $blueprint_name; |         $post_value = $_POST['blueprint']; | ||||||
|  |         $sanitized_value = htmlspecialchars(strip_tags($post_value), ENT_QUOTES, 'UTF-8'); | ||||||
|  |  | ||||||
|  |         return $sanitized_value ?: $this->template(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ trait NestedPropertyTrait | |||||||
|     public function getNestedProperty($property, $default = null, $separator = null) |     public function getNestedProperty($property, $default = null, $separator = null) | ||||||
|     { |     { | ||||||
|         $separator = $separator ?: '.'; |         $separator = $separator ?: '.'; | ||||||
|         $path = explode($separator, $property); |         $path = explode($separator, (string) $property); | ||||||
|         $offset = array_shift($path); |         $offset = array_shift($path); | ||||||
|  |  | ||||||
|         if (!$this->hasProperty($offset)) { |         if (!$this->hasProperty($offset)) { | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ namespace Grav\Framework\Session; | |||||||
|  |  | ||||||
| use ArrayIterator; | use ArrayIterator; | ||||||
| use Exception; | use Exception; | ||||||
|  | use Throwable; | ||||||
| use Grav\Common\Debugger; | use Grav\Common\Debugger; | ||||||
| use Grav\Common\Grav; | use Grav\Common\Grav; | ||||||
| use Grav\Common\User\Interfaces\UserInterface; | use Grav\Common\User\Interfaces\UserInterface; | ||||||
| @@ -254,13 +255,17 @@ class Session implements SessionInterface | |||||||
|         $this->started = true; |         $this->started = true; | ||||||
|         $this->onSessionStart(); |         $this->onSessionStart(); | ||||||
|  |  | ||||||
|  |         try { | ||||||
|             $user = $this->__get('user'); |             $user = $this->__get('user'); | ||||||
|             if ($user && (!$user instanceof UserInterface || (method_exists($user, 'isValid') && !$user->isValid()))) { |             if ($user && (!$user instanceof UserInterface || (method_exists($user, 'isValid') && !$user->isValid()))) { | ||||||
|  |                 throw new RuntimeException('Bad user'); | ||||||
|  |             } | ||||||
|  |         } catch (Throwable $e) { | ||||||
|             $this->invalidate(); |             $this->invalidate(); | ||||||
|  |  | ||||||
|             throw new SessionException('Invalid User object, session destroyed.', 500); |             throw new SessionException('Invalid User object, session destroyed.', 500); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|         // Extend the lifetime of the session. |         // Extend the lifetime of the session. | ||||||
|         if ($sessionExists) { |         if ($sessionExists) { | ||||||
|             $this->setCookie(); |             $this->setCookie(); | ||||||
|   | |||||||
| @@ -93,7 +93,7 @@ class UriFactory | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Support ngnix routes. |         // Support ngnix routes. | ||||||
|         if (strpos($query, '_url=') === 0) { |         if (strpos((string) $query, '_url=') === 0) { | ||||||
|             parse_str($query, $q); |             parse_str($query, $q); | ||||||
|             unset($q['_url']); |             unset($q['_url']); | ||||||
|             $query = http_build_query($q); |             $query = http_build_query($q); | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								system/src/Twig/DeferredExtension/DeferredDeclareNode.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								system/src/Twig/DeferredExtension/DeferredDeclareNode.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This file is part of the rybakit/twig-deferred-extension package. | ||||||
|  |  * | ||||||
|  |  * (c) Eugene Leonovich <gen.work@gmail.com> | ||||||
|  |  * | ||||||
|  |  * For the full copyright and license information, please view the LICENSE | ||||||
|  |  * file that was distributed with this source code. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Twig\DeferredExtension; | ||||||
|  |  | ||||||
|  | use Twig\Compiler; | ||||||
|  | use Twig\Node\Node; | ||||||
|  |  | ||||||
|  | final class DeferredDeclareNode extends Node | ||||||
|  | { | ||||||
|  |     public function compile(Compiler $compiler) : void | ||||||
|  |     { | ||||||
|  |         $compiler | ||||||
|  |             ->write("private \$deferred;\n") | ||||||
|  |         ; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -16,7 +16,7 @@ namespace Twig\DeferredExtension; | |||||||
| use Twig\Compiler; | use Twig\Compiler; | ||||||
| use Twig\Node\Node; | use Twig\Node\Node; | ||||||
| 
 | 
 | ||||||
| final class DeferredExtensionNode extends Node | final class DeferredInitializeNode extends Node | ||||||
| { | { | ||||||
|     public function compile(Compiler $compiler) : void |     public function compile(Compiler $compiler) : void | ||||||
|     { |     { | ||||||
| @@ -34,8 +34,9 @@ final class DeferredNodeVisitor implements NodeVisitorInterface | |||||||
|     public function leaveNode(Node $node, Environment $env) : ?Node |     public function leaveNode(Node $node, Environment $env) : ?Node | ||||||
|     { |     { | ||||||
|         if ($this->hasDeferred && $node instanceof ModuleNode) { |         if ($this->hasDeferred && $node instanceof ModuleNode) { | ||||||
|             $node->setNode('constructor_end', new Node([new DeferredExtensionNode(), $node->getNode('constructor_end')])); |             $node->getNode('constructor_end')->setNode('deferred_initialize', new DeferredInitializeNode()); | ||||||
|             $node->setNode('display_end', new Node([new DeferredNode(), $node->getNode('display_end')])); |             $node->getNode('display_end')->setNode('deferred_resolve', new DeferredResolveNode()); | ||||||
|  |             $node->getNode('class_end')->setNode('deferred_declare', new DeferredDeclareNode()); | ||||||
|             $this->hasDeferred = false; |             $this->hasDeferred = false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -46,8 +46,9 @@ final class DeferredNodeVisitorCompat implements NodeVisitorInterface | |||||||
|     public function leaveNode(\Twig_NodeInterface $node, Environment $env): ?Node |     public function leaveNode(\Twig_NodeInterface $node, Environment $env): ?Node | ||||||
|     { |     { | ||||||
|         if ($this->hasDeferred && $node instanceof ModuleNode) { |         if ($this->hasDeferred && $node instanceof ModuleNode) { | ||||||
|             $node->setNode('constructor_end', new Node([new DeferredExtensionNode(), $node->getNode('constructor_end')])); |             $node->getNode('constructor_end')->setNode('deferred_initialize', new DeferredInitializeNode()); | ||||||
|             $node->setNode('display_end', new Node([new DeferredNode(), $node->getNode('display_end')])); |             $node->getNode('display_end')->setNode('deferred_resolve', new DeferredResolveNode()); | ||||||
|  |             $node->getNode('class_end')->setNode('deferred_declare', new DeferredDeclareNode()); | ||||||
|             $this->hasDeferred = false; |             $this->hasDeferred = false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								system/src/Twig/DeferredExtension/DeferredResolveNode.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								system/src/Twig/DeferredExtension/DeferredResolveNode.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This file is part of the rybakit/twig-deferred-extension package. | ||||||
|  |  * | ||||||
|  |  * (c) Eugene Leonovich <gen.work@gmail.com> | ||||||
|  |  * | ||||||
|  |  * For the full copyright and license information, please view the LICENSE | ||||||
|  |  * file that was distributed with this source code. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Twig\DeferredExtension; | ||||||
|  |  | ||||||
|  | use Twig\Compiler; | ||||||
|  | use Twig\Node\Node; | ||||||
|  |  | ||||||
|  | final class DeferredResolveNode extends Node | ||||||
|  | { | ||||||
|  |     public function compile(Compiler $compiler) : void | ||||||
|  |     { | ||||||
|  |         $compiler | ||||||
|  |             ->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n") | ||||||
|  |         ; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| core: | core: | ||||||
|   grav: |   grav: | ||||||
|     version: 1.7.38 |     version: 1.7.42.3 | ||||||
|     schema: 1.7.0_2020-11-20_1 |     schema: 1.7.0_2020-11-20_1 | ||||||
|     history: |     history: | ||||||
|       - { version: 1.7.16, date: '2021-06-10 14:03:35' } |       - { version: 1.7.16, date: '2021-06-10 14:03:35' } | ||||||
| @@ -8,3 +8,4 @@ core: | |||||||
|       - { version: 1.7.25, date: '2021-12-06 12:22:00' } |       - { version: 1.7.25, date: '2021-12-06 12:22:00' } | ||||||
|       - { version: 1.7.31, date: '2022-03-15 08:48:47' } |       - { version: 1.7.31, date: '2022-03-15 08:48:47' } | ||||||
|       - { version: 1.7.38, date: '2023-01-03 15:06:08' } |       - { version: 1.7.38, date: '2023-01-03 15:06:08' } | ||||||
|  |       - { version: 1.7.42.3, date: '2023-09-19 10:47:33' } | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ server { | |||||||
|     ## Begin - PHP |     ## Begin - PHP | ||||||
|     location ~ \.php$ { |     location ~ \.php$ { | ||||||
|         # Choose either a socket or TCP/IP address |         # Choose either a socket or TCP/IP address | ||||||
|         fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; |         fastcgi_pass unix:/var/run/php/php-fpm.sock; | ||||||
|         # fastcgi_pass unix:/var/run/php5-fpm.sock; #legacy |         # fastcgi_pass unix:/var/run/php5-fpm.sock; #legacy | ||||||
|         # fastcgi_pass 127.0.0.1:9000; |         # fastcgi_pass 127.0.0.1:9000; | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user