Skip to content

Commit

Permalink
Merge 3.0.20
Browse files Browse the repository at this point in the history
  • Loading branch information
machina86 committed Oct 22, 2024
2 parents 4be4fe0 + 4db7a55 commit a93aca2
Show file tree
Hide file tree
Showing 18 changed files with 522 additions and 207 deletions.
5 changes: 4 additions & 1 deletion src/Bridges/Tracy/templates/LattePanel.panel.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ $colors = [
<h2>Parameters</h2>

<div class="tracy-inner">
<?= Dumper::toHtml(reset($list)->template->getParameters(), [Dumper::LIVE => true]); ?>
<table class="tracy-sortable tracy-dump-seamless">
<?php foreach (reset($list)->template->getParameters() as $k => $v): ?>
<tr><th><?= Helpers::escapeHtml($k) ?></th><td><?= Dumper::toHtml($v, [Dumper::LIVE => true]) ?></td></tr>
<?php endforeach ?>
</div>
<?php endif ?>
</div>
258 changes: 129 additions & 129 deletions src/Latte/Compiler/TagParserData.php

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions src/Latte/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
*/
class Engine
{
public const Version = '3.0.17';
public const VersionId = 30017;
public const Version = '3.0.20';
public const VersionId = 30020;

/** @deprecated use Engine::Version */
public const
Expand Down Expand Up @@ -50,8 +50,8 @@ class Engine
private bool $sandboxed = false;
private ?string $phpBinary = null;
private ?string $cacheKey;
private ?string $defaultSyntax = null;
private ?string $locale = null;
private ?string $defaultSyntax = null;


public function __construct()
Expand Down Expand Up @@ -568,6 +568,7 @@ public function isStrictParsing(): bool
{
return $this->strictParsing;
}


/**
* Sets default tag syntax
Expand All @@ -577,15 +578,15 @@ public function setDefaultSyntax(?string $defaultSyntax): static
$this->defaultSyntax = $defaultSyntax;
return $this;
}


/**
* Sets locale for date and number formatting. See PHP intl extension.
* Sets the locale. It uses the same identifiers as the PHP intl extension.
*/
public function setLocale(?string $locale): static
{
if ($locale && !extension_loaded('intl')) {
throw new RuntimeException("Locate requires the 'intl' extension to be installed.");
throw new RuntimeException("Setting a locale requires the 'intl' extension to be installed.");
}
$this->locale = $locale;
return $this;
Expand Down
5 changes: 2 additions & 3 deletions src/Latte/Essential/CachingIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,10 @@ public function getParent(): ?self
* Returns property value.
* @throws \LogicException if the property is not defined.
*/
public function &__get(string $name): mixed
public function __get(string $name): mixed
{
if (method_exists($this, $m = 'get' . $name) || method_exists($this, $m = 'is' . $name)) {
$ret = $this->$m();
return $ret;
return $this->$m();
}

throw new \LogicException('Attempt to read undeclared property ' . static::class . "::\$$name.");
Expand Down
2 changes: 2 additions & 0 deletions src/Latte/Essential/CoreExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public function getFilters(): array
'escapeUrl' => 'rawurlencode',
'escapeXml' => [Latte\Runtime\Filters::class, 'escapeXml'],
'explode' => [$this->filters, 'explode'],
'filter' => [$this->filters, 'filter'],
'first' => [$this->filters, 'first'],
'firstUpper' => extension_loaded('mbstring')
? [$this->filters, 'firstUpper']
Expand All @@ -145,6 +146,7 @@ public function getFilters(): array
'join' => [$this->filters, 'implode'],
'last' => [$this->filters, 'last'],
'length' => [$this->filters, 'length'],
'localDate' => [$this->filters, 'localDate'],
'lower' => extension_loaded('mbstring')
? [$this->filters, 'lower']
: fn() => throw new RuntimeException('Filter |lower requires mbstring extension.'),
Expand Down
83 changes: 83 additions & 0 deletions src/Latte/Essential/Filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,71 @@ public function date(string|int|\DateTimeInterface|\DateInterval|null $time, ?st
}


/**
* Date/time formatting according to locale.
*/
public function localDate(
string|int|\DateTimeInterface|null $value,
?string $format = null,
?string $date = null,
?string $time = null,
): ?string
{
if ($this->locale === null) {
throw new Latte\RuntimeException('Filter |localDate requires the locale to be set using Engine::setLocale()');
} elseif ($value == null) { // intentionally ==
return null;
} elseif (is_numeric($value)) {
$value = (new \DateTime)->setTimestamp((int) $value);
} elseif (!$value instanceof \DateTimeInterface) {
$value = new \DateTime($value);
$errors = \DateTime::getLastErrors();
if (!empty($errors['warnings'])) {
throw new \InvalidArgumentException(reset($errors['warnings']));
}
}

if ($format === null) {
$xlt = ['' => \IntlDateFormatter::NONE, 'full' => \IntlDateFormatter::FULL, 'long' => \IntlDateFormatter::LONG, 'medium' => \IntlDateFormatter::MEDIUM, 'short' => \IntlDateFormatter::SHORT,
'relative-full' => \IntlDateFormatter::RELATIVE_FULL, 'relative-long' => \IntlDateFormatter::RELATIVE_LONG, 'relative-medium' => \IntlDateFormatter::RELATIVE_MEDIUM, 'relative-short' => \IntlDateFormatter::RELATIVE_SHORT];
$date ??= $time === null ? 'long' : null;
$formatter = new \IntlDateFormatter($this->locale, $xlt[$date], $xlt[$time]);
} else {
$formatter = new \IntlDateFormatter($this->locale, pattern: (new \IntlDatePatternGenerator($this->locale))->getBestPattern($format));
}

$res = $formatter->format($value);
$res = preg_replace('~(\d\.) ~', "\$1\u{a0}", $res);
return $res;
}


/**
* Formats a number with grouped thousands and optionally decimal digits according to locale.
*/
public function number(
float $number,
string|int $patternOrDecimals = 0,
string $decimalSeparator = '.',
string $thousandsSeparator = ',',
): string
{
if (is_int($patternOrDecimals) && $patternOrDecimals < 0) {
throw new Latte\RuntimeException('Filter |number: the number of decimal must not be negative');
} elseif ($this->locale === null || func_num_args() > 2) {
return number_format($number, $patternOrDecimals, $decimalSeparator, $thousandsSeparator);
}

$formatter = new \NumberFormatter($this->locale, \NumberFormatter::DECIMAL);
if (is_string($patternOrDecimals)) {
$formatter->setPattern($patternOrDecimals);
} else {
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $patternOrDecimals);
}
return $formatter->format($number);
}


/**
* Converts to human-readable file size.
*/
Expand Down Expand Up @@ -556,6 +621,24 @@ public static function group(iterable $data, string|int|\Closure $by): iterable
}


/**
* Filters elements according to a given $predicate. Maintains original keys.
* @template K
* @template V
* @param iterable<K, V> $iterable
* @param callable(V, K, iterable<K, V>): bool $predicate
* @return iterable<K, V>
*/
public static function filter(iterable $iterable, callable $predicate): iterable
{
foreach ($iterable as $k => $v) {
if ($predicate($v, $k, $iterable)) {
yield $k => $v;
}
}
}


/**
* Returns value clamped to the inclusive range of min and max.
*/
Expand Down
32 changes: 14 additions & 18 deletions src/Latte/Essential/Nodes/VarNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
namespace Latte\Essential\Nodes;

use Latte\Compiler\Nodes\Php\Expression\AssignNode;
use Latte\Compiler\Nodes\Php\Expression\AssignOpNode;
use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode;
use Latte\Compiler\Nodes\Php\Expression\TernaryNode;
use Latte\Compiler\Nodes\Php\Expression\VariableNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;
use Latte\Compiler\Nodes\Php\Scalar\NullNode;
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\PrintContext;
Expand Down Expand Up @@ -67,25 +69,19 @@ private static function parseAssignments(Tag $tag, bool $default): array
public function print(PrintContext $context): string
{
$res = [];
if ($this->default) {
foreach ($this->assignments as $assign) {
foreach ($this->assignments as $assign) {
if ($this->default) {
assert($assign->var instanceof VariableNode);
if ($assign->var->name instanceof ExpressionNode) {
$var = $assign->var->name->print($context);
} else {
$var = $context->encodeString($assign->var->name);
}
$res[] = $var . ' => ' . $assign->expr->print($context);
$assign = new AssignOpNode(
$assign->var,
'??',
new TernaryNode(
new AuxiliaryNode(fn() => 'array_key_exists(' . $context->encodeString($assign->var->name) . ', get_defined_vars())'),
new NullNode,
$assign->expr,
),
);
}

return $context->format(
'extract([%raw], EXTR_SKIP) %line;',
implode(', ', $res),
$this->position,
);
}

foreach ($this->assignments as $assign) {
$res[] = $assign->print($context);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Latte/Essential/TranslatorExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function getTags(): array
public function getFilters(): array
{
return [
'translate' => fn(Latte\Runtime\FilterInfo $fi, ...$args): string => $this->translator
'translate' => fn(Latte\Runtime\FilterInfo $fi, ...$args) => $this->translator
? ($this->translator)(...$args)
: $args[0],
];
Expand Down
9 changes: 5 additions & 4 deletions src/Latte/Loaders/FileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function isExpired(string $file, int $time): bool
*/
public function getReferredName(string $file, string $referringFile): string
{
if ($this->baseDir || !preg_match('#/|\\\\|[a-z][a-z0-9+.-]*:#iA', $file)) {
if ($this->baseDir || !preg_match('#/|\\\\|[a-z]:|phar:#iA', $file)) {
$file = $this->normalizePath($referringFile . '/../' . $file);
}

Expand All @@ -78,15 +78,16 @@ public function getUniqueId(string $file): string

protected static function normalizePath(string $path): string
{
preg_match('#^([a-z]:|phar://.+?/)?(.*)#i', $path, $m);
$res = [];
foreach (explode('/', strtr($path, '\\', '/')) as $part) {
if ($part === '..' && $res && end($res) !== '..') {
foreach (explode('/', strtr($m[2], '\\', '/')) as $part) {
if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') {
array_pop($res);
} elseif ($part !== '.') {
$res[] = $part;
}
}

return implode(DIRECTORY_SEPARATOR, $res);
return $m[1] . implode(DIRECTORY_SEPARATOR, $res);
}
}
1 change: 0 additions & 1 deletion src/Latte/Sandbox/Nodes/FunctionCallNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,5 @@ public function print(PrintContext $context): string
return '$this->global->sandbox->call('
. $context->memberAsString($this->name) . ', '
. $context->argumentsAsArray($this->args) . ')';

}
}
5 changes: 5 additions & 0 deletions tests/common/Loaders.FileLoader.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ Assert::same('/a/b/inner', strtr($loader->getReferredName('inner', '/a\\b/c'), '
Assert::same('/a/b/c', strtr($loader->getReferredName('/a/b/c', '/a/b/c'), '\\', '/'));
Assert::same('/a/c', strtr($loader->getReferredName('../c', '/a/b/c'), '\\', '/'));

Assert::same('/../c', strtr($loader->getReferredName('../c', '/a'), '\\', '/'));
Assert::same('w:/../c', strtr($loader->getReferredName('../c', 'w:\a'), '\\', '/'));
Assert::same('phar://file.phar/../c', strtr($loader->getReferredName('../c', 'phar://file.phar/a'), '\\', '/'));


$loader = new FileLoader;
Assert::exception(
fn() => $loader->getContent('unknown'),
Expand Down
50 changes: 17 additions & 33 deletions tests/filters/date.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,31 @@ use Tester\Assert;
require __DIR__ . '/../bootstrap.php';


test('no locale', function () {
$filters = new Filters;
test('datatypes', function () {
Assert::same("5.\u{a0}5.\u{a0}1978", Filters::date('1978-05-05'));
Assert::same("23.\u{a0}1.\u{a0}1978", Filters::date(254_400_000));
Assert::same("5.\u{a0}5.\u{a0}1978", Filters::date(new DateTime('1978-05-05')));
Assert::same("5.\u{a0}5.\u{a0}1978", Filters::date(new DateTimeImmutable('1978-05-05')));
});

Assert::null($filters->date(null));
Assert::same("5.\u{a0}5.\u{a0}1978", $filters->date('1978-05-05'));
Assert::same("5.\u{a0}5.\u{a0}1978", $filters->date(new DateTime('1978-05-05')));
Assert::same('1978-01-23', $filters->date(254_400_000, 'Y-m-d'));
Assert::same('1212-09-26', $filters->date('1212-09-26', 'Y-m-d'));
Assert::same('1212-09-26', $filters->date(new DateTimeImmutable('1212-09-26'), 'Y-m-d'));

// timestamp
date_default_timezone_set('America/Los_Angeles');
Assert::same("23.\u{a0}1.\u{a0}1978", $filters->date(254_400_000));
Assert::same('07:09', $filters->date(1_408_284_571, 'H:i'));
test('edge cases', function () {
Assert::null(Filters::date(null));
Assert::null(Filters::date(''));
});


test('date interval', function () {
$filters = new Filters;

Assert::same('30:10:10', $filters->date(new DateInterval('PT30H10M10S'), '%H:%I:%S'));
test('timestamp & zone', function () {
date_default_timezone_set('America/Los_Angeles');
Assert::same('07:09', Filters::date(1_408_284_571, 'H:i'));
});


test('local date/time', function () {
$filters = new Filters;
$filters->locale = 'cs_CZ';

// date format
Assert::null($filters->date(null, 'medium'));
Assert::same("5.\u{a0}5.\u{a0}1978", $filters->date('1978-05-05', 'medium'));
Assert::same('05.05.78', $filters->date(new DateTime('1978-05-05'), 'short'));
Assert::same("5.\u{a0}5.\u{a0}1978", $filters->date(new DateTime('1978-05-05'), 'medium'));
Assert::same("5.\u{a0}května 1978", $filters->date(new DateTime('1978-05-05'), 'long'));
Assert::same("pátek 5.\u{a0}května 1978", $filters->date(new DateTime('1978-05-05'), 'full'));
test('date/time formatting', function () {
Assert::same('1212-09-26', Filters::date('1212-09-26', 'Y-m-d'));
});

// time format
Assert::same('12:13', $filters->date(new DateTime('12:13:14'), 'time'));
Assert::same('12:13:14', $filters->date(new DateTime('12:13:14'), 'time+sec'));

// combined
Assert::same('05.05.78 12:13', $filters->date(new DateTime('1978-05-05 12:13:14'), 'short+time'));
Assert::same('05.05.78 12:13:14', $filters->date(new DateTime('1978-05-05 12:13:14'), 'short+time+sec'));
test('interval', function () {
Assert::same('30:10:10', Filters::date(new DateInterval('PT30H10M10S'), '%H:%I:%S'));
});
Loading

0 comments on commit a93aca2

Please sign in to comment.