bach 7 months ago
parent
commit
adc7d775dc
47 changed files with 698 additions and 340 deletions
  1. 112 1
      CHANGELOG.md
  2. 170 224
      composer.lock
  3. 0 1
      system/assets/debugger/phpdebugbar.css
  4. 11 1
      system/blueprints/config/system.yaml
  5. 1 1
      system/blueprints/user/account.yaml
  6. 1 0
      system/config/system.yaml
  7. 4 1
      system/defines.php
  8. 7 1
      system/src/Grav/Common/Assets.php
  9. 1 0
      system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
  10. 2 1
      system/src/Grav/Common/Data/BlueprintSchema.php
  11. 5 1
      system/src/Grav/Common/Data/Validation.php
  12. 1 1
      system/src/Grav/Common/Errors/SimplePageHandler.php
  13. 13 0
      system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php
  14. 1 1
      system/src/Grav/Common/Helpers/LogViewer.php
  15. 1 1
      system/src/Grav/Common/Helpers/Truncator.php
  16. 1 0
      system/src/Grav/Common/Markdown/Parsedown.php
  17. 17 1
      system/src/Grav/Common/Markdown/ParsedownGravTrait.php
  18. 9 9
      system/src/Grav/Common/Page/Medium/ImageMedium.php
  19. 8 3
      system/src/Grav/Common/Page/Page.php
  20. 4 3
      system/src/Grav/Common/Page/Pages.php
  21. 2 1
      system/src/Grav/Common/Processors/PagesProcessor.php
  22. 1 1
      system/src/Grav/Common/Scheduler/Scheduler.php
  23. 2 2
      system/src/Grav/Common/Service/PagesServiceProvider.php
  24. 2 2
      system/src/Grav/Common/Service/TaskServiceProvider.php
  25. 4 4
      system/src/Grav/Common/Session.php
  26. 64 9
      system/src/Grav/Common/Twig/Extension/GravExtension.php
  27. 47 12
      system/src/Grav/Common/Twig/Node/TwigNodeCache.php
  28. 29 26
      system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php
  29. 18 3
      system/src/Grav/Common/Twig/Twig.php
  30. 7 3
      system/src/Grav/Common/Uri.php
  31. 32 10
      system/src/Grav/Common/Utils.php
  32. 1 1
      system/src/Grav/Console/Cli/LogViewerCommand.php
  33. 1 1
      system/src/Grav/Console/Gpm/InstallCommand.php
  34. 18 0
      system/src/Grav/Events/PageEvent.php
  35. 18 0
      system/src/Grav/Events/TypesEvent.php
  36. 2 0
      system/src/Grav/Framework/Acl/RecursiveActionIterator.php
  37. 7 2
      system/src/Grav/Framework/Flex/Pages/Traits/PageLegacyTrait.php
  38. 1 1
      system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php
  39. 8 3
      system/src/Grav/Framework/Session/Session.php
  40. 1 1
      system/src/Grav/Framework/Uri/UriFactory.php
  41. 27 0
      system/src/Twig/DeferredExtension/DeferredDeclareNode.php
  42. 1 1
      system/src/Twig/DeferredExtension/DeferredInitializeNode.php
  43. 3 2
      system/src/Twig/DeferredExtension/DeferredNodeVisitor.php
  44. 3 2
      system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php
  45. 27 0
      system/src/Twig/DeferredExtension/DeferredResolveNode.php
  46. 2 1
      user/config/versions.yaml
  47. 1 1
      webserver-configs/nginx.conf

+ 112 - 1
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
 ## 01/02/2023
 
@@ -7,7 +119,6 @@
    * Vendor library updates to latest versions
    * Updated `bin/composer.phar` to latest `2.4.4` version [#3627](https://github.com/getgrav/grav/issues/3627)
 1. [](#bugfix)
-
    * Don't fail hard if pages recurse with same path
    * Github workflows security hardening [#3624](https://github.com/getgrav/grav/pull/3624)
 

File diff suppressed because it is too large
+ 170 - 224
composer.lock


File diff suppressed because it is too large
+ 0 - 1
system/assets/debugger/phpdebugbar.css


+ 11 - 1
system/blueprints/config/system.yaml

@@ -448,6 +448,17 @@ form:
               validate:
                 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:
           type: tab
           title: PLUGIN_ADMIN.HTTP_HEADERS
@@ -608,7 +619,6 @@ form:
                 file: File
                 apc: APC
                 apcu: APCu
-                xcache: Xcache
                 memcache: Memcache
                 memcached: Memcached
                 wincache: WinCache

+ 1 - 1
system/blueprints/user/account.yaml

@@ -140,7 +140,7 @@ form:
                     multiple: true
                     size: large
                     label: PLUGIN_ADMIN.GROUPS
-                    data-options@: '\Grav\Common\User\Group::groupNames'
+                    data-options@: 'Grav\Common\Flex\Types\UserGroups\UserGroupObject::groupNames'
                     classes: fancy
                     help: PLUGIN_ADMIN.GROUPS_HELP
                     validate:

+ 1 - 0
system/config/system.yaml

@@ -28,6 +28,7 @@ languages:
   override_locale: false                         # Override the default or system locale with language specific one
   content_fallback: {}                           # Custom language fallbacks. eg: {fr: ['fr', 'en']}
   pages_fallback_only: false                     # DEPRECATED: Use `content_fallback` instead
+  debug: false                                   # Debug language detection
 
 home:
   alias: '/home'                                 # Default path for home, ie /

+ 4 - 1
system/defines.php

@@ -9,7 +9,7 @@
 
 // Some standard defines
 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_TESTING', false);
 
@@ -99,3 +99,6 @@ define('RAW_CONTENT', 1);
 define('TWIG_CONTENT', 2);
 define('TWIG_CONTENT_LIST', 3);
 define('TWIG_TEMPLATES', 4);
+
+// Filters
+define('GRAV_SANITIZE_STRING', 5001);

+ 7 - 1
system/src/Grav/Common/Assets.php

@@ -268,7 +268,13 @@ class Assets extends PropertyObject
         }
 
         // Add timestamp
-        $options['timestamp'] = $this->timestamp;
+        $timestamp_override = $options['timestamp'] ?? true;
+
+        if (filter_var($timestamp_override, FILTER_VALIDATE_BOOLEAN)) {
+            $options['timestamp'] = $this->timestamp;
+        } else {
+            $options['timestamp'] = null;
+        }
 
         // Set order
         $group = $options['group'] ?? 'head';

+ 1 - 0
system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php

@@ -192,6 +192,7 @@ trait AssetUtilsTrait
         $querystring = '';
 
         $asset = $asset ?? $this->asset;
+        $attributes = $this->attributes;
 
         if (!empty($this->query)) {
             if (Utils::contains($asset, '?')) {

+ 2 - 1
system/src/Grav/Common/Data/BlueprintSchema.php

@@ -129,7 +129,8 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
             $items = $name !== '' ? $this->getProperty($name)['fields'] ?? [] : $this->items;
             foreach ($items as $key => $rules) {
                 $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;
                 }
             }

+ 5 - 1
system/src/Grav/Common/Data/Validation.php

@@ -631,6 +631,10 @@ class Validation
      */
     public static function typeEmail($value, array $params, array $field)
     {
+        if (empty($value)) {
+            return false;
+        }
+
         if (!isset($params['max'])) {
             $params['max'] = 320;
         }
@@ -638,7 +642,7 @@ class Validation
         $values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
 
         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;
             }
         }

+ 1 - 1
system/src/Grav/Common/Errors/SimplePageHandler.php

@@ -57,7 +57,7 @@ class SimplePageHandler extends Handler
         $vars = array(
             'stylesheet' => file_get_contents($cssFile),
             'code'        => $code,
-            'message'     => filter_var(rawurldecode($message), FILTER_SANITIZE_STRING),
+            'message'     => htmlspecialchars(strip_tags(rawurldecode($message)), ENT_QUOTES, 'UTF-8'),
         );
 
         $helper->setVariables($vars);

+ 13 - 0
system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php

@@ -12,6 +12,7 @@ declare(strict_types=1);
 namespace Grav\Common\Flex\Types\UserGroups;
 
 use Grav\Common\Flex\FlexObject;
+use Grav\Common\Grav;
 use Grav\Common\User\Access;
 use Grav\Common\User\Interfaces\UserGroupInterface;
 use function is_bool;
@@ -74,6 +75,18 @@ class UserGroupObject extends FlexObject implements UserGroupInterface
         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
      */

+ 1 - 1
system/src/Grav/Common/Helpers/LogViewer.php

@@ -21,7 +21,7 @@ use function is_string;
 class LogViewer
 {
     /** @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

+ 1 - 1
system/src/Grav/Common/Helpers/Truncator.php

@@ -144,7 +144,7 @@ class Truncator
         }
 
         // 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.
         libxml_use_internal_errors(true);

+ 1 - 0
system/src/Grav/Common/Markdown/Parsedown.php

@@ -18,6 +18,7 @@ use Grav\Common\Page\Markdown\Excerpts;
  */
 class Parsedown extends \Parsedown
 {
+
     use ParsedownGravTrait;
 
     /**

+ 17 - 1
system/src/Grav/Common/Markdown/ParsedownGravTrait.php

@@ -25,6 +25,7 @@ trait ParsedownGravTrait
     public $completable_blocks = [];
     /** @var array */
     public $continuable_blocks = [];
+    public $plugins = [];
 
     /** @var Excerpts */
     protected $excerpts;
@@ -292,7 +293,12 @@ trait ParsedownGravTrait
     #[\ReturnTypeWillChange]
     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};
 
             return call_user_func_array($func, $args);
@@ -300,4 +306,14 @@ trait ParsedownGravTrait
 
         return null;
     }
+
+    public function __set($name, $value)
+    {
+        if (is_callable($value)) {
+            $this->plugins[$name] = $value;
+        }
+
+    }
+
+
 }

+ 9 - 9
system/src/Grav/Common/Page/Medium/ImageMedium.php

@@ -62,8 +62,8 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
         if (!($this->offsetExists('width') && $this->offsetExists('height') && $this->offsetExists('mime'))) {
             $image_info = getimagesize($path);
             if ($image_info) {
-                $this->def('width', $image_info[0]);
-                $this->def('height', $image_info[1]);
+                $this->def('width', (int) $image_info[0]);
+                $this->def('height', (int) $image_info[1]);
                 $this->def('mime', $image_info['mime']);
             }
         }
@@ -299,7 +299,7 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
         }
 
         if ($width && $height) {
-            $this->__call('cropResize', [$width, $height]);
+            $this->__call('cropResize', [(int) $width, (int) $height]);
         }
 
         return parent::lightbox($width, $height, $reset);
@@ -361,8 +361,8 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
 
         // Scaling operations
         $scale     = ($scale ?? $config->get('system.images.watermark.scale', 100)) / 100;
-        $wwidth    = (int)$this->get('width')  * $scale;
-        $wheight   = (int)$this->get('height') * $scale;
+        $wwidth    = (int) ($this->get('width')  * $scale);
+        $wheight   = (int) ($this->get('height') * $scale);
         $watermark->resize($wwidth, $wheight);
 
         // Position operations
@@ -392,11 +392,11 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
                 break;
 
             case 'right':
-                $positionX = (int)$this->get('width')-$wwidth;
+                $positionX = (int) ($this->get('width')-$wwidth);
                 break;
 
             case 'center':
-                $positionX = ((int)$this->get('width')/2) - ($wwidth/2);
+                $positionX = (int) (($this->get('width')/2) - ($wwidth/2));
                 break;
         }
 
@@ -431,8 +431,8 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
         return $this;
       }
 
-      $dst_width = $image->width()+2*$border;
-      $dst_height = $image->height()+2*$border;
+      $dst_width = (int) ($image->width()+2*$border);
+      $dst_height = (int) ($image->height()+2*$border);
 
       $frame = ImageFile::create($dst_width, $dst_height);
 

+ 8 - 3
system/src/Grav/Common/Page/Page.php

@@ -1270,9 +1270,14 @@ class Page implements PageInterface
      */
     public function blueprintName()
     {
-        $blueprint_name = filter_input(INPUT_POST, 'blueprint', FILTER_SANITIZE_STRING) ?: $this->template();
+        if (!isset($_POST['blueprint'])) {
+            return $this->template();
+        }
+
+        $post_value = $_POST['blueprint'];
+        $sanitized_value = htmlspecialchars(strip_tags($post_value), ENT_QUOTES, 'UTF-8');
 
-        return $blueprint_name;
+        return $sanitized_value ?: $this->template();
     }
 
     /**
@@ -1802,7 +1807,7 @@ class Page implements PageInterface
         }
 
         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;

+ 4 - 3
system/src/Grav/Common/Page/Pages.php

@@ -26,6 +26,7 @@ use Grav\Common\Page\Interfaces\PageInterface;
 use Grav\Common\Taxonomy;
 use Grav\Common\Uri;
 use Grav\Common\Utils;
+use Grav\Events\TypesEvent;
 use Grav\Framework\Flex\Flex;
 use Grav\Framework\Flex\FlexDirectory;
 use Grav\Framework\Flex\Interfaces\FlexTranslateInterface;
@@ -1289,7 +1290,7 @@ class Pages
 
             $scanBlueprintsAndTemplates = static function (Types $types) use ($grav) {
                 // Scan blueprints
-                $event = new Event();
+                $event = new TypesEvent();
                 $event->types = $types;
                 $grav->fireEvent('onGetPageBlueprints', $event);
 
@@ -1303,7 +1304,7 @@ class Pages
                 $types->scanBlueprints($lookup);
 
                 // Scan templates
-                $event = new Event();
+                $event = new TypesEvent();
                 $event->types = $types;
                 $grav->fireEvent('onGetPageTemplates', $event);
 
@@ -1773,7 +1774,7 @@ class Pages
         $dirs = (array) $grav['config']->get('system.pages.dirs', ['page://']);
         foreach ($dirs as $dir) {
             $path = $locator->findResource($dir);
-            if (file_exists($path)) {
+            if (file_exists($path) && !in_array($path, $paths, true)) {
                 $paths[] = $path;
             }
         }

+ 2 - 1
system/src/Grav/Common/Processors/PagesProcessor.php

@@ -10,6 +10,7 @@
 namespace Grav\Common\Processors;
 
 use Grav\Common\Page\Interfaces\PageInterface;
+use Grav\Events\PageEvent;
 use Grav\Framework\RequestHandler\Exception\RequestException;
 use Grav\Plugin\Form\Forms;
 use RocketTheme\Toolbox\Event\Event;
@@ -66,7 +67,7 @@ class PagesProcessor extends ProcessorBase
         if (!$page->routable()) {
             $exception = new RequestException($request, 'Page Not Found', 404);
             // If no page found, fire event
-            $event = new Event([
+            $event = new PageEvent([
                 'page' => $page,
                 'code' => $exception->getCode(),
                 'message' => $exception->getMessage(),

+ 1 - 1
system/src/Grav/Common/Scheduler/Scheduler.php

@@ -357,7 +357,7 @@ class Scheduler
      */
     public function whoami()
     {
-        $process = new Process('whoami');
+        $process = new Process(['whoami']);
         $process->run();
 
         if ($process->isSuccessful()) {

+ 2 - 2
system/src/Grav/Common/Service/PagesServiceProvider.php

@@ -59,7 +59,7 @@ class PagesServiceProvider implements ServiceProviderInterface
             /** @var Uri $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);
 
             // Redirection tests
@@ -72,7 +72,7 @@ class PagesServiceProvider implements ServiceProviderInterface
                 if ($config->get('system.force_ssl')) {
                     $scheme = $uri->scheme(true);
                     if ($scheme !== 'https') {
-                        $url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+                        $url = 'https://' . $uri->host() . $uri->uri();
                         $grav->redirect($url);
                     }
                 }

+ 2 - 2
system/src/Grav/Common/Service/TaskServiceProvider.php

@@ -33,7 +33,7 @@ class TaskServiceProvider implements ServiceProviderInterface
 
             $task = $body['task'] ?? $c['uri']->param('task');
             if (null !== $task) {
-                $task = filter_var($task, FILTER_SANITIZE_STRING);
+                $task = htmlspecialchars(strip_tags($task), ENT_QUOTES, 'UTF-8');
             }
 
             return $task ?: null;
@@ -46,7 +46,7 @@ class TaskServiceProvider implements ServiceProviderInterface
 
             $action = $body['action'] ?? $c['uri']->param('action');
             if (null !== $action) {
-                $action = filter_var($action, FILTER_SANITIZE_STRING);
+                $action = htmlspecialchars(strip_tags($action), ENT_QUOTES, 'UTF-8');
             }
 
             return $action ?: null;

+ 4 - 4
system/src/Grav/Common/Session.php

@@ -122,10 +122,10 @@ class Session extends \Grav\Framework\Session\Session
 
             // Make sure that Forms 3.0+ has been installed.
             if (null === $object && isset($grav['forms'])) {
-                user_error(
-                    __CLASS__ . '::' . __FUNCTION__ . '(\'files-upload\') is deprecated since Grav 1.6, use $form->getFlash()->getLegacyFiles() instead',
-                    E_USER_DEPRECATED
-                );
+//                user_error(
+//                    __CLASS__ . '::' . __FUNCTION__ . '(\'files-upload\') is deprecated since Grav 1.6, use $form->getFlash()->getLegacyFiles() instead',
+//                    E_USER_DEPRECATED
+//                );
 
                 /** @var Uri $uri */
                 $uri = $grav['uri'];

+ 64 - 9
system/src/Grav/Common/Twig/Extension/GravExtension.php

@@ -46,6 +46,7 @@ use Twig\Error\RuntimeError;
 use Twig\Extension\AbstractExtension;
 use Twig\Extension\GlobalsInterface;
 use Twig\Loader\FilesystemLoader;
+use Twig\Markup;
 use Twig\TwigFilter;
 use Twig\TwigFunction;
 use function array_slice;
@@ -170,8 +171,10 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
             new TwigFilter('count', 'count'),
             new TwigFilter('array_diff', 'array_diff'),
 
-            // Security fix
-            new TwigFilter('filter', [$this, 'filterFilter'], ['needs_environment' => true]),
+            // Security fixes
+            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('count', 'count'),
             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)
     {
-        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);
         }
 
-        // else use the default grav translate functionality
-        return $this->grav['language']->translate($args);
+        $translation = $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)
     {
-        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)
     {
+        if ($str === null) {
+            $str = '';
+        }
         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)
     {
-        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
      * @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.');
         }
 
         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);
+    }
 }

+ 47 - 12
system/src/Grav/Common/Twig/Node/TwigNodeCache.php

@@ -10,13 +10,15 @@
 namespace Grav\Common\Twig\Node;
 
 use Twig\Compiler;
+use Twig\Node\Expression\AbstractExpression;
 use Twig\Node\Node;
+use Twig\Node\NodeOutputInterface;
 
 /**
  * Class TwigNodeCache
  * @package Grav\Common\Twig\Node
  */
-class TwigNodeCache extends Node
+class TwigNodeCache extends Node implements NodeOutputInterface
 {
     /**
      * @param string    $key       unique name for key
@@ -25,25 +27,58 @@ class TwigNodeCache extends Node
      * @param integer   $lineno
      * @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
     {
-        $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
-            ->addDebugInfo($this)
             ->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("if (\$cache_body === false) {\n")
             ->indent()
+                ->write("\\Grav\\Common\\Grav::instance()['debugger']->addMessage(\"Cache Key: \$key, Lifetime: \$lifetime\");\n")
                 ->write("ob_start();\n")
                     ->indent()
                         ->subcompile($this->getNode('body'))
@@ -53,6 +88,6 @@ class TwigNodeCache extends Node
                 ->write("\$cache->save(\$key, \$cache_body, \$lifetime);\n")
             ->outdent()
             ->write("}\n")
-            ->write("echo \$cache_body;\n");
+            ->write("echo '' === \$cache_body ? '' : new Markup(\$cache_body, \$this->env->getCharset());\n");
     }
-}
+}

+ 29 - 26
system/src/Grav/Common/Twig/TokenParser/TwigTokenParserCache.php

@@ -11,7 +11,6 @@ namespace Grav\Common\Twig\TokenParser;
 
 use Grav\Common\Grav;
 use Grav\Common\Twig\Node\TwigNodeCache;
-use Twig\Error\SyntaxError;
 use Twig\Token;
 use Twig\TokenParser\AbstractTokenParser;
 
@@ -22,50 +21,54 @@ use Twig\TokenParser\AbstractTokenParser;
  * {{ some_complex_work() }}
  * {% 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
 {
-    /**
-     * @param Token $token
-     * @return TwigNodeCache
-     * @throws SyntaxError
-     */
     public function parse(Token $token)
     {
-        $lineno = $token->getLine();
         $stream = $this->parser->getStream();
-        $key = $this->parser->getVarName() . $lineno;
-        $lifetime = Grav::instance()['cache']->getLifetime();
+        $lineno = $token->getLine();
 
-        // Check for optional lifetime override
-        if (!$stream->test(Token::BLOCK_END_TYPE)) {
-            $lifetime_expr = $this->parser->getExpressionParser()->parseExpression();
-            $lifetime = $lifetime_expr->getAttribute('value');
+        // Parse the optional key and timeout parameters
+        $defaults = [
+            'key' => $this->parser->getVarName() . $lineno,
+            '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);
-        $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);
 
-        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
     {
         return $token->test('endcache');
     }
-    /**
-     * {@inheritDoc}
-     */
+
     public function getTag(): string
     {
         return 'cache';
     }
-}
+}

+ 18 - 3
system/src/Grav/Common/Twig/Twig.php

@@ -57,6 +57,15 @@ class Twig
     /** @var string */
     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 */
     protected $grav;
     /** @var FilesystemLoader */
@@ -493,13 +502,19 @@ class Twig
     /**
      * Simple helper method to get the twig template if it has already been set, else return
      * 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
      * @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';
         $extension = $format ?: $page->templateFormat();
         $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.
         /** @var ExistsLoaderInterface $loader */

+ 7 - 3
system/src/Grav/Common/Uri.php

@@ -1005,7 +1005,7 @@ class Uri
             foreach ($matches as $match) {
                 $param = explode($delimiter, $match[1]);
                 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;
                     $uri = str_replace($match[0], '', $uri);
                 }
@@ -1388,7 +1388,11 @@ class Uri
         if ($this->post && null !== $element) {
             $item = Utils::getDotNotation($this->post, $element);
             if ($filter_type) {
-                $item = filter_var($item, $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);
+                }
             }
             return $item;
         }
@@ -1514,7 +1518,7 @@ class Uri
             foreach ($matches as $match) {
                 $param = explode($delimiter, $match[1]);
                 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;
                     $uri = str_replace($match[0], '', $uri);
                 }

+ 32 - 10
system/src/Grav/Common/Utils.php

@@ -201,7 +201,7 @@ abstract class Utils
         $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos';
 
         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) {
                 break;
             }
@@ -225,8 +225,8 @@ abstract class Utils
         $compare_func = $case_sensitive ? 'mb_strrpos' : 'mb_strripos';
 
         foreach ((array)$needle as $each_needle) {
-            $expectedPosition = mb_strlen($haystack) - mb_strlen($each_needle);
-            $status = $each_needle === '' || $compare_func($haystack, $each_needle, 0) === $expectedPosition;
+            $expectedPosition = mb_strlen((string) $haystack) - mb_strlen($each_needle);
+            $status = $each_needle === '' || $compare_func((string) $haystack, $each_needle, 0) === $expectedPosition;
             if ($status) {
                 break;
             }
@@ -250,7 +250,7 @@ abstract class Utils
         $compare_func = $case_sensitive ? 'mb_strpos' : 'mb_stripos';
 
         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) {
                 break;
             }
@@ -1145,9 +1145,9 @@ abstract class Utils
             $offset_prefix = $offset < 0 ? '-' : '+';
             $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;
@@ -1874,9 +1874,9 @@ abstract class Utils
         }
 
         if ($block) {
-            $string = $parsedown->text($string);
+            $string = $parsedown->text((string) $string);
         } else {
-            $string = $parsedown->line($string);
+            $string = $parsedown->line((string) $string);
         }
 
         return $string;
@@ -1950,10 +1950,10 @@ abstract class Utils
     }
 
     /**
-     * @param string $name
+     * @param string|array|Closure $name
      * @return bool
      */
-    public static function isDangerousFunction(string $name): bool
+    public static function isDangerousFunction($name): bool
     {
         static $commandExecutionFunctions = [
             'exec',
@@ -2048,8 +2048,30 @@ abstract class Utils
             'posix_setpgid',
             'posix_setsid',
             '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)) {
             return true;
         }

+ 1 - 1
system/src/Grav/Console/Cli/LogViewerCommand.php

@@ -82,7 +82,7 @@ class LogViewerCommand extends GravCommand
                 if ($log['trace'] && $verbose) {
                     $output .= " <white>{$log['message']}</white>\n";
                     foreach ((array) $log['trace'] as $index => $tracerow) {
-                        $output .= "<white>{$index}</white>${tracerow}\n";
+                        $output .= "<white>{$index}</white>{$tracerow}\n";
                     }
                 } else {
                     $output .= " {$log['message']}";

+ 1 - 1
system/src/Grav/Console/Gpm/InstallCommand.php

@@ -317,7 +317,7 @@ class InstallCommand extends GpmCommand
                 $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);
 
             if ($answer) {

+ 18 - 0
system/src/Grav/Events/PageEvent.php

@@ -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 - 0
system/src/Grav/Events/TypesEvent.php

@@ -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;
+}

+ 2 - 0
system/src/Grav/Framework/Acl/RecursiveActionIterator.php

@@ -23,6 +23,8 @@ class RecursiveActionIterator implements RecursiveIterator, \Countable
 {
     use Constructor, Iterator, Countable;
 
+    public $items;
+
     /**
      * @see \Iterator::key()
      * @return string

+ 7 - 2
system/src/Grav/Framework/Flex/Pages/Traits/PageLegacyTrait.php

@@ -366,9 +366,14 @@ trait PageLegacyTrait
      */
     public function blueprintName(): string
     {
-        $blueprint_name = filter_input(INPUT_POST, 'blueprint', FILTER_SANITIZE_STRING) ?: $this->template();
+        if (!isset($_POST['blueprint'])) {
+            return $this->template();
+        }
+
+        $post_value = $_POST['blueprint'];
+        $sanitized_value = htmlspecialchars(strip_tags($post_value), ENT_QUOTES, 'UTF-8');
 
-        return $blueprint_name;
+        return $sanitized_value ?: $this->template();
     }
 
     /**

+ 1 - 1
system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php

@@ -42,7 +42,7 @@ trait NestedPropertyTrait
     public function getNestedProperty($property, $default = null, $separator = null)
     {
         $separator = $separator ?: '.';
-        $path = explode($separator, $property);
+        $path = explode($separator, (string) $property);
         $offset = array_shift($path);
 
         if (!$this->hasProperty($offset)) {

+ 8 - 3
system/src/Grav/Framework/Session/Session.php

@@ -11,6 +11,7 @@ namespace Grav\Framework\Session;
 
 use ArrayIterator;
 use Exception;
+use Throwable;
 use Grav\Common\Debugger;
 use Grav\Common\Grav;
 use Grav\Common\User\Interfaces\UserInterface;
@@ -254,13 +255,17 @@ class Session implements SessionInterface
         $this->started = true;
         $this->onSessionStart();
 
-        $user = $this->__get('user');
-        if ($user && (!$user instanceof UserInterface || (method_exists($user, 'isValid') && !$user->isValid()))) {
+        try {
+            $user = $this->__get('user');
+            if ($user && (!$user instanceof UserInterface || (method_exists($user, 'isValid') && !$user->isValid()))) {
+                throw new RuntimeException('Bad user');
+            }
+        } catch (Throwable $e) {
             $this->invalidate();
-
             throw new SessionException('Invalid User object, session destroyed.', 500);
         }
 
+
         // Extend the lifetime of the session.
         if ($sessionExists) {
             $this->setCookie();

+ 1 - 1
system/src/Grav/Framework/Uri/UriFactory.php

@@ -93,7 +93,7 @@ class UriFactory
         }
 
         // Support ngnix routes.
-        if (strpos($query, '_url=') === 0) {
+        if (strpos((string) $query, '_url=') === 0) {
             parse_str($query, $q);
             unset($q['_url']);
             $query = http_build_query($q);

+ 27 - 0
system/src/Twig/DeferredExtension/DeferredDeclareNode.php

@@ -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")
+        ;
+    }
+}

+ 1 - 1
system/src/Twig/DeferredExtension/DeferredExtensionNode.php → system/src/Twig/DeferredExtension/DeferredInitializeNode.php

@@ -16,7 +16,7 @@ namespace Twig\DeferredExtension;
 use Twig\Compiler;
 use Twig\Node\Node;
 
-final class DeferredExtensionNode extends Node
+final class DeferredInitializeNode extends Node
 {
     public function compile(Compiler $compiler) : void
     {

+ 3 - 2
system/src/Twig/DeferredExtension/DeferredNodeVisitor.php

@@ -34,8 +34,9 @@ final class DeferredNodeVisitor implements NodeVisitorInterface
     public function leaveNode(Node $node, Environment $env) : ?Node
     {
         if ($this->hasDeferred && $node instanceof ModuleNode) {
-            $node->setNode('constructor_end', new Node([new DeferredExtensionNode(), $node->getNode('constructor_end')]));
-            $node->setNode('display_end', new Node([new DeferredNode(), $node->getNode('display_end')]));
+            $node->getNode('constructor_end')->setNode('deferred_initialize', new DeferredInitializeNode());
+            $node->getNode('display_end')->setNode('deferred_resolve', new DeferredResolveNode());
+            $node->getNode('class_end')->setNode('deferred_declare', new DeferredDeclareNode());
             $this->hasDeferred = false;
         }
 

+ 3 - 2
system/src/Twig/DeferredExtension/DeferredNodeVisitorCompat.php

@@ -46,8 +46,9 @@ final class DeferredNodeVisitorCompat implements NodeVisitorInterface
     public function leaveNode(\Twig_NodeInterface $node, Environment $env): ?Node
     {
         if ($this->hasDeferred && $node instanceof ModuleNode) {
-            $node->setNode('constructor_end', new Node([new DeferredExtensionNode(), $node->getNode('constructor_end')]));
-            $node->setNode('display_end', new Node([new DeferredNode(), $node->getNode('display_end')]));
+            $node->getNode('constructor_end')->setNode('deferred_initialize', new DeferredInitializeNode());
+            $node->getNode('display_end')->setNode('deferred_resolve', new DeferredResolveNode());
+            $node->getNode('class_end')->setNode('deferred_declare', new DeferredDeclareNode());
             $this->hasDeferred = false;
         }
 

+ 27 - 0
system/src/Twig/DeferredExtension/DeferredResolveNode.php

@@ -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")
+        ;
+    }
+}

+ 2 - 1
user/config/versions.yaml

@@ -1,6 +1,6 @@
 core:
   grav:
-    version: 1.7.38
+    version: 1.7.42.3
     schema: 1.7.0_2020-11-20_1
     history:
       - { 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.31, date: '2022-03-15 08:48:47' }
       - { version: 1.7.38, date: '2023-01-03 15:06:08' }
+      - { version: 1.7.42.3, date: '2023-09-19 10:47:33' }

+ 1 - 1
webserver-configs/nginx.conf

@@ -30,7 +30,7 @@ server {
     ## Begin - PHP
     location ~ \.php$ {
         # 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 127.0.0.1:9000;
 

Some files were not shown because too many files changed in this diff