Skip to content

Commit

Permalink
Merge branch '4.x' of https://github.com/craftcms/cms into 5.x
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
#	src/web/twig/Extension.php
  • Loading branch information
brandonkelly committed Sep 12, 2024
2 parents 243c899 + 41686b5 commit 00111c2
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Updated Twig to 3.14. ([#15704](https://github.com/craftcms/cms/issues/15704))
- Fixed a bug where soft-deleted structures weren’t getting hard-deleted via garbage collection. ([#15705](https://github.com/craftcms/cms/pull/15705))
- Fixed a bug where address’ Label fields were being marked as translatable. ([#15702](https://github.com/craftcms/cms/pull/15702))
- Fixed an error that could occur when saving an entry with a Matrix field, if the nested entries didn’t have slugs.
- Fixed a bug where relation fields weren’t merging uploaded asset IDs with the existing field values. ([#15707](https://github.com/craftcms/cms/issues/15707))
Expand Down
5 changes: 3 additions & 2 deletions src/services/Gc.php
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ private function _hardDeleteStructures(): void
->leftJoin(['r' => $revisionsTable], '[[r.canonicalId]] = coalesce([[e.canonicalId]],[[e.id]])')
->where([
'and',
['not', ['se.elementId' => null]],
$this->_hardDeleteCondition('s'),
[
'r.canonicalId' => null,
Expand All @@ -719,7 +720,7 @@ private function _hardDeleteStructures(): void
if ($this->db->getIsMysql()) {
$sql = <<<SQL
DELETE [[s]].* FROM $structuresTable [[s]]
WHERE [[s.id]] NOT IN ($ids)
WHERE [[s.id]] IN ($ids)
AND $conditionSql
SQL;
} else {
Expand All @@ -728,7 +729,7 @@ private function _hardDeleteStructures(): void
USING $structuresTable [[s]]
WHERE
$structuresTable.[[id]] = [[s.id]] AND
[[s.id]] NOT IN ($ids) AND
[[s.id]] IN ($ids) AND
$conditionSql
SQL;
}
Expand Down
117 changes: 80 additions & 37 deletions src/web/twig/Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
use craft\helpers\UrlHelper;
use craft\i18n\Locale;
use craft\models\EntryType;
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;
Expand Down Expand Up @@ -72,7 +74,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;
Expand All @@ -83,8 +87,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
Expand All @@ -94,6 +96,40 @@
*/
class Extension extends AbstractExtension implements GlobalsInterface
{
/**
* @since 5.4.3
*/
public static function arraySome(TwigEnvironment $env, $array, $arrow)
{
self::checkArrowFunction($arrow, 'has some', 'operator');
return CoreExtension::arraySome($env, $array, $arrow);
}

/**
* @since 5.4.3
*/
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
*/
Expand Down Expand Up @@ -217,6 +253,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']),
Expand Down Expand Up @@ -318,6 +355,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
Expand Down Expand Up @@ -503,8 +554,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);
}

/**
Expand All @@ -520,8 +571,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);
}

/**
Expand All @@ -536,8 +587,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);
}


Expand Down Expand Up @@ -656,6 +707,15 @@ public function timestampFilter(mixed $value, ?string $format = null, bool $with
}
}

/**
* @since 5.4.3
*/
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.
Expand Down Expand Up @@ -959,7 +1019,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?
Expand All @@ -971,7 +1031,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 ? Craft::$app->getI18n()->getLocaleById($locale)->getFormatter() : Craft::$app->getFormatter();
$fmtTimeZone = $formatter->timeZone;
$formatter->timeZone = $timezone !== null ? $date->getTimezone()->getName() : $formatter->timeZone;
Expand Down Expand Up @@ -1011,7 +1071,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);
}

/**
Expand Down Expand Up @@ -1042,7 +1102,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);
}

/**
Expand All @@ -1066,7 +1126,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 ? Craft::$app->getI18n()->getLocaleById($locale)->getFormatter() : Craft::$app->getFormatter();
$fmtTimeZone = $formatter->timeZone;
$formatter->timeZone = $timezone !== null ? $date->getTimezone()->getName() : $formatter->timeZone;
Expand Down Expand Up @@ -1096,7 +1156,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 ? Craft::$app->getI18n()->getLocaleById($locale)->getFormatter() : Craft::$app->getFormatter();
$fmtTimeZone = $formatter->timeZone;
$formatter->timeZone = $timezone !== null ? $date->getTimezone()->getName() : $formatter->timeZone;
Expand Down Expand Up @@ -1127,7 +1187,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) {
Expand All @@ -1137,7 +1197,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;
Expand Down Expand Up @@ -1187,7 +1247,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);
}


Expand Down Expand Up @@ -1237,7 +1297,7 @@ public function lengthFilter(TwigEnvironment $env, mixed $value): int
return $value->count();
}

return twig_length_filter($env, $value);
return CoreExtension::length($env->getCharset(), $value);
}

/**
Expand Down Expand Up @@ -1309,7 +1369,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);
}

/**
Expand Down Expand Up @@ -1474,7 +1534,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);
}

/**
Expand Down Expand Up @@ -1726,21 +1786,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.');
}
}
}
37 changes: 37 additions & 0 deletions src/web/twig/nodes/expressions/binaries/HasEveryBinary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\web\twig\nodes\expressions\binaries;

use craft\web\twig\Extension;
use Twig\Compiler;
use Twig\Node\Expression\Binary\AbstractBinary;

/**
* Class HasEveryBinary
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 5.4.3
*/
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('');
}
}
37 changes: 37 additions & 0 deletions src/web/twig/nodes/expressions/binaries/HasSomeBinary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace craft\web\twig\nodes\expressions\binaries;

use craft\web\twig\Extension;
use Twig\Compiler;
use Twig\Node\Expression\Binary\AbstractBinary;

/**
* Class HasSomeBinary
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 5.4.3
*/
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('');
}
}

0 comments on commit 00111c2

Please sign in to comment.