@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.
Files changed (115) hide show
  1. package/index.js +2 -2
  2. package/package.json +13 -11
  3. package/types/index.d.ts +2 -0
  4. package/vendor/autoload.php +19 -1
  5. package/vendor/bin/latte-lint +16 -4
  6. package/vendor/composer/ClassLoader.php +72 -65
  7. package/vendor/composer/InstalledVersions.php +21 -12
  8. package/vendor/composer/autoload_classmap.php +12 -8
  9. package/vendor/composer/autoload_namespaces.php +1 -1
  10. package/vendor/composer/autoload_psr4.php +1 -1
  11. package/vendor/composer/autoload_real.php +7 -26
  12. package/vendor/composer/autoload_static.php +13 -9
  13. package/vendor/composer/installed.json +8 -8
  14. package/vendor/composer/installed.php +10 -10
  15. package/vendor/latte/latte/bin/latte-lint +8 -2
  16. package/vendor/latte/latte/composer.json +1 -1
  17. package/vendor/latte/latte/readme.md +6 -6
  18. package/vendor/latte/latte/src/Bridges/Tracy/BlueScreenPanel.php +1 -0
  19. package/vendor/latte/latte/src/Bridges/Tracy/LattePanel.php +3 -2
  20. package/vendor/latte/latte/src/Latte/Compiler/Block.php +0 -3
  21. package/vendor/latte/latte/src/Latte/Compiler/Escaper.php +103 -88
  22. package/vendor/latte/latte/src/Latte/Compiler/ExpressionBuilder.php +2 -1
  23. package/vendor/latte/latte/src/Latte/Compiler/Node.php +0 -4
  24. package/vendor/latte/latte/src/Latte/Compiler/NodeHelpers.php +0 -4
  25. package/vendor/latte/latte/src/Latte/Compiler/NodeTraverser.php +0 -4
  26. package/vendor/latte/latte/src/Latte/Compiler/Nodes/AuxiliaryNode.php +11 -3
  27. package/vendor/latte/latte/src/Latte/Compiler/Nodes/FragmentNode.php +10 -0
  28. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/AttributeNode.php +22 -4
  29. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/ElementNode.php +35 -12
  30. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArgumentNode.php +6 -0
  31. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ArrayItemNode.php +12 -0
  32. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ArrayNode.php +4 -31
  33. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignNode.php +15 -1
  34. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AssignOpNode.php +11 -0
  35. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/AuxiliaryNode.php +42 -0
  36. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClassConstantFetchNode.php +2 -2
  37. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ClosureNode.php +1 -1
  38. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/ConstantFetchNode.php +8 -0
  39. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/IssetNode.php +15 -1
  40. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PostOpNode.php +11 -0
  41. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/PreOpNode.php +11 -0
  42. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallNode.php → StaticMethodCallNode.php} +11 -1
  43. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/{StaticCallableNode.php → StaticMethodCallableNode.php} +11 -1
  44. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Expression/TemporaryNode.php +38 -0
  45. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ExpressionNode.php +24 -0
  46. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListItemNode.php +48 -0
  47. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListNode.php +56 -0
  48. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php +3 -3
  49. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/InterpolatedStringNode.php +1 -8
  50. package/vendor/latte/latte/src/Latte/Compiler/PhpHelpers.php +30 -0
  51. package/vendor/latte/latte/src/Latte/Compiler/Position.php +4 -8
  52. package/vendor/latte/latte/src/Latte/Compiler/PrintContext.php +14 -7
  53. package/vendor/latte/latte/src/Latte/Compiler/Tag.php +13 -14
  54. package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +3 -7
  55. package/vendor/latte/latte/src/Latte/Compiler/TagParser.php +52 -3
  56. package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +354 -322
  57. package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +6 -5
  58. package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +95 -176
  59. package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +40 -33
  60. package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +182 -126
  61. package/vendor/latte/latte/src/Latte/Compiler/Token.php +5 -9
  62. package/vendor/latte/latte/src/Latte/Compiler/TokenStream.php +6 -22
  63. package/vendor/latte/latte/src/Latte/Engine.php +37 -8
  64. package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +0 -2
  65. package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +0 -4
  66. package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +14 -3
  67. package/vendor/latte/latte/src/Latte/Essential/Filters.php +8 -6
  68. package/vendor/latte/latte/src/Latte/Essential/Nodes/BlockNode.php +1 -2
  69. package/vendor/latte/latte/src/Latte/Essential/Nodes/CaptureNode.php +2 -13
  70. package/vendor/latte/latte/src/Latte/Essential/Nodes/ContentTypeNode.php +1 -1
  71. package/vendor/latte/latte/src/Latte/Essential/Nodes/DefineNode.php +1 -2
  72. package/vendor/latte/latte/src/Latte/Essential/Nodes/EmbedNode.php +1 -1
  73. package/vendor/latte/latte/src/Latte/Essential/Nodes/ExtendsNode.php +0 -3
  74. package/vendor/latte/latte/src/Latte/Essential/Nodes/FirstLastSepNode.php +1 -1
  75. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForNode.php +1 -1
  76. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForeachNode.php +4 -13
  77. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfChangedNode.php +1 -1
  78. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfContentNode.php +4 -1
  79. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfNode.php +5 -3
  80. package/vendor/latte/latte/src/Latte/Essential/Nodes/IncludeBlockNode.php +5 -2
  81. package/vendor/latte/latte/src/Latte/Essential/Nodes/IterateWhileNode.php +6 -4
  82. package/vendor/latte/latte/src/Latte/Essential/Nodes/JumpNode.php +26 -23
  83. package/vendor/latte/latte/src/Latte/Essential/Nodes/NElseNode.php +88 -0
  84. package/vendor/latte/latte/src/Latte/Essential/Nodes/NTagNode.php +20 -28
  85. package/vendor/latte/latte/src/Latte/Essential/Nodes/PrintNode.php +7 -12
  86. package/vendor/latte/latte/src/Latte/Essential/Nodes/RollbackNode.php +1 -1
  87. package/vendor/latte/latte/src/Latte/Essential/Nodes/SpacelessNode.php +1 -1
  88. package/vendor/latte/latte/src/Latte/Essential/Nodes/SwitchNode.php +1 -1
  89. package/vendor/latte/latte/src/Latte/Essential/Nodes/TranslateNode.php +1 -1
  90. package/vendor/latte/latte/src/Latte/Essential/Nodes/TryNode.php +3 -4
  91. package/vendor/latte/latte/src/Latte/Essential/Nodes/WhileNode.php +1 -1
  92. package/vendor/latte/latte/src/Latte/Essential/Passes.php +9 -11
  93. package/vendor/latte/latte/src/Latte/Essential/RawPhpExtension.php +0 -2
  94. package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +6 -1
  95. package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +0 -2
  96. package/vendor/latte/latte/src/Latte/Loaders/StringLoader.php +0 -2
  97. package/vendor/latte/latte/src/Latte/PositionAwareException.php +1 -1
  98. package/vendor/latte/latte/src/Latte/Runtime/Block.php +0 -4
  99. package/vendor/latte/latte/src/Latte/Runtime/FilterExecutor.php +43 -51
  100. package/vendor/latte/latte/src/Latte/Runtime/FilterInfo.php +0 -2
  101. package/vendor/latte/latte/src/Latte/Runtime/Filters.php +64 -30
  102. package/vendor/latte/latte/src/Latte/Runtime/Html.php +0 -4
  103. package/vendor/latte/latte/src/Latte/Runtime/Template.php +2 -2
  104. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +2 -1
  105. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/MethodCallNode.php +1 -1
  106. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/SandboxNode.php +3 -3
  107. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallNode.php → StaticMethodCallNode.php} +3 -3
  108. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallableNode.php → StaticMethodCallableNode.php} +2 -2
  109. package/vendor/latte/latte/src/Latte/Sandbox/RuntimeChecker.php +0 -2
  110. package/vendor/latte/latte/src/Latte/Sandbox/SandboxExtension.php +11 -9
  111. package/vendor/latte/latte/src/Latte/Sandbox/SecurityPolicy.php +0 -2
  112. package/vendor/latte/latte/src/Latte/exceptions.php +2 -11
  113. package/vendor/latte/latte/src/Tools/Linter.php +13 -37
  114. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/QuotedValue.php +0 -53
  115. 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\ExpressionNode;
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 ExpressionNode $name;
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
- $node = new static;
37
- $node->name = $tag->parser->parseExpression();
38
- $node->origName = $tag->htmlElement->name;
39
- $tag->htmlElement->customName = $node;
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
- return self::class . '::check('
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): string
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
- } elseif (
59
- !is_string($new)
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 $this->name;
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(['try'])) {
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
- ob_end_clean();
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\FunctionCallNode;
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 ExpressionBuilder::function(
93
- ExpressionBuilder::variable('$this')->property('global')->property('fn')->property($orig),
89
+ return new Expression\AuxiliaryNode(
90
+ fn(PrintContext $context, ...$args) => '($this->global->fn->' . $orig . ')(' . $context->implode($args) . ')',
94
91
  $node->args,
95
- )->build();
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
- (new NodeTraverser)->traverse($node, function (Node $node) {
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
  }
@@ -17,8 +17,6 @@ use Latte;
17
17
  */
18
18
  final class RawPhpExtension extends Latte\Extension
19
19
  {
20
- use Latte\Strict;
21
-
22
20
  public function getTags(): array
23
21
  {
24
22
  return [
@@ -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
- private /*?callable|Translator*/ $translator,
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
  }
@@ -17,8 +17,6 @@ use Latte;
17
17
  */
18
18
  class FileLoader implements Latte\Loader
19
19
  {
20
- use Latte\Strict;
21
-
22
20
  protected ?string $baseDir = null;
23
21
 
24
22
 
@@ -17,8 +17,6 @@ use Latte;
17
17
  */
18
18
  class StringLoader implements Latte\Loader
19
19
  {
20
- use Latte\Strict;
21
-
22
20
  /** @var string[]|null [name => content] */
23
21
  private ?array $templates = null;
24
22
 
@@ -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->toWords();
41
+ $info[] = $this->position;
42
42
  }
43
43
  $this->message = $info
44
44
  ? rtrim($this->origMessage, '.') . ' (' . implode(' ', $info) . ')'
@@ -9,14 +9,10 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Runtime;
11
11
 
12
- use Latte;
13
-
14
12
 
15
13
  /** @internal */
16
14
  final class Block
17
15
  {
18
- use Latte\Strict;
19
-
20
16
  public ?string $contentType = null;
21
17
 
22
18
  /** @var callable[] */
@@ -59,39 +59,10 @@ class FilterExecutor
59
59
  */
60
60
  public function __get(string $name): callable
61
61
  {
62
- if (isset($this->_static[$name])) {
63
- [$callback, $aware] = $this->prepareFilter($name);
64
- if ($aware) { // FilterInfo aware filter
65
- return $this->$name = function (...$args) use ($callback) {
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
- if ($aware) { // FilterInfo aware filter
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 = ($this->$name)(...$args);
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 (!isset($this->_static[$name][1])) {
144
- $params = Helpers::toReflection($this->_static[$name][0])->getParameters();
145
- $this->_static[$name][1] = $params
146
- && $params[0]->getType() instanceof \ReflectionNamedType
147
- && $params[0]->getType()->getName() === FilterInfo::class;
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
- return $this->_static[$name];
139
+ $res = $filter(...$args);
140
+ return $info->contentType === ContentType::Html
141
+ ? new Html($res)
142
+ : $res;
151
143
  }
152
144
  }
@@ -17,8 +17,6 @@ use Latte;
17
17
  */
18
18
  class FilterInfo
19
19
  {
20
- use Latte\Strict;
21
-
22
20
  public ?string $contentType = null;
23
21
 
24
22
 
@@ -75,9 +75,12 @@ class Filters
75
75
  public static function escapeHtmlTag($s): string
76
76
  {
77
77
  $s = (string) $s;
78
- return preg_match('#^[a-z0-9:-]+$#i', $s)
79
- ? $s
80
- : '"' . self::escapeHtmlAttr($s) . '"';
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, ['"' => '&quot;', "'" => '&apos;']);
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 preg_match('#^[a-z0-9:-]+$#i', $s)
127
- ? $s
128
- : '"' . self::escapeXml($s) . '"';
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(), $error);
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 HTML text to quoted attribute. The quotation marks need to be escaped.
223
+ * Converts JS and CSS for usage in <script> or <style>
206
224
  */
207
- public static function convertHtmlToHtmlAttr(string $s): string
225
+ public static function convertJSToHtmlRawText($s): string
208
226
  {
209
- return self::escapeHtmlAttr($s, false);
227
+ return preg_replace('#</(script|style)#i', '<\/$1', (string) $s);
210
228
  }
211
229
 
212
230
 
213
231
  /**
214
- * Converts HTML text to unquoted attribute. The quotation marks need to be escaped.
232
+ * Sanitizes <script> in <script type=text/html>
215
233
  */
216
- public static function convertHtmlToUnquotedAttr(string $s): string
234
+ public static function convertHtmlToHtmlRawText(string $s): string
217
235
  {
218
- return '"' . self::escapeHtmlAttr($s, false) . '"';
236
+ return preg_replace('#(</?)(script)#i', '$1x-$2', $s);
219
237
  }
220
238
 
221
239
 
222
240
  /**
223
- * Converts HTML quoted attribute to unquoted.
241
+ * Converts HTML text to quoted attribute. The quotation marks need to be escaped.
224
242
  */
225
- public static function convertHtmlAttrToUnquotedAttr(string $s): string
243
+ public static function convertHtmlToHtmlAttr(string $s): string
226
244
  {
227
- return '"' . $s . '"';
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|HtmlStringable $s): string
262
+ public static function safeUrl(string|\Stringable $s): string
245
263
  {
246
- if ($s instanceof HtmlStringable) {
247
- $s = self::convertHtmlToText((string) $s);
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
  }
@@ -9,16 +9,12 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Runtime;
11
11
 
12
- use Latte;
13
-
14
12
 
15
13
  /**
16
14
  * HTML literal.
17
15
  */
18
16
  class Html implements HtmlStringable
19
17
  {
20
- use Latte\Strict;
21
-
22
18
  private string $value;
23
19
 
24
20