diff --git a/src/web/twig/Extension.php b/src/web/twig/Extension.php index 72d399cff9d..a6a456128db 100644 --- a/src/web/twig/Extension.php +++ b/src/web/twig/Extension.php @@ -32,6 +32,8 @@ use craft\helpers\Template as TemplateHelper; use craft\helpers\UrlHelper; use craft\i18n\Locale; +use craft\web\twig\nodes\expressions\binaries\HasEveryBinary; +use craft\web\twig\nodes\expressions\binaries\HasSomeBinary; use craft\web\twig\nodevisitors\EventTagAdder; use craft\web\twig\nodevisitors\EventTagFinder; use craft\web\twig\nodevisitors\GetAttrAdjuster; @@ -69,7 +71,9 @@ use Traversable; use Twig\Environment as TwigEnvironment; use Twig\Error\RuntimeError; +use Twig\ExpressionParser; use Twig\Extension\AbstractExtension; +use Twig\Extension\CoreExtension; use Twig\Extension\GlobalsInterface; use Twig\TwigFilter; use Twig\TwigFunction; @@ -80,8 +84,6 @@ use yii\db\Expression; use yii\db\QueryInterface; use yii\helpers\Markdown; -use function twig_date_converter; -use function twig_date_format_filter; /** * Class Extension @@ -91,6 +93,40 @@ */ class Extension extends AbstractExtension implements GlobalsInterface { + /** + * @since 4.12.2 + */ + public static function arraySome(TwigEnvironment $env, $array, $arrow) + { + self::checkArrowFunction($arrow, 'has some', 'operator'); + return CoreExtension::arraySome($env, $array, $arrow); + } + + /** + * @since 4.12.2 + */ + public static function arrayEvery(TwigEnvironment $env, $array, $arrow) + { + self::checkArrowFunction($arrow, 'has every', 'operator'); + return CoreExtension::arrayEvery($env, $array, $arrow); + } + + private static function checkArrowFunction(mixed $arrow, string $thing, string $type): void + { + if ( + is_string($arrow) && + in_array(ltrim(strtolower($arrow), '\\'), [ + 'system', + 'passthru', + 'exec', + 'file_get_contents', + 'file_put_contents', + ]) + ) { + throw new RuntimeError(sprintf('The "%s" %s does not support passing "%s".', $thing, $type, $arrow)); + } + } + /** * @var View|null */ @@ -212,6 +248,7 @@ public function getFilters(): array new TwigFilter('indexOf', [$this, 'indexOfFilter']), new TwigFilter('integer', 'intval'), new TwigFilter('intersect', 'array_intersect'), + new TwigFilter('find', [$this, 'findFilter'], ['needs_environment' => true]), new TwigFilter('float', 'floatval'), new TwigFilter('json_encode', [$this, 'jsonEncodeFilter']), new TwigFilter('json_decode', [Json::class, 'decode']), @@ -313,6 +350,20 @@ public function getTests(): array ]; } + /** + * @inheritdoc + */ + public function getOperators(): array + { + return [ + [], + [ + 'has some' => ['precedence' => 20, 'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'has every' => ['precedence' => 20, 'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + ], + ]; + } + /** * @param Address|null $address * @param array $options @@ -498,8 +549,8 @@ public function snakeFilter(mixed $string): string */ public function sortFilter(TwigEnvironment $env, iterable $array, string|callable|null $arrow = null): array { - $this->_checkFilterSupport($arrow); - return twig_sort_filter($env, $array, $arrow); + self::checkArrowFunction($arrow, 'sort', 'filter'); + return CoreExtension::sort($env, $array, $arrow); } /** @@ -515,8 +566,8 @@ public function sortFilter(TwigEnvironment $env, iterable $array, string|callabl */ public function reduceFilter(TwigEnvironment $env, mixed $array, mixed $arrow, mixed $initial = null): mixed { - $this->_checkFilterSupport($arrow); - return twig_array_reduce($env, $array, $arrow, $initial); + self::checkArrowFunction($arrow, 'reduce', 'filter'); + return CoreExtension::reduce($env, $array, $arrow, $initial); } /** @@ -531,8 +582,8 @@ public function reduceFilter(TwigEnvironment $env, mixed $array, mixed $arrow, m */ public function mapFilter(TwigEnvironment $env, mixed $array, mixed $arrow = null): array { - $this->_checkFilterSupport($arrow); - return twig_array_map($env, $array, $arrow); + self::checkArrowFunction($arrow, 'map', 'filter'); + return CoreExtension::map($env, $array, $arrow); } @@ -651,6 +702,15 @@ public function timestampFilter(mixed $value, ?string $format = null, bool $with } } + /** + * @since 4.12.2 + */ + public function findFilter(TwigEnvironment $env, $array, $arrow): mixed + { + self::checkArrowFunction($arrow, 'find', 'filter'); + return CoreExtension::find($env, $array, $arrow); + } + /** * This method will JSON encode a variable. We're overriding Twig's default implementation to set some stricter * encoding options on text/html/xml requests. @@ -954,7 +1014,7 @@ private function isRegex(string $str): bool public function dateFilter(TwigEnvironment $env, mixed $date, ?string $format = null, mixed $timezone = null, ?string $locale = null): string { if ($date instanceof DateInterval) { - return twig_date_format_filter($env, $date, $format, $timezone); + return $env->getExtension(CoreExtension::class)->formatDate($date, $format, $timezone); } // Is this a custom PHP date format? @@ -966,7 +1026,7 @@ public function dateFilter(TwigEnvironment $env, mixed $date, ?string $format = } } - $date = twig_date_converter($env, $date, $timezone); + $date = $env->getExtension(CoreExtension::class)->convertDate($date, $timezone); $formatter = $locale ? (new Locale($locale))->getFormatter() : Craft::$app->getFormatter(); $fmtTimeZone = $formatter->timeZone; $formatter->timeZone = $timezone !== null ? $date->getTimezone()->getName() : $formatter->timeZone; @@ -1006,7 +1066,7 @@ public function appendFilter(string $tag, string $html, ?string $ifExists = null */ public function atomFilter(TwigEnvironment $env, mixed $date, mixed $timezone = null): string { - return twig_date_format_filter($env, $date, DateTime::ATOM, $timezone); + return $env->getExtension(CoreExtension::class)->formatDate($date, DateTime::ATOM, $timezone); } /** @@ -1037,7 +1097,7 @@ public function attrFilter(string $tag, array $attributes): string */ public function rssFilter(TwigEnvironment $env, mixed $date, mixed $timezone = null): string { - return twig_date_format_filter($env, $date, DateTime::RSS, $timezone); + return $env->getExtension(CoreExtension::class)->formatDate($date, DateTime::RSS, $timezone); } /** @@ -1061,7 +1121,7 @@ public function timeFilter(TwigEnvironment $env, mixed $date, ?string $format = } } - $date = twig_date_converter($env, $date, $timezone); + $date = $env->getExtension(CoreExtension::class)->convertDate($date, $timezone); $formatter = $locale ? (new Locale($locale))->getFormatter() : Craft::$app->getFormatter(); $fmtTimeZone = $formatter->timeZone; $formatter->timeZone = $timezone !== null ? $date->getTimezone()->getName() : $formatter->timeZone; @@ -1091,7 +1151,7 @@ public function datetimeFilter(TwigEnvironment $env, mixed $date, ?string $forma } } - $date = twig_date_converter($env, $date, $timezone); + $date = $env->getExtension(CoreExtension::class)->convertDate($date, $timezone); $formatter = $locale ? (new Locale($locale))->getFormatter() : Craft::$app->getFormatter(); $fmtTimeZone = $formatter->timeZone; $formatter->timeZone = $timezone !== null ? $date->getTimezone()->getName() : $formatter->timeZone; @@ -1122,7 +1182,7 @@ public function encencFilter(mixed $str): string */ public function filterFilter(TwigEnvironment $env, iterable $arr, ?callable $arrow = null): array { - $this->_checkFilterSupport($arrow); + self::checkArrowFunction($arrow, 'filter', 'filter'); /** @var array|Traversable $arr */ if ($arrow === null) { @@ -1132,7 +1192,7 @@ public function filterFilter(TwigEnvironment $env, iterable $arr, ?callable $arr return array_filter($arr); } - $filtered = twig_array_filter($env, $arr, $arrow); + $filtered = CoreExtension::filter($env, $arr, $arrow); if (is_array($filtered)) { return $filtered; @@ -1182,7 +1242,7 @@ public function groupFilter(iterable $arr, callable|string $arrow): array */ public function httpdateFilter(TwigEnvironment $env, mixed $date, mixed $timezone = null): string { - return twig_date_format_filter($env, $date, DateTime::RFC7231, $timezone); + return $env->getExtension(CoreExtension::class)->formatDate($date, DateTime::RFC7231, $timezone); } @@ -1232,7 +1292,7 @@ public function lengthFilter(TwigEnvironment $env, mixed $value): int return $value->count(); } - return twig_length_filter($env, $value); + return CoreExtension::length($env->getCharset(), $value); } /** @@ -1304,7 +1364,7 @@ public function mergeFilter(iterable $arr1, iterable $arr2, bool $recursive = fa return ArrayHelper::merge($arr1, $arr2); } - return twig_array_merge($arr1, $arr2); + return CoreExtension::merge($arr1, $arr2); } /** @@ -1467,7 +1527,7 @@ public function dateFunction(TwigEnvironment $env, mixed $date = null, mixed $ti } } - return twig_date_converter($env, $date, $timezone); + return $env->getExtension(CoreExtension::class)->convertDate($date, $timezone); } /** @@ -1693,21 +1753,4 @@ public function getGlobals(): array 'yesterday' => DateTimeHelper::yesterday(), ]; } - - /** - * @param mixed $arrow - * @throws RuntimeError - */ - private function _checkFilterSupport(mixed $arrow): void - { - if (is_string($arrow) && in_array(ltrim(strtolower($arrow), '\\'), [ - 'system', - 'passthru', - 'exec', - 'file_get_contents', - 'file_put_contents', - ])) { - throw new RuntimeError('Not supported in this filter.'); - } - } } diff --git a/src/web/twig/nodes/expressions/binaries/HasEveryBinary.php b/src/web/twig/nodes/expressions/binaries/HasEveryBinary.php new file mode 100644 index 00000000000..ef82803c5c0 --- /dev/null +++ b/src/web/twig/nodes/expressions/binaries/HasEveryBinary.php @@ -0,0 +1,37 @@ + + * @since 4.12.2 + */ +class HasEveryBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw(sprintf('%s::arrayEvery($this->env, ', Extension::class)) + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/src/web/twig/nodes/expressions/binaries/HasSomeBinary.php b/src/web/twig/nodes/expressions/binaries/HasSomeBinary.php new file mode 100644 index 00000000000..ae69b2eb6c2 --- /dev/null +++ b/src/web/twig/nodes/expressions/binaries/HasSomeBinary.php @@ -0,0 +1,37 @@ + + * @since 4.12.2 + */ +class HasSomeBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw(sprintf('%s::arraySome($this->env, ', Extension::class)) + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +}