From 0e4f37eca743e1d2e1bb419aa156def545ec8535 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 22 Dec 2025 21:43:10 -0700 Subject: [PATCH] fix of setEscaper move in Twig 3.9+ Signed-off-by: Andy Miller --- system/src/Grav/Common/Twig/Twig.php | 32 ++++++++++++++ .../src/Grav/Common/Twig/TwigEnvironment.php | 43 +++++++++++-------- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php index 109a50025..a9b88ce6a 100644 --- a/system/src/Grav/Common/Twig/Twig.php +++ b/system/src/Grav/Common/Twig/Twig.php @@ -33,12 +33,14 @@ use Twig\Error\LoaderError; use Twig\Error\RuntimeError; use Twig\Extension\CoreExtension; use Twig\Extension\DebugExtension; +use Twig\Extension\EscaperExtension; use Twig\Extension\StringLoaderExtension; use Twig\Loader\ArrayLoader; use Twig\Loader\ChainLoader; use Twig\Loader\ExistsLoaderInterface; use Twig\Loader\FilesystemLoader; use Twig\Profiler\Profile; +use Twig\Runtime\EscaperRuntime; use Twig\TwigFilter; use Twig\TwigFunction; use function function_exists; @@ -594,4 +596,34 @@ class Twig $this->autoescape = (bool) $state; } + /** + * Register a custom escaper strategy. + * + * This method handles the differences between Twig versions: + * - Twig 1.x: Uses CoreExtension::setEscaper() + * - Twig 2.x/3.x (< 3.9): Uses EscaperExtension::setEscaper() + * - Twig 3.9+: Uses EscaperRuntime::setEscaper() + * + * @param string $strategy The escaper strategy name (e.g., 'yaml', 'json') + * @param callable $callable The escaper callable: function($twig, $string, $charset) + * @return void + */ + public function setEscaper(string $strategy, callable $callable): void + { + // Twig 3.9+ moved setEscaper to EscaperRuntime + if (class_exists(EscaperRuntime::class)) { + $this->twig->getRuntime(EscaperRuntime::class)->setEscaper($strategy, $callable); + return; + } + + // Twig 2.x/3.x (before 3.9) uses EscaperExtension + if (class_exists(EscaperExtension::class)) { + $this->twig->getExtension(EscaperExtension::class)->setEscaper($strategy, $callable); + return; + } + + // Twig 1.x fallback (uses CoreExtension) + $this->twig->getExtension(CoreExtension::class)->setEscaper($strategy, $callable); + } + } diff --git a/system/src/Grav/Common/Twig/TwigEnvironment.php b/system/src/Grav/Common/Twig/TwigEnvironment.php index f5ee39a36..163cdcfc2 100644 --- a/system/src/Grav/Common/Twig/TwigEnvironment.php +++ b/system/src/Grav/Common/Twig/TwigEnvironment.php @@ -9,6 +9,7 @@ namespace Grav\Common\Twig; +use ReflectionClass; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Extension\EscaperExtension; @@ -32,27 +33,35 @@ class TwigEnvironment extends Environment { $extension = parent::getExtension($name); + // Provide setEscaper() compatibility shim for older code calling it on the extension. + // In Twig 3.9+, setEscaper() moved to EscaperRuntime. + // In Twig 3.10+, EscaperExtension is final and cannot be extended. if ($name === EscaperExtension::class && class_exists(EscaperRuntime::class)) { - return new class($extension, $this) extends EscaperExtension { - private $original; - private $env; + $reflection = new ReflectionClass(EscaperExtension::class); + if (!$reflection->isFinal()) { + return new class($extension, $this) extends EscaperExtension { + private $original; + private $env; - public function __construct($original, $env) - { - $this->original = $original; - $this->env = $env; - } + public function __construct($original, $env) + { + $this->original = $original; + $this->env = $env; + } - public function setEscaper($strategy, $callable) - { - $this->env->getRuntime(EscaperRuntime::class)->setEscaper($strategy, $callable); - } + public function setEscaper($strategy, $callable) + { + $this->env->getRuntime(EscaperRuntime::class)->setEscaper($strategy, $callable); + } - public function getDefaultStrategy($filename) - { - return $this->original->getDefaultStrategy($filename); - } - }; + public function getDefaultStrategy($filename) + { + return $this->original->getDefaultStrategy($filename); + } + }; + } + // When EscaperExtension is final (Twig 3.10+), setEscaper() must be called + // directly on the runtime: $twig->getRuntime(EscaperRuntime::class)->setEscaper(...) } return $extension;