diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aa6d4796..161afaaa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,10 @@ 1. [](#new) * Register plugin autoloaders into plugin objects -1. [](#bugfix) +2. [](#improved) + * Improve Twig 2 compatibility + * Update to customized version of Twig DeferredExtension (Twig 1/2 compatible) +3. [](#bugfix) * Fixed conflicting `$_original` variable in `Flex Pages` # v1.7.21 diff --git a/composer.json b/composer.json index c3fb23ea9..2f507e5c5 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,6 @@ "miljar/php-exif": "^0.6", "composer/ca-bundle": "^1.2", "dragonmantank/cron-expression": "^1.2", - "phive/twig-extensions-deferred": "^1.0", "willdurand/negotiation": "^3.0", "itsgoingd/clockwork": "^5.0", "enshrined/svg-sanitize": "~0.13", @@ -93,7 +92,8 @@ }, "autoload": { "psr-4": { - "Grav\\": "system/src/Grav" + "Grav\\": "system/src/Grav", + "Twig\\": "system/src/Twig" }, "files": [ "system/defines.php" diff --git a/composer.lock b/composer.lock index 8d2907f41..91732e88b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "36375d8a5daf3aef0341f9a2b023f4e9", + "content-hash": "a777a09c8efd3109a03df1d9c9c20b87", "packages": [ { "name": "antoligy/dom-string-iterators", @@ -380,20 +380,20 @@ }, { "name": "donatj/phpuseragentparser", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/donatj/PhpUserAgent.git", - "reference": "246c1cf0a44f07168c702203bf30d5f48f17bab0" + "reference": "cc9d872cddfc180c52d084d0dff1e4aad653d37f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/246c1cf0a44f07168c702203bf30d5f48f17bab0", - "reference": "246c1cf0a44f07168c702203bf30d5f48f17bab0", + "url": "https://api.github.com/repos/donatj/PhpUserAgent/zipball/cc9d872cddfc180c52d084d0dff1e4aad653d37f", + "reference": "cc9d872cddfc180c52d084d0dff1e4aad653d37f", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.4.0" }, "require-dev": { "camspiers/json-pretty": "~1.0", @@ -432,7 +432,7 @@ ], "support": { "issues": "https://github.com/donatj/PhpUserAgent/issues", - "source": "https://github.com/donatj/PhpUserAgent/tree/v1.4.0" + "source": "https://github.com/donatj/PhpUserAgent/tree/v1.5.0" }, "funding": [ { @@ -444,7 +444,7 @@ "type": "github" } ], - "time": "2021-03-16T16:25:14+00:00" + "time": "2021-09-16T17:05:03+00:00" }, { "name": "dragonmantank/cron-expression", @@ -1516,59 +1516,6 @@ ], "time": "2021-05-12T11:11:27+00:00" }, - { - "name": "phive/twig-extensions-deferred", - "version": "v1.0.2", - "source": { - "type": "git", - "url": "https://github.com/rybakit/twig-extensions-deferred-legacy.git", - "reference": "5a2426d622afa74034e754ca5ea1d1ff7887627f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rybakit/twig-extensions-deferred-legacy/zipball/5a2426d622afa74034e754ca5ea1d1ff7887627f", - "reference": "5a2426d622afa74034e754ca5ea1d1ff7887627f", - "shasum": "" - }, - "require": { - "twig/twig": "~1.18" - }, - "type": "library", - "autoload": { - "psr-4": { - "Phive\\Twig\\Extensions\\Deferred\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eugene Leonovich", - "email": "gen.work@gmail.com" - } - ], - "description": "An extension for Twig that allows to defer block rendering", - "homepage": "https://github.com/rybakit/twig-extensions-deferred", - "keywords": [ - "defer", - "extension", - "lazy", - "twig" - ], - "support": { - "issues": "https://github.com/rybakit/twig-extensions-deferred-legacy/issues", - "source": "https://github.com/rybakit/twig-extensions-deferred-legacy/tree/v1.0.2" - }, - "funding": [ - { - "url": "https://github.com/rybakit", - "type": "github" - } - ], - "time": "2017-03-17T21:39:21+00:00" - }, { "name": "php-http/message-factory", "version": "v1.0.2", @@ -3231,16 +3178,16 @@ }, { "name": "twig/twig", - "version": "v1.44.4", + "version": "v1.44.5", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "4d400421528e9fa40caaffcf7824c172526dd99d" + "reference": "dd4353357c5a116322e92a00d16043a31881a81e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/4d400421528e9fa40caaffcf7824c172526dd99d", - "reference": "4d400421528e9fa40caaffcf7824c172526dd99d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/dd4353357c5a116322e92a00d16043a31881a81e", + "reference": "dd4353357c5a116322e92a00d16043a31881a81e", "shasum": "" }, "require": { @@ -3293,7 +3240,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v1.44.4" + "source": "https://github.com/twigphp/Twig/tree/v1.44.5" }, "funding": [ { @@ -3305,7 +3252,7 @@ "type": "tidelift" } ], - "time": "2021-05-16T12:11:20+00:00" + "time": "2021-09-17T08:35:19+00:00" }, { "name": "willdurand/negotiation", @@ -4502,33 +4449,33 @@ }, { "name": "phpspec/prophecy", - "version": "1.13.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", - "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.1", + "php": "^7.2 || ~8.0, <8.2", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0", "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^6.0", + "phpspec/phpspec": "^6.0 || ^7.0", "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -4563,22 +4510,22 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + "source": "https://github.com/phpspec/prophecy/tree/1.14.0" }, - "time": "2021-03-17T13:42:18+00:00" + "time": "2021-09-10T09:02:12+00:00" }, { "name": "phpstan/phpstan", - "version": "0.12.98", + "version": "0.12.99", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "3bb7cc246c057405dd5e290c3ecc62ab51d57e00" + "reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3bb7cc246c057405dd5e290c3ecc62ab51d57e00", - "reference": "3bb7cc246c057405dd5e290c3ecc62ab51d57e00", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b4d40f1d759942f523be267a1bab6884f46ca3f7", + "reference": "b4d40f1d759942f523be267a1bab6884f46ca3f7", "shasum": "" }, "require": { @@ -4609,7 +4556,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/0.12.98" + "source": "https://github.com/phpstan/phpstan/tree/0.12.99" }, "funding": [ { @@ -4629,7 +4576,7 @@ "type": "tidelift" } ], - "time": "2021-09-02T12:33:01+00:00" + "time": "2021-09-12T20:09:55+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -4684,23 +4631,23 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.6", + "version": "9.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f6293e1b30a2354e8428e004689671b83871edde" + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", - "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218", + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.10.2", + "nikic/php-parser": "^4.12.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -4749,7 +4696,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7" }, "funding": [ { @@ -4757,7 +4704,7 @@ "type": "github" } ], - "time": "2021-03-28T07:26:59+00:00" + "time": "2021-09-17T05:39:03+00:00" }, { "name": "phpunit/php-file-iterator", @@ -6008,7 +5955,6 @@ "type": "github" } ], - "abandoned": true, "time": "2020-09-28T06:45:17+00:00" }, { diff --git a/system/src/Grav/Common/Twig/Extension/GravExtension.php b/system/src/Grav/Common/Twig/Extension/GravExtension.php index 5b4a16002..d21c8b188 100644 --- a/system/src/Grav/Common/Twig/Extension/GravExtension.php +++ b/system/src/Grav/Common/Twig/Extension/GravExtension.php @@ -90,7 +90,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface * * @return array */ - public function getGlobals() + public function getGlobals(): array { return [ 'grav' => $this->grav, @@ -102,7 +102,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface * * @return array */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('*ize', [$this, 'inflectorFilter']), @@ -172,7 +172,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface * * @return array */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('array', [$this, 'arrayFilter']), @@ -243,7 +243,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface /** * @return array */ - public function getTokenParsers() + public function getTokenParsers(): array { return [ new TwigTokenParserRender(), diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php index ef74ce75a..19bad01b1 100644 --- a/system/src/Grav/Common/Twig/Twig.php +++ b/system/src/Grav/Common/Twig/Twig.php @@ -22,9 +22,9 @@ use Grav\Common\Twig\Extension\GravExtension; use Grav\Common\Utils; use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; use RocketTheme\Toolbox\Event\Event; -use Phive\Twig\Extensions\Deferred\DeferredExtension; use RuntimeException; use Twig\Cache\FilesystemCache; +use Twig\DeferredExtension\DeferredExtension; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; @@ -33,7 +33,6 @@ use Twig\Extension\DebugExtension; 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\TwigFilter; @@ -515,25 +514,21 @@ class Twig $twig_extension = $extension ? '.'. $extension .TWIG_EXT : TEMPLATE_EXT; $template_file = $this->template($page->template() . $twig_extension); - $page_template = null; - $loader = $this->twig->getLoader(); - if ($loader instanceof ExistsLoaderInterface) { - if ($loader->exists($template_file)) { - // template.xxx.twig - $page_template = $template_file; - } elseif ($twig_extension !== TEMPLATE_EXT && $loader->exists($template . TEMPLATE_EXT)) { - // template.html.twig - $page_template = $template . TEMPLATE_EXT; - $format = 'html'; - } elseif ($loader->exists($default . $twig_extension)) { - // default.xxx.twig - $page_template = $default . $twig_extension; - } else { - // default.html.twig - $page_template = $default . TEMPLATE_EXT; - $format = 'html'; - } + if ($loader->exists($template_file)) { + // template.xxx.twig + $page_template = $template_file; + } elseif ($twig_extension !== TEMPLATE_EXT && $loader->exists($template . TEMPLATE_EXT)) { + // template.html.twig + $page_template = $template . TEMPLATE_EXT; + $format = 'html'; + } elseif ($loader->exists($default . $twig_extension)) { + // default.xxx.twig + $page_template = $default . $twig_extension; + } else { + // default.html.twig + $page_template = $default . TEMPLATE_EXT; + $format = 'html'; } return $page_template; diff --git a/system/src/Phive/Twig/Extensions/Deferred/DeferredExtension.php b/system/src/Phive/Twig/Extensions/Deferred/DeferredExtension.php deleted file mode 100644 index 3a41e4a0e..000000000 --- a/system/src/Phive/Twig/Extensions/Deferred/DeferredExtension.php +++ /dev/null @@ -1,70 +0,0 @@ -getTemplateName(); - $this->blocks[$templateName][] = [ob_get_level(), $blockName]; - } - - public function resolve(\Twig_Template $template, array $context, array $blocks) - { - $templateName = $template->getTemplateName(); - if (empty($this->blocks[$templateName])) { - return; - } - - while ($block = array_pop($this->blocks[$templateName])) { - [$level, $blockName] = $block; - if (ob_get_level() !== $level) { - continue; - } - - $buffer = ob_get_clean(); - - $blocks[$blockName] = array($template, 'block_'.$blockName.'_deferred'); - $template->displayBlock($blockName, $context, $blocks); - - echo $buffer; - } - - if ($parent = $template->getParent($context)) { - $this->resolve($parent, $context, $blocks); - } - } -} diff --git a/system/src/Twig/DeferredExtension/DeferredBlockNode.php b/system/src/Twig/DeferredExtension/DeferredBlockNode.php new file mode 100755 index 000000000..6ae974f88 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredBlockNode.php @@ -0,0 +1,43 @@ + + * + * 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\BlockNode; + +final class DeferredBlockNode extends BlockNode +{ + public function compile(Compiler $compiler) : void + { + $name = $this->getAttribute('name'); + + $compiler + ->write("public function block_$name(\$context, array \$blocks = [])\n", "{\n") + ->indent() + ->write("\$this->deferred->defer(\$this, '$name');\n") + ->outdent() + ->write("}\n\n") + ; + + $compiler + ->addDebugInfo($this) + ->write("public function block_{$name}_deferred(\$context, array \$blocks = [])\n", "{\n") + ->indent() + ->subcompile($this->getNode('body')) + ->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredExtension.php b/system/src/Twig/DeferredExtension/DeferredExtension.php new file mode 100644 index 000000000..aa7044995 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredExtension.php @@ -0,0 +1,66 @@ + + * + * 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\Extension\AbstractExtension; +use Twig\Template; + +final class DeferredExtension extends AbstractExtension +{ + private $blocks = []; + + public function getTokenParsers() : array + { + return [new DeferredTokenParser()]; + } + + public function getNodeVisitors() : array + { + return [new DeferredNodeVisitor()]; + } + + public function defer(Template $template, string $blockName) : void + { + $templateName = $template->getTemplateName(); + $this->blocks[$templateName][] = $blockName; + $index = \count($this->blocks[$templateName]) - 1; + + \ob_start(function (string $buffer) use ($index, $templateName) { + unset($this->blocks[$templateName][$index]); + + return $buffer; + }); + } + + public function resolve(Template $template, array $context, array $blocks) : void + { + $templateName = $template->getTemplateName(); + if (empty($this->blocks[$templateName])) { + return; + } + + while ($blockName = \array_pop($this->blocks[$templateName])) { + $buffer = \ob_get_clean(); + + $blocks[$blockName] = [$template, 'block_'.$blockName.'_deferred']; + $template->displayBlock($blockName, $context, $blocks); + + echo $buffer; + } + + if ($parent = $template->getParent($context)) { + $this->resolve($parent, $context, $blocks); + } + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredExtensionNode.php b/system/src/Twig/DeferredExtension/DeferredExtensionNode.php new file mode 100644 index 000000000..1b851b4c2 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredExtensionNode.php @@ -0,0 +1,27 @@ + + * + * 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 DeferredExtensionNode extends Node +{ + public function compile(Compiler $compiler) : void + { + $compiler + ->write("\$this->deferred = \$this->env->getExtension('".DeferredExtension::class."');\n") + ; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredNode.php b/system/src/Twig/DeferredExtension/DeferredNode.php new file mode 100755 index 000000000..2ac73bd1f --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredNode.php @@ -0,0 +1,27 @@ + + * + * 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 DeferredNode extends Node +{ + public function compile(Compiler $compiler) : void + { + $compiler + ->write("\$this->deferred->resolve(\$this, \$context, \$blocks);\n") + ; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredNodeVisitor.php b/system/src/Twig/DeferredExtension/DeferredNodeVisitor.php new file mode 100644 index 000000000..0aa364ce7 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredNodeVisitor.php @@ -0,0 +1,49 @@ + + * + * 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\Environment; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\NodeVisitor\NodeVisitorInterface; + +final class DeferredNodeVisitor implements NodeVisitorInterface +{ + private $hasDeferred = false; + + public function enterNode(\Twig_NodeInterface $node, Environment $env) : Node + { + if (!$this->hasDeferred && $node instanceof DeferredBlockNode) { + $this->hasDeferred = true; + } + + return $node; + } + + 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')])); + $this->hasDeferred = false; + } + + return $node; + } + + public function getPriority() : int + { + return 0; + } +} diff --git a/system/src/Twig/DeferredExtension/DeferredTokenParser.php b/system/src/Twig/DeferredExtension/DeferredTokenParser.php new file mode 100644 index 000000000..1870ae0e4 --- /dev/null +++ b/system/src/Twig/DeferredExtension/DeferredTokenParser.php @@ -0,0 +1,77 @@ + + * + * 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\Node\BlockNode; +use Twig\Node\Node; +use Twig\Parser; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; +use Twig\TokenParser\BlockTokenParser; + +final class DeferredTokenParser extends AbstractTokenParser +{ + private $blockTokenParser; + + public function setParser(Parser $parser) : void + { + parent::setParser($parser); + + $this->blockTokenParser = new BlockTokenParser(); + $this->blockTokenParser->setParser($parser); + } + + public function parse(Token $token) : Node + { + $stream = $this->parser->getStream(); + $nameToken = $stream->next(); + $deferredToken = $stream->nextIf(Token::NAME_TYPE, 'deferred'); + $stream->injectTokens([$nameToken]); + + $node = $this->blockTokenParser->parse($token); + + if ($deferredToken) { + $this->replaceBlockNode($nameToken->getValue()); + } + + return $node; + } + + public function getTag() : string + { + return 'block'; + } + + private function replaceBlockNode(string $name) : void + { + $block = $this->parser->getBlock($name)->getNode('0'); + $this->parser->setBlock($name, $this->createDeferredBlockNode($block)); + } + + private function createDeferredBlockNode(BlockNode $block) : DeferredBlockNode + { + $name = $block->getAttribute('name'); + $deferredBlock = new DeferredBlockNode($name, new Node([]), $block->getTemplateLine()); + + foreach ($block as $nodeName => $node) { + $deferredBlock->setNode($nodeName, $node); + } + + if ($sourceContext = $block->getSourceContext()) { + $deferredBlock->setSourceContext($sourceContext); + } + + return $deferredBlock; + } +}