@vituum/vite-plugin-latte 1.0.0-beta.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +2 -2
- package/package.json +13 -11
- package/types/index.d.ts +2 -0
- package/vendor/autoload.php +19 -1
- package/vendor/bin/latte-lint +16 -4
- package/vendor/composer/ClassLoader.php +72 -65
- package/vendor/composer/InstalledVersions.php +21 -12
- package/vendor/composer/autoload_classmap.php +12 -8
- package/vendor/composer/autoload_namespaces.php +1 -1
- package/vendor/composer/autoload_psr4.php +1 -1
- package/vendor/composer/autoload_real.php +7 -26
- package/vendor/composer/autoload_static.php +13 -9
- package/vendor/composer/installed.json +8 -8
- package/vendor/composer/installed.php +10 -10
- package/vendor/latte/latte/bin/latte-lint +8 -2
- package/vendor/latte/latte/composer.json +1 -1
- package/vendor/latte/latte/readme.md +6 -6
- package/vendor/latte/latte/src/Bridges/Tracy/BlueScreenPanel.php +1 -0
- package/vendor/latte/latte/src/Bridges/Tracy/LattePanel.php +3 -2
- package/vendor/latte/latte/src/Latte/Compiler/Block.php +0 -3
- package/vendor/latte/latte/src/Latte/Compiler/Escaper.php +103 -88
- package/vendor/latte/latte/src/Latte/Compiler/ExpressionBuilder.php +2 -1
- package/vendor/latte/latte/src/Latte/Compiler/Node.php +0 -4
- package/vendor/latte/latte/src/Latte/Compiler/NodeHelpers.php +0 -4
- package/vendor/latte/latte/src/Latte/Compiler/NodeTraverser.php +0 -4
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/AuxiliaryNode.php +11 -3
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/FragmentNode.php +10 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/AttributeNode.php +22 -4
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/ElementNode.php +35 -12
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArgumentNode.php +6 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArrayItemNode.php +12 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ArrayNode.php +4 -31
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignNode.php +15 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignOpNode.php +11 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AuxiliaryNode.php +42 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClassConstantFetchNode.php +2 -2
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClosureNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ConstantFetchNode.php +8 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/IssetNode.php +15 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PostOpNode.php +11 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PreOpNode.php +11 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallNode.php → StaticMethodCallNode.php} +11 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallableNode.php → StaticMethodCallableNode.php} +11 -1
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/TemporaryNode.php +38 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ExpressionNode.php +24 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListItemNode.php +48 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListNode.php +56 -0
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php +3 -3
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/InterpolatedStringNode.php +1 -8
- package/vendor/latte/latte/src/Latte/Compiler/PhpHelpers.php +30 -0
- package/vendor/latte/latte/src/Latte/Compiler/Position.php +4 -8
- package/vendor/latte/latte/src/Latte/Compiler/PrintContext.php +14 -7
- package/vendor/latte/latte/src/Latte/Compiler/Tag.php +13 -14
- package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +3 -7
- package/vendor/latte/latte/src/Latte/Compiler/TagParser.php +52 -3
- package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +354 -322
- package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +6 -5
- package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +95 -176
- package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +40 -33
- package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +182 -126
- package/vendor/latte/latte/src/Latte/Compiler/Token.php +5 -9
- package/vendor/latte/latte/src/Latte/Compiler/TokenStream.php +6 -22
- package/vendor/latte/latte/src/Latte/Engine.php +37 -8
- package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +0 -2
- package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +0 -4
- package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +14 -3
- package/vendor/latte/latte/src/Latte/Essential/Filters.php +8 -6
- package/vendor/latte/latte/src/Latte/Essential/Nodes/BlockNode.php +1 -2
- package/vendor/latte/latte/src/Latte/Essential/Nodes/CaptureNode.php +2 -13
- package/vendor/latte/latte/src/Latte/Essential/Nodes/ContentTypeNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/DefineNode.php +1 -2
- package/vendor/latte/latte/src/Latte/Essential/Nodes/EmbedNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/ExtendsNode.php +0 -3
- package/vendor/latte/latte/src/Latte/Essential/Nodes/FirstLastSepNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/ForNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/ForeachNode.php +4 -13
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IfChangedNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IfContentNode.php +4 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IfNode.php +5 -3
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IncludeBlockNode.php +5 -2
- package/vendor/latte/latte/src/Latte/Essential/Nodes/IterateWhileNode.php +6 -4
- package/vendor/latte/latte/src/Latte/Essential/Nodes/JumpNode.php +26 -23
- package/vendor/latte/latte/src/Latte/Essential/Nodes/NElseNode.php +88 -0
- package/vendor/latte/latte/src/Latte/Essential/Nodes/NTagNode.php +20 -28
- package/vendor/latte/latte/src/Latte/Essential/Nodes/PrintNode.php +7 -12
- package/vendor/latte/latte/src/Latte/Essential/Nodes/RollbackNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/SpacelessNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/SwitchNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/TranslateNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Nodes/TryNode.php +3 -4
- package/vendor/latte/latte/src/Latte/Essential/Nodes/WhileNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Essential/Passes.php +9 -11
- package/vendor/latte/latte/src/Latte/Essential/RawPhpExtension.php +0 -2
- package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +6 -1
- package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +0 -2
- package/vendor/latte/latte/src/Latte/Loaders/StringLoader.php +0 -2
- package/vendor/latte/latte/src/Latte/PositionAwareException.php +1 -1
- package/vendor/latte/latte/src/Latte/Runtime/Block.php +0 -4
- package/vendor/latte/latte/src/Latte/Runtime/FilterExecutor.php +43 -51
- package/vendor/latte/latte/src/Latte/Runtime/FilterInfo.php +0 -2
- package/vendor/latte/latte/src/Latte/Runtime/Filters.php +64 -30
- package/vendor/latte/latte/src/Latte/Runtime/Html.php +0 -4
- package/vendor/latte/latte/src/Latte/Runtime/Template.php +2 -2
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +2 -1
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/MethodCallNode.php +1 -1
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/SandboxNode.php +3 -3
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallNode.php → StaticMethodCallNode.php} +3 -3
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallableNode.php → StaticMethodCallableNode.php} +2 -2
- package/vendor/latte/latte/src/Latte/Sandbox/RuntimeChecker.php +0 -2
- package/vendor/latte/latte/src/Latte/Sandbox/SandboxExtension.php +11 -9
- package/vendor/latte/latte/src/Latte/Sandbox/SecurityPolicy.php +0 -2
- package/vendor/latte/latte/src/Latte/exceptions.php +2 -11
- package/vendor/latte/latte/src/Tools/Linter.php +13 -37
- package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/QuotedValue.php +0 -53
- package/vendor/latte/latte/src/Latte/Strict.php +0 -101
|
@@ -11,10 +11,12 @@ namespace Latte\Essential\Nodes;
|
|
|
11
11
|
|
|
12
12
|
use Latte;
|
|
13
13
|
use Latte\CompileException;
|
|
14
|
-
use Latte\Compiler\Nodes\Php\
|
|
14
|
+
use Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode;
|
|
15
15
|
use Latte\Compiler\Nodes\StatementNode;
|
|
16
16
|
use Latte\Compiler\PrintContext;
|
|
17
17
|
use Latte\Compiler\Tag;
|
|
18
|
+
use Latte\Compiler\TemplateParser;
|
|
19
|
+
use Latte\ContentType;
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -22,50 +24,40 @@ use Latte\Compiler\Tag;
|
|
|
22
24
|
*/
|
|
23
25
|
final class NTagNode extends StatementNode
|
|
24
26
|
{
|
|
25
|
-
public
|
|
26
|
-
public string $origName;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
public static function create(Tag $tag): void
|
|
27
|
+
public static function create(Tag $tag, TemplateParser $parser): void
|
|
30
28
|
{
|
|
31
29
|
if (preg_match('(style$|script$)iA', $tag->htmlElement->name)) {
|
|
32
30
|
throw new CompileException('Attribute n:tag is not allowed in <script> or <style>', $tag->position);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
$tag->expectArguments();
|
|
36
|
-
$
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
$tag->htmlElement->variableName = new AuxiliaryNode(
|
|
35
|
+
fn(PrintContext $context, $newName) => $context->format(
|
|
36
|
+
self::class . '::check(%dump, %node, %dump)',
|
|
37
|
+
$tag->htmlElement->name,
|
|
38
|
+
$newName,
|
|
39
|
+
$parser->getContentType() === ContentType::Xml,
|
|
40
|
+
),
|
|
41
|
+
[$tag->parser->parseExpression()],
|
|
42
|
+
);
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
public function print(PrintContext $context): string
|
|
44
47
|
{
|
|
45
|
-
|
|
46
|
-
. var_export($this->origName, true)
|
|
47
|
-
. ', '
|
|
48
|
-
. $this->name->print($context)
|
|
49
|
-
. ')';
|
|
48
|
+
throw new \LogicException('Cannot directly print');
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
|
|
53
|
-
public static function check(string $orig, $new):
|
|
52
|
+
public static function check(string $orig, mixed $new, bool $xml): mixed
|
|
54
53
|
{
|
|
55
54
|
if ($new === null) {
|
|
56
55
|
return $orig;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|| !preg_match('~' . Latte\Compiler\TemplateLexer::ReTagName . '$~DA', $new)
|
|
61
|
-
) {
|
|
62
|
-
throw new Latte\RuntimeException('Invalid tag name ' . var_export($new, true));
|
|
63
|
-
|
|
64
|
-
} elseif (
|
|
65
|
-
in_array($lower = strtolower($new), ['style', 'script'], true)
|
|
66
|
-
|| isset(Latte\Helpers::$emptyElements[strtolower($orig)]) !== isset(Latte\Helpers::$emptyElements[$lower])
|
|
56
|
+
} elseif (!$xml
|
|
57
|
+
&& is_string($new)
|
|
58
|
+
&& isset(Latte\Helpers::$emptyElements[strtolower($orig)]) !== isset(Latte\Helpers::$emptyElements[strtolower($new)])
|
|
67
59
|
) {
|
|
68
|
-
throw new Latte\RuntimeException("Forbidden tag <$orig> change to <$new
|
|
60
|
+
throw new Latte\RuntimeException("Forbidden tag <$orig> change to <$new>");
|
|
69
61
|
}
|
|
70
62
|
|
|
71
63
|
return $new;
|
|
@@ -74,6 +66,6 @@ final class NTagNode extends StatementNode
|
|
|
74
66
|
|
|
75
67
|
public function &getIterator(): \Generator
|
|
76
68
|
{
|
|
77
|
-
yield
|
|
69
|
+
false && yield;
|
|
78
70
|
}
|
|
79
71
|
}
|
|
@@ -16,7 +16,6 @@ use Latte\Compiler\Nodes\StatementNode;
|
|
|
16
16
|
use Latte\Compiler\PrintContext;
|
|
17
17
|
use Latte\Compiler\Tag;
|
|
18
18
|
use Latte\Compiler\TemplateParser;
|
|
19
|
-
use Latte\ContentType;
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
/**
|
|
@@ -26,24 +25,17 @@ class PrintNode extends StatementNode
|
|
|
26
25
|
{
|
|
27
26
|
public ExpressionNode $expression;
|
|
28
27
|
public ModifierNode $modifier;
|
|
28
|
+
private ?string $followsQuote = null;
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
public static function create(Tag $tag, TemplateParser $parser): static
|
|
32
32
|
{
|
|
33
33
|
$tag->outputMode = $tag::OutputKeepIndentation;
|
|
34
|
-
|
|
35
|
-
$stream = $parser->getStream();
|
|
36
|
-
if (
|
|
37
|
-
$tag->isInText()
|
|
38
|
-
&& $parser->getContentType() === ContentType::Html
|
|
39
|
-
&& $tag->htmlElement?->name === 'script'
|
|
40
|
-
&& preg_match('#["\']#A', $stream->peek()->text)
|
|
41
|
-
) {
|
|
42
|
-
throw new CompileException("Do not place {$tag->getNotation(true)} inside quotes in JavaScript.", $tag->position);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
34
|
$tag->expectArguments();
|
|
46
35
|
$node = new static;
|
|
36
|
+
$node->followsQuote = preg_match('#["\']#A', $parser->getStream()->peek()->text)
|
|
37
|
+
? $tag->getNotation(true)
|
|
38
|
+
: null;
|
|
47
39
|
$node->expression = $tag->parser->parseExpression();
|
|
48
40
|
$node->modifier = $tag->parser->parseModifier();
|
|
49
41
|
$node->modifier->escape = true;
|
|
@@ -53,6 +45,9 @@ class PrintNode extends StatementNode
|
|
|
53
45
|
|
|
54
46
|
public function print(PrintContext $context): string
|
|
55
47
|
{
|
|
48
|
+
if ($this->followsQuote && $context->getEscaper()->export() === 'html/raw/js') {
|
|
49
|
+
throw new CompileException("Do not place {$this->followsQuote} inside quotes in JavaScript.", $this->position);
|
|
50
|
+
}
|
|
56
51
|
return $context->format(
|
|
57
52
|
"echo %modify(%node) %line;\n",
|
|
58
53
|
$this->modifier,
|
|
@@ -22,7 +22,7 @@ class RollbackNode extends StatementNode
|
|
|
22
22
|
{
|
|
23
23
|
public static function create(Tag $tag): static
|
|
24
24
|
{
|
|
25
|
-
if (!$tag->closestTag([
|
|
25
|
+
if (!$tag->closestTag([TryNode::class])) {
|
|
26
26
|
throw new CompileException('Tag {rollback} must be inside {try} ... {/try}.', $tag->position);
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -27,7 +27,7 @@ class SpacelessNode extends StatementNode
|
|
|
27
27
|
/** @return \Generator<int, ?array, array{AreaNode, ?Tag}, static> */
|
|
28
28
|
public static function create(Tag $tag): \Generator
|
|
29
29
|
{
|
|
30
|
-
$node = new static;
|
|
30
|
+
$node = $tag->node = new static;
|
|
31
31
|
[$node->content] = yield;
|
|
32
32
|
return $node;
|
|
33
33
|
}
|
|
@@ -37,7 +37,7 @@ class SwitchNode extends StatementNode
|
|
|
37
37
|
throw new CompileException('Attribute n:switch is not supported.', $tag->position);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
$node = new static;
|
|
40
|
+
$node = $tag->node = new static;
|
|
41
41
|
$node->expression = $tag->parser->isEnd()
|
|
42
42
|
? null
|
|
43
43
|
: $tag->parser->parseExpression();
|
|
@@ -35,7 +35,7 @@ class TranslateNode extends StatementNode
|
|
|
35
35
|
{
|
|
36
36
|
$tag->outputMode = $tag::OutputKeepIndentation;
|
|
37
37
|
|
|
38
|
-
$node = new static;
|
|
38
|
+
$node = $tag->node = new static;
|
|
39
39
|
$args = $tag->parser->parseArguments();
|
|
40
40
|
$node->modifier = $tag->parser->parseModifier();
|
|
41
41
|
$node->modifier->escape = true;
|
|
@@ -25,9 +25,9 @@ class TryNode extends StatementNode
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
/** @return \Generator<int, ?array, array{AreaNode, ?Tag}, static> */
|
|
28
|
-
public static function create(): \Generator
|
|
28
|
+
public static function create(Tag $tag): \Generator
|
|
29
29
|
{
|
|
30
|
-
$node = new static;
|
|
30
|
+
$node = $tag->node = new static;
|
|
31
31
|
[$node->try, $nextTag] = yield ['else'];
|
|
32
32
|
if ($nextTag?->name === 'else') {
|
|
33
33
|
[$node->else] = yield;
|
|
@@ -46,12 +46,11 @@ class TryNode extends StatementNode
|
|
|
46
46
|
try %line {
|
|
47
47
|
%node
|
|
48
48
|
} catch (Throwable $ʟ_e) {
|
|
49
|
-
|
|
49
|
+
ob_clean();
|
|
50
50
|
if (!($ʟ_e instanceof Latte\Essential\RollbackException) && isset($this->global->coreExceptionHandler)) {
|
|
51
51
|
($this->global->coreExceptionHandler)($ʟ_e, $this);
|
|
52
52
|
}
|
|
53
53
|
%node
|
|
54
|
-
ob_start();
|
|
55
54
|
} finally {
|
|
56
55
|
echo ob_get_clean();
|
|
57
56
|
$iterator = $ʟ_it = $ʟ_try[%0.dump][0];
|
|
@@ -29,7 +29,7 @@ class WhileNode extends StatementNode
|
|
|
29
29
|
/** @return \Generator<int, ?array, array{AreaNode, ?Tag}, static> */
|
|
30
30
|
public static function create(Tag $tag): \Generator
|
|
31
31
|
{
|
|
32
|
-
$node = new static;
|
|
32
|
+
$node = $tag->node = new static;
|
|
33
33
|
$node->postTest = $tag->parser->isEnd();
|
|
34
34
|
if (!$node->postTest) {
|
|
35
35
|
$node->condition = $tag->parser->parseExpression();
|
|
@@ -11,10 +11,9 @@ namespace Latte\Essential;
|
|
|
11
11
|
|
|
12
12
|
use Latte;
|
|
13
13
|
use Latte\CompileException;
|
|
14
|
-
use Latte\Compiler\ExpressionBuilder;
|
|
15
14
|
use Latte\Compiler\Node;
|
|
16
15
|
use Latte\Compiler\Nodes\AuxiliaryNode;
|
|
17
|
-
use Latte\Compiler\Nodes\Php\Expression
|
|
16
|
+
use Latte\Compiler\Nodes\Php\Expression;
|
|
18
17
|
use Latte\Compiler\Nodes\Php\Expression\VariableNode;
|
|
19
18
|
use Latte\Compiler\Nodes\Php\NameNode;
|
|
20
19
|
use Latte\Compiler\Nodes\TemplateNode;
|
|
@@ -25,8 +24,6 @@ use Latte\Essential\Nodes\ForeachNode;
|
|
|
25
24
|
|
|
26
25
|
final class Passes
|
|
27
26
|
{
|
|
28
|
-
use Latte\Strict;
|
|
29
|
-
|
|
30
27
|
/**
|
|
31
28
|
* Checks if foreach overrides template variables.
|
|
32
29
|
*/
|
|
@@ -81,7 +78,7 @@ final class Passes
|
|
|
81
78
|
$names = array_combine(array_map('strtolower', $names), $names);
|
|
82
79
|
|
|
83
80
|
(new NodeTraverser)->traverse($node, function (Node $node) use ($names) {
|
|
84
|
-
if (($node instanceof FunctionCallNode || $node instanceof FunctionCallableNode)
|
|
81
|
+
if (($node instanceof Expression\FunctionCallNode || $node instanceof Expression\FunctionCallableNode)
|
|
85
82
|
&& $node->name instanceof NameNode
|
|
86
83
|
&& ($orig = $names[strtolower((string) $node->name)] ?? null)
|
|
87
84
|
) {
|
|
@@ -89,10 +86,10 @@ final class Passes
|
|
|
89
86
|
trigger_error("Case mismatch on function name '{$node->name}', correct name is '$orig'.", E_USER_WARNING);
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
return
|
|
93
|
-
|
|
89
|
+
return new Expression\AuxiliaryNode(
|
|
90
|
+
fn(PrintContext $context, ...$args) => '($this->global->fn->' . $orig . ')(' . $context->implode($args) . ')',
|
|
94
91
|
$node->args,
|
|
95
|
-
)
|
|
92
|
+
);
|
|
96
93
|
}
|
|
97
94
|
});
|
|
98
95
|
}
|
|
@@ -101,12 +98,13 @@ final class Passes
|
|
|
101
98
|
/**
|
|
102
99
|
* $ʟ_xxx variables are forbidden
|
|
103
100
|
*/
|
|
104
|
-
public static function internalVariablesPass(TemplateNode $node): void
|
|
101
|
+
public static function internalVariablesPass(TemplateNode $node, bool $forbidThis = false): void
|
|
105
102
|
{
|
|
106
|
-
|
|
103
|
+
$forbidden = $forbidThis ? ['GLOBALS', 'this'] : ['GLOBALS'];
|
|
104
|
+
(new NodeTraverser)->traverse($node, function (Node $node) use ($forbidden) {
|
|
107
105
|
if ($node instanceof VariableNode
|
|
108
106
|
&& is_string($node->name)
|
|
109
|
-
&& (str_starts_with($node->name, 'ʟ_'))
|
|
107
|
+
&& (str_starts_with($node->name, 'ʟ_') || in_array($node->name, $forbidden, true))
|
|
110
108
|
) {
|
|
111
109
|
throw new CompileException("Forbidden variable \$$node->name.", $node->position);
|
|
112
110
|
}
|
|
@@ -23,10 +23,15 @@ use Nette\Localization\Translator;
|
|
|
23
23
|
*/
|
|
24
24
|
final class TranslatorExtension extends Latte\Extension
|
|
25
25
|
{
|
|
26
|
+
/** @var callable|Translator|null */
|
|
27
|
+
private $translator;
|
|
28
|
+
|
|
29
|
+
|
|
26
30
|
public function __construct(
|
|
27
|
-
|
|
31
|
+
callable|Translator|null $translator,
|
|
28
32
|
private ?string $key = null,
|
|
29
33
|
) {
|
|
34
|
+
$this->translator = $translator;
|
|
30
35
|
if ($translator instanceof Translator) {
|
|
31
36
|
$this->translator = [$translator, 'translate'];
|
|
32
37
|
}
|
|
@@ -38,7 +38,7 @@ trait PositionAwareException
|
|
|
38
38
|
$info[] = "in '" . str_replace(dirname($this->sourceName, 2), '...', $this->sourceName) . "'";
|
|
39
39
|
}
|
|
40
40
|
if ($this->position) {
|
|
41
|
-
$info[] = $this->position
|
|
41
|
+
$info[] = $this->position;
|
|
42
42
|
}
|
|
43
43
|
$this->message = $info
|
|
44
44
|
? rtrim($this->origMessage, '.') . ' (' . implode(' ', $info) . ')'
|
|
@@ -59,39 +59,10 @@ class FilterExecutor
|
|
|
59
59
|
*/
|
|
60
60
|
public function __get(string $name): callable
|
|
61
61
|
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
array_unshift($args, $info = new FilterInfo);
|
|
67
|
-
if ($args[1] instanceof HtmlStringable) {
|
|
68
|
-
$args[1] = $args[1]->__toString();
|
|
69
|
-
$info->contentType = ContentType::Html;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
$res = $callback(...$args);
|
|
73
|
-
return $info->contentType === ContentType::Html
|
|
74
|
-
? new Html($res)
|
|
75
|
-
: $res;
|
|
76
|
-
};
|
|
77
|
-
} else { // classic filter
|
|
78
|
-
return $this->$name = $callback;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// dynamic filter
|
|
83
|
-
foreach ($this->_dynamic as $loader) {
|
|
84
|
-
$callback = $loader($name);
|
|
85
|
-
if ($callback !== null) {
|
|
86
|
-
$this->_static[$name] = [$callback, null];
|
|
87
|
-
return $this->__get($name);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
$hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name))
|
|
92
|
-
? ", did you mean '$t'?"
|
|
93
|
-
: '.';
|
|
94
|
-
throw new \LogicException("Filter '$name' is not defined$hint");
|
|
62
|
+
[$callback, $infoAware] = $this->prepareFilter($name);
|
|
63
|
+
return $this->$name = $infoAware
|
|
64
|
+
? fn(...$args) => $this->callInfoAwareAsClassic($callback, ...$args)
|
|
65
|
+
: $callback;
|
|
95
66
|
}
|
|
96
67
|
|
|
97
68
|
|
|
@@ -100,31 +71,23 @@ class FilterExecutor
|
|
|
100
71
|
*/
|
|
101
72
|
public function filterContent(string $name, FilterInfo $info, mixed ...$args): mixed
|
|
102
73
|
{
|
|
103
|
-
if (!isset($this->_static[$name])) {
|
|
104
|
-
$hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name))
|
|
105
|
-
? ", did you mean '$t'?"
|
|
106
|
-
: '.';
|
|
107
|
-
throw new \LogicException("Filter |$name is not defined$hint");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
[$callback, $aware] = $this->prepareFilter($name);
|
|
111
|
-
|
|
112
74
|
if ($info->contentType === ContentType::Html && $args[0] instanceof HtmlStringable) {
|
|
113
75
|
$args[0] = $args[0]->__toString();
|
|
114
76
|
}
|
|
115
77
|
|
|
116
|
-
|
|
78
|
+
[$callback, $infoAware] = $this->prepareFilter($name);
|
|
79
|
+
if ($infoAware) {
|
|
117
80
|
array_unshift($args, $info);
|
|
118
81
|
return $callback(...$args);
|
|
119
82
|
}
|
|
120
83
|
|
|
121
84
|
// classic filter
|
|
122
85
|
if ($info->contentType !== ContentType::Text) {
|
|
123
|
-
throw new Latte\RuntimeException("Filter |$name is called with incompatible content type " . strtoupper($info->contentType)
|
|
86
|
+
throw new Latte\RuntimeException("Filter |$name is called with incompatible content type " . strtoupper($info->contentType ?? 'NULL')
|
|
124
87
|
. ($info->contentType === ContentType::Html ? ', try to prepend |stripHtml.' : '.'));
|
|
125
88
|
}
|
|
126
89
|
|
|
127
|
-
$res =
|
|
90
|
+
$res = $callback(...$args);
|
|
128
91
|
if ($res instanceof HtmlStringable) {
|
|
129
92
|
trigger_error("Filter |$name should be changed to content-aware filter.");
|
|
130
93
|
$info->contentType = ContentType::Html;
|
|
@@ -140,13 +103,42 @@ class FilterExecutor
|
|
|
140
103
|
*/
|
|
141
104
|
private function prepareFilter(string $name): array
|
|
142
105
|
{
|
|
143
|
-
if (
|
|
144
|
-
$
|
|
145
|
-
$this->_static[$name]
|
|
146
|
-
|
|
147
|
-
|
|
106
|
+
if (isset($this->_static[$name])) {
|
|
107
|
+
$this->_static[$name][1] ??= $this->isInfoAware($this->_static[$name][0]);
|
|
108
|
+
return $this->_static[$name];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
foreach ($this->_dynamic as $loader) {
|
|
112
|
+
if ($callback = $loader($name)) {
|
|
113
|
+
return $this->_static[$name] = [$callback, $this->isInfoAware($callback)];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
$hint = ($t = Helpers::getSuggestion(array_keys($this->_static), $name))
|
|
118
|
+
? ", did you mean '$t'?"
|
|
119
|
+
: '.';
|
|
120
|
+
throw new \LogicException("Filter '$name' is not defined$hint");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
private function isInfoAware(callable $filter): bool
|
|
125
|
+
{
|
|
126
|
+
$params = Helpers::toReflection($filter)->getParameters();
|
|
127
|
+
return $params && (string) $params[0]->getType() === FilterInfo::class;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
private function callInfoAwareAsClassic(callable $filter, mixed ...$args): mixed
|
|
132
|
+
{
|
|
133
|
+
array_unshift($args, $info = new FilterInfo);
|
|
134
|
+
if ($args[1] instanceof HtmlStringable) {
|
|
135
|
+
$args[1] = $args[1]->__toString();
|
|
136
|
+
$info->contentType = ContentType::Html;
|
|
148
137
|
}
|
|
149
138
|
|
|
150
|
-
|
|
139
|
+
$res = $filter(...$args);
|
|
140
|
+
return $info->contentType === ContentType::Html
|
|
141
|
+
? new Html($res)
|
|
142
|
+
: $res;
|
|
151
143
|
}
|
|
152
144
|
}
|
|
@@ -75,9 +75,12 @@ class Filters
|
|
|
75
75
|
public static function escapeHtmlTag($s): string
|
|
76
76
|
{
|
|
77
77
|
$s = (string) $s;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
$s = htmlspecialchars($s, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8');
|
|
79
|
+
return preg_replace_callback(
|
|
80
|
+
'#[=/\s]#',
|
|
81
|
+
fn($m) => '&#' . ord($m[0]) . ';',
|
|
82
|
+
$s,
|
|
83
|
+
);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
|
|
@@ -100,6 +103,28 @@ class Filters
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Escapes HTML for usage in <script type=text/html>
|
|
108
|
+
*/
|
|
109
|
+
public static function escapeHtmlRawTextHtml($s): string
|
|
110
|
+
{
|
|
111
|
+
if ($s instanceof HtmlStringable || $s instanceof Nette\HtmlStringable) {
|
|
112
|
+
return self::convertHtmlToHtmlRawText($s->__toString());
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return htmlspecialchars((string) $s, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Escapes only quotes.
|
|
121
|
+
*/
|
|
122
|
+
public static function escapeHtmlQuotes($s): string
|
|
123
|
+
{
|
|
124
|
+
return strtr((string) $s, ['"' => '"', "'" => ''']);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
103
128
|
/**
|
|
104
129
|
* Escapes string for use everywhere inside XML (except for comments and tags).
|
|
105
130
|
*/
|
|
@@ -122,10 +147,12 @@ class Filters
|
|
|
122
147
|
*/
|
|
123
148
|
public static function escapeXmlTag($s): string
|
|
124
149
|
{
|
|
125
|
-
$s = (string) $s;
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
$s = self::escapeXml((string) $s);
|
|
151
|
+
return preg_replace_callback(
|
|
152
|
+
'#[=/\s]#',
|
|
153
|
+
fn($m) => '&#' . ord($m[0]) . ';',
|
|
154
|
+
$s,
|
|
155
|
+
);
|
|
129
156
|
}
|
|
130
157
|
|
|
131
158
|
|
|
@@ -150,7 +177,7 @@ class Filters
|
|
|
150
177
|
|
|
151
178
|
$json = json_encode($s, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
|
|
152
179
|
if ($error = json_last_error()) {
|
|
153
|
-
throw new Latte\RuntimeException(json_last_error_msg()
|
|
180
|
+
throw new Latte\RuntimeException(json_last_error_msg());
|
|
154
181
|
}
|
|
155
182
|
|
|
156
183
|
return str_replace([']]>', '<!', '</'], [']]\u003E', '\u003C!', '<\/'], $json);
|
|
@@ -169,15 +196,6 @@ class Filters
|
|
|
169
196
|
}
|
|
170
197
|
|
|
171
198
|
|
|
172
|
-
/**
|
|
173
|
-
* Converts JS and CSS for usage in <script> or <style>
|
|
174
|
-
*/
|
|
175
|
-
public static function convertJSToHtmlRawText($s): string
|
|
176
|
-
{
|
|
177
|
-
return preg_replace('#</(script|style)#i', '<\/$1', (string) $s);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
199
|
/**
|
|
182
200
|
* Converts ... to ...
|
|
183
201
|
*/
|
|
@@ -202,29 +220,29 @@ class Filters
|
|
|
202
220
|
|
|
203
221
|
|
|
204
222
|
/**
|
|
205
|
-
* Converts
|
|
223
|
+
* Converts JS and CSS for usage in <script> or <style>
|
|
206
224
|
*/
|
|
207
|
-
public static function
|
|
225
|
+
public static function convertJSToHtmlRawText($s): string
|
|
208
226
|
{
|
|
209
|
-
return
|
|
227
|
+
return preg_replace('#</(script|style)#i', '<\/$1', (string) $s);
|
|
210
228
|
}
|
|
211
229
|
|
|
212
230
|
|
|
213
231
|
/**
|
|
214
|
-
*
|
|
232
|
+
* Sanitizes <script> in <script type=text/html>
|
|
215
233
|
*/
|
|
216
|
-
public static function
|
|
234
|
+
public static function convertHtmlToHtmlRawText(string $s): string
|
|
217
235
|
{
|
|
218
|
-
return '
|
|
236
|
+
return preg_replace('#(</?)(script)#i', '$1x-$2', $s);
|
|
219
237
|
}
|
|
220
238
|
|
|
221
239
|
|
|
222
240
|
/**
|
|
223
|
-
* Converts HTML quoted attribute to
|
|
241
|
+
* Converts HTML text to quoted attribute. The quotation marks need to be escaped.
|
|
224
242
|
*/
|
|
225
|
-
public static function
|
|
243
|
+
public static function convertHtmlToHtmlAttr(string $s): string
|
|
226
244
|
{
|
|
227
|
-
return
|
|
245
|
+
return self::escapeHtmlAttr($s, false);
|
|
228
246
|
}
|
|
229
247
|
|
|
230
248
|
|
|
@@ -241,12 +259,28 @@ class Filters
|
|
|
241
259
|
/**
|
|
242
260
|
* Sanitizes string for use inside href attribute.
|
|
243
261
|
*/
|
|
244
|
-
public static function safeUrl(string
|
|
262
|
+
public static function safeUrl(string|\Stringable $s): string
|
|
245
263
|
{
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
264
|
+
$s = $s instanceof HtmlStringable
|
|
265
|
+
? self::convertHtmlToText((string) $s)
|
|
266
|
+
: (string) $s;
|
|
249
267
|
|
|
250
268
|
return preg_match('~^(?:(?:https?|ftp)://[^@]+(?:/.*)?|(?:mailto|tel|sms):.+|[/?#].*|[^:]+)$~Di', $s) ? $s : '';
|
|
251
269
|
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Validates HTML tag name.
|
|
274
|
+
*/
|
|
275
|
+
public static function safeTag(mixed $name, bool $xml = false): string
|
|
276
|
+
{
|
|
277
|
+
if (!is_string($name)) {
|
|
278
|
+
throw new Latte\RuntimeException('Tag name must be string, ' . get_debug_type($name) . ' given');
|
|
279
|
+
} elseif (!preg_match('~' . Latte\Compiler\TemplateLexer::ReTagName . '$~DA', $name)) {
|
|
280
|
+
throw new Latte\RuntimeException("Invalid tag name '$name'");
|
|
281
|
+
} elseif (!$xml && in_array(strtolower($name), ['style', 'script'], true)) {
|
|
282
|
+
throw new Latte\RuntimeException("Forbidden variable tag name <$name>");
|
|
283
|
+
}
|
|
284
|
+
return $name;
|
|
285
|
+
}
|
|
252
286
|
}
|