@vituum/vite-plugin-latte 1.0.0 → 1.2.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 (123) hide show
  1. package/index.js +2 -2
  2. package/latte/PlaceholderFunction.php +2 -2
  3. package/package.json +10 -11
  4. package/vendor/autoload.php +18 -0
  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 +14 -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 +3 -22
  12. package/vendor/composer/autoload_static.php +13 -7
  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 +113 -89
  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/FilterNode.php +1 -1
  47. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListItemNode.php +48 -0
  48. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ListNode.php +56 -0
  49. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/ModifierNode.php +5 -5
  50. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/NameNode.php +11 -21
  51. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Php/Scalar/InterpolatedStringNode.php +1 -8
  52. package/vendor/latte/latte/src/Latte/Compiler/PhpHelpers.php +30 -0
  53. package/vendor/latte/latte/src/Latte/Compiler/Position.php +4 -8
  54. package/vendor/latte/latte/src/Latte/Compiler/PrintContext.php +15 -8
  55. package/vendor/latte/latte/src/Latte/Compiler/Tag.php +13 -14
  56. package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +5 -9
  57. package/vendor/latte/latte/src/Latte/Compiler/TagParser.php +52 -3
  58. package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +353 -326
  59. package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +6 -5
  60. package/vendor/latte/latte/src/Latte/Compiler/TemplateLexer.php +105 -178
  61. package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +40 -33
  62. package/vendor/latte/latte/src/Latte/Compiler/TemplateParserHtml.php +186 -126
  63. package/vendor/latte/latte/src/Latte/Compiler/Token.php +5 -9
  64. package/vendor/latte/latte/src/Latte/Compiler/TokenStream.php +6 -22
  65. package/vendor/latte/latte/src/Latte/Engine.php +136 -95
  66. package/vendor/latte/latte/src/Latte/Essential/AuxiliaryIterator.php +46 -0
  67. package/vendor/latte/latte/src/Latte/Essential/Blueprint.php +42 -27
  68. package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +0 -4
  69. package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +81 -66
  70. package/vendor/latte/latte/src/Latte/Essential/Filters.php +103 -43
  71. package/vendor/latte/latte/src/Latte/Essential/Nodes/BlockNode.php +1 -2
  72. package/vendor/latte/latte/src/Latte/Essential/Nodes/CaptureNode.php +2 -13
  73. package/vendor/latte/latte/src/Latte/Essential/Nodes/ContentTypeNode.php +8 -1
  74. package/vendor/latte/latte/src/Latte/Essential/Nodes/DefineNode.php +1 -2
  75. package/vendor/latte/latte/src/Latte/Essential/Nodes/EmbedNode.php +1 -1
  76. package/vendor/latte/latte/src/Latte/Essential/Nodes/ExtendsNode.php +0 -3
  77. package/vendor/latte/latte/src/Latte/Essential/Nodes/FirstLastSepNode.php +1 -1
  78. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForNode.php +1 -1
  79. package/vendor/latte/latte/src/Latte/Essential/Nodes/ForeachNode.php +40 -13
  80. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfChangedNode.php +1 -1
  81. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfContentNode.php +4 -1
  82. package/vendor/latte/latte/src/Latte/Essential/Nodes/IfNode.php +5 -3
  83. package/vendor/latte/latte/src/Latte/Essential/Nodes/IncludeBlockNode.php +5 -2
  84. package/vendor/latte/latte/src/Latte/Essential/Nodes/IterateWhileNode.php +6 -4
  85. package/vendor/latte/latte/src/Latte/Essential/Nodes/JumpNode.php +26 -23
  86. package/vendor/latte/latte/src/Latte/Essential/Nodes/NElseNode.php +88 -0
  87. package/vendor/latte/latte/src/Latte/Essential/Nodes/NTagNode.php +20 -28
  88. package/vendor/latte/latte/src/Latte/Essential/Nodes/PrintNode.php +7 -12
  89. package/vendor/latte/latte/src/Latte/Essential/Nodes/RollbackNode.php +1 -1
  90. package/vendor/latte/latte/src/Latte/Essential/Nodes/SpacelessNode.php +1 -1
  91. package/vendor/latte/latte/src/Latte/Essential/Nodes/SwitchNode.php +1 -1
  92. package/vendor/latte/latte/src/Latte/Essential/Nodes/TemplatePrintNode.php +25 -3
  93. package/vendor/latte/latte/src/Latte/Essential/Nodes/TranslateNode.php +1 -1
  94. package/vendor/latte/latte/src/Latte/Essential/Nodes/TryNode.php +3 -4
  95. package/vendor/latte/latte/src/Latte/Essential/Nodes/VarPrintNode.php +9 -2
  96. package/vendor/latte/latte/src/Latte/Essential/Nodes/WhileNode.php +1 -1
  97. package/vendor/latte/latte/src/Latte/Essential/Passes.php +16 -58
  98. package/vendor/latte/latte/src/Latte/Essential/RawPhpExtension.php +0 -2
  99. package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +6 -1
  100. package/vendor/latte/latte/src/Latte/Helpers.php +3 -1
  101. package/vendor/latte/latte/src/Latte/Loader.php +1 -0
  102. package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +1 -4
  103. package/vendor/latte/latte/src/Latte/Loaders/StringLoader.php +0 -2
  104. package/vendor/latte/latte/src/Latte/PositionAwareException.php +1 -1
  105. package/vendor/latte/latte/src/Latte/Runtime/Block.php +0 -4
  106. package/vendor/latte/latte/src/Latte/Runtime/FilterExecutor.php +43 -51
  107. package/vendor/latte/latte/src/Latte/Runtime/FilterInfo.php +0 -2
  108. package/vendor/latte/latte/src/Latte/Runtime/Filters.php +64 -33
  109. package/vendor/latte/latte/src/Latte/Runtime/FunctionExecutor.php +68 -0
  110. package/vendor/latte/latte/src/Latte/Runtime/Html.php +0 -4
  111. package/vendor/latte/latte/src/Latte/Runtime/Template.php +3 -5
  112. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +2 -1
  113. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/MethodCallNode.php +1 -1
  114. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/SandboxNode.php +3 -3
  115. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallNode.php → StaticMethodCallNode.php} +3 -3
  116. package/vendor/latte/latte/src/Latte/Sandbox/Nodes/{StaticCallableNode.php → StaticMethodCallableNode.php} +2 -2
  117. package/vendor/latte/latte/src/Latte/Sandbox/RuntimeChecker.php +0 -2
  118. package/vendor/latte/latte/src/Latte/Sandbox/SandboxExtension.php +11 -9
  119. package/vendor/latte/latte/src/Latte/Sandbox/SecurityPolicy.php +0 -2
  120. package/vendor/latte/latte/src/Latte/exceptions.php +2 -11
  121. package/vendor/latte/latte/src/Tools/Linter.php +13 -37
  122. package/vendor/latte/latte/src/Latte/Compiler/Nodes/Html/QuotedValue.php +0 -53
  123. package/vendor/latte/latte/src/Latte/Strict.php +0 -101
@@ -9,79 +9,36 @@ declare(strict_types=1);
9
9
 
10
10
  namespace Latte\Essential;
11
11
 
12
- use Latte;
13
12
  use Latte\CompileException;
14
- use Latte\Compiler\ExpressionBuilder;
15
13
  use Latte\Compiler\Node;
16
- use Latte\Compiler\Nodes\AuxiliaryNode;
17
- use Latte\Compiler\Nodes\Php\Expression\FunctionCallNode;
14
+ use Latte\Compiler\Nodes\Php\Expression;
18
15
  use Latte\Compiler\Nodes\Php\Expression\VariableNode;
19
16
  use Latte\Compiler\Nodes\Php\NameNode;
20
17
  use Latte\Compiler\Nodes\TemplateNode;
21
18
  use Latte\Compiler\NodeTraverser;
22
19
  use Latte\Compiler\PrintContext;
23
- use Latte\Essential\Nodes\ForeachNode;
20
+ use Latte\Engine;
24
21
 
25
22
 
26
23
  final class Passes
27
24
  {
28
- use Latte\Strict;
29
-
30
- /**
31
- * Checks if foreach overrides template variables.
32
- */
33
- public static function overwrittenVariablesPass(TemplateNode $node): void
34
- {
35
- $vars = [];
36
- (new NodeTraverser)->traverse($node, function (Node $node) use (&$vars) {
37
- if ($node instanceof ForeachNode && $node->checkArgs) {
38
- foreach ([$node->key, $node->value] as $var) {
39
- if ($var instanceof VariableNode) {
40
- $vars[$var->name][] = $node->position->line;
41
- }
42
- }
43
- }
44
- });
45
- if ($vars) {
46
- array_unshift($node->head->children, new AuxiliaryNode(fn(PrintContext $context) => $context->format(
47
- <<<'XX'
48
- if (!$this->getReferringTemplate() || $this->getReferenceType() === 'extends') {
49
- foreach (array_intersect_key(%dump, $this->params) as $ʟ_v => $ʟ_l) {
50
- trigger_error("Variable \$$ʟ_v overwritten in foreach on line $ʟ_l");
51
- }
52
- }
53
-
54
- XX,
55
- array_map(fn($l) => implode(', ', $l), $vars),
56
- )));
57
- }
58
- }
59
-
60
-
61
- /**
62
- * Move TemplatePrintNode to head.
63
- */
64
- public static function moveTemplatePrintToHeadPass(TemplateNode $templateNode): void
65
- {
66
- (new NodeTraverser)->traverse($templateNode->main, function (Node $node) use ($templateNode) {
67
- if ($node instanceof Latte\Essential\Nodes\TemplatePrintNode) {
68
- array_unshift($templateNode->head->children, $node);
69
- return new Latte\Compiler\Nodes\NopNode;
70
- }
71
- });
25
+ public function __construct(
26
+ private Engine $engine,
27
+ ) {
72
28
  }
73
29
 
74
30
 
75
31
  /**
76
32
  * Enable custom functions.
77
33
  */
78
- public static function customFunctionsPass(TemplateNode $node, array $functions): void
34
+ public function customFunctionsPass(TemplateNode $node): void
79
35
  {
36
+ $functions = $this->engine->getFunctions();
80
37
  $names = array_keys($functions);
81
38
  $names = array_combine(array_map('strtolower', $names), $names);
82
39
 
83
40
  (new NodeTraverser)->traverse($node, function (Node $node) use ($names) {
84
- if (($node instanceof FunctionCallNode || $node instanceof FunctionCallableNode)
41
+ if (($node instanceof Expression\FunctionCallNode || $node instanceof Expression\FunctionCallableNode)
85
42
  && $node->name instanceof NameNode
86
43
  && ($orig = $names[strtolower((string) $node->name)] ?? null)
87
44
  ) {
@@ -89,24 +46,25 @@ final class Passes
89
46
  trigger_error("Case mismatch on function name '{$node->name}', correct name is '$orig'.", E_USER_WARNING);
90
47
  }
91
48
 
92
- return ExpressionBuilder::function(
93
- ExpressionBuilder::variable('$this')->property('global')->property('fn')->property($orig),
49
+ return new Expression\AuxiliaryNode(
50
+ fn(PrintContext $context, ...$args) => '($this->global->fn->' . $orig . ')($this, ' . $context->implode($args) . ')',
94
51
  $node->args,
95
- )->build();
52
+ );
96
53
  }
97
54
  });
98
55
  }
99
56
 
100
57
 
101
58
  /**
102
- * $ʟ_xxx variables are forbidden
59
+ * $ʟ_xxx, $GLOBALS and $this are forbidden
103
60
  */
104
- public static function internalVariablesPass(TemplateNode $node): void
61
+ public function forbiddenVariablesPass(TemplateNode $node): void
105
62
  {
106
- (new NodeTraverser)->traverse($node, function (Node $node) {
63
+ $forbidden = $this->engine->isStrictParsing() ? ['GLOBALS', 'this'] : ['GLOBALS'];
64
+ (new NodeTraverser)->traverse($node, function (Node $node) use ($forbidden) {
107
65
  if ($node instanceof VariableNode
108
66
  && is_string($node->name)
109
- && (str_starts_with($node->name, 'ʟ_'))
67
+ && (str_starts_with($node->name, 'ʟ_') || in_array($node->name, $forbidden, true))
110
68
  ) {
111
69
  throw new CompileException("Forbidden variable \$$node->name.", $node->position);
112
70
  }
@@ -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
  }
@@ -46,7 +46,9 @@ class Helpers
46
46
  public static function toReflection($callable): \ReflectionFunctionAbstract
47
47
  {
48
48
  if (is_string($callable) && strpos($callable, '::')) {
49
- return new \ReflectionMethod($callable);
49
+ return PHP_VERSION_ID < 80300
50
+ ? new \ReflectionMethod($callable)
51
+ : \ReflectionMethod::createFromMethodName($callable);
50
52
  } elseif (is_array($callable)) {
51
53
  return new \ReflectionMethod($callable[0], $callable[1]);
52
54
  } elseif (is_object($callable) && !$callable instanceof \Closure) {
@@ -22,6 +22,7 @@ interface Loader
22
22
 
23
23
  /**
24
24
  * Checks whether template is expired.
25
+ * @deprecated
25
26
  */
26
27
  function isExpired(string $name, int $time): bool;
27
28
 
@@ -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
 
@@ -52,8 +50,7 @@ class FileLoader implements Latte\Loader
52
50
 
53
51
  public function isExpired(string $file, int $time): bool
54
52
  {
55
- $mtime = @filemtime($this->baseDir . $file); // @ - stat may fail
56
- return !$mtime || $mtime > $time;
53
+ return false;
57
54
  }
58
55
 
59
56
 
@@ -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
 
@@ -24,9 +24,6 @@ class Filters
24
24
  /** @deprecated */
25
25
  public static string $dateFormat = "j.\u{a0}n.\u{a0}Y";
26
26
 
27
- /** @internal use XML syntax? */
28
- public static bool $xml = false;
29
-
30
27
 
31
28
  /**
32
29
  * Escapes string for use everywhere inside HTML (except for comments).
@@ -75,9 +72,12 @@ class Filters
75
72
  public static function escapeHtmlTag($s): string
76
73
  {
77
74
  $s = (string) $s;
78
- return preg_match('#^[a-z0-9:-]+$#i', $s)
79
- ? $s
80
- : '"' . self::escapeHtmlAttr($s) . '"';
75
+ $s = htmlspecialchars($s, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8');
76
+ return preg_replace_callback(
77
+ '#[=/\s]#',
78
+ fn($m) => '&#' . ord($m[0]) . ';',
79
+ $s,
80
+ );
81
81
  }
82
82
 
83
83
 
@@ -100,6 +100,28 @@ class Filters
100
100
  }
101
101
 
102
102
 
103
+ /**
104
+ * Escapes HTML for usage in <script type=text/html>
105
+ */
106
+ public static function escapeHtmlRawTextHtml($s): string
107
+ {
108
+ if ($s instanceof HtmlStringable || $s instanceof Nette\HtmlStringable) {
109
+ return self::convertHtmlToHtmlRawText($s->__toString());
110
+ }
111
+
112
+ return htmlspecialchars((string) $s, ENT_QUOTES | ENT_HTML5 | ENT_SUBSTITUTE, 'UTF-8');
113
+ }
114
+
115
+
116
+ /**
117
+ * Escapes only quotes.
118
+ */
119
+ public static function escapeHtmlQuotes($s): string
120
+ {
121
+ return strtr((string) $s, ['"' => '&quot;', "'" => '&apos;']);
122
+ }
123
+
124
+
103
125
  /**
104
126
  * Escapes string for use everywhere inside XML (except for comments and tags).
105
127
  */
@@ -122,10 +144,12 @@ class Filters
122
144
  */
123
145
  public static function escapeXmlTag($s): string
124
146
  {
125
- $s = (string) $s;
126
- return preg_match('#^[a-z0-9:-]+$#i', $s)
127
- ? $s
128
- : '"' . self::escapeXml($s) . '"';
147
+ $s = self::escapeXml((string) $s);
148
+ return preg_replace_callback(
149
+ '#[=/\s]#',
150
+ fn($m) => '&#' . ord($m[0]) . ';',
151
+ $s,
152
+ );
129
153
  }
130
154
 
131
155
 
@@ -150,7 +174,7 @@ class Filters
150
174
 
151
175
  $json = json_encode($s, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
152
176
  if ($error = json_last_error()) {
153
- throw new Latte\RuntimeException(json_last_error_msg(), $error);
177
+ throw new Latte\RuntimeException(json_last_error_msg());
154
178
  }
155
179
 
156
180
  return str_replace([']]>', '<!', '</'], [']]\u003E', '\u003C!', '<\/'], $json);
@@ -169,15 +193,6 @@ class Filters
169
193
  }
170
194
 
171
195
 
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
196
  /**
182
197
  * Converts ... to ...
183
198
  */
@@ -202,29 +217,29 @@ class Filters
202
217
 
203
218
 
204
219
  /**
205
- * Converts HTML text to quoted attribute. The quotation marks need to be escaped.
220
+ * Converts JS and CSS for usage in <script> or <style>
206
221
  */
207
- public static function convertHtmlToHtmlAttr(string $s): string
222
+ public static function convertJSToHtmlRawText($s): string
208
223
  {
209
- return self::escapeHtmlAttr($s, false);
224
+ return preg_replace('#</(script|style)#i', '<\/$1', (string) $s);
210
225
  }
211
226
 
212
227
 
213
228
  /**
214
- * Converts HTML text to unquoted attribute. The quotation marks need to be escaped.
229
+ * Sanitizes <script> in <script type=text/html>
215
230
  */
216
- public static function convertHtmlToUnquotedAttr(string $s): string
231
+ public static function convertHtmlToHtmlRawText(string $s): string
217
232
  {
218
- return '"' . self::escapeHtmlAttr($s, false) . '"';
233
+ return preg_replace('#(</?)(script)#i', '$1x-$2', $s);
219
234
  }
220
235
 
221
236
 
222
237
  /**
223
- * Converts HTML quoted attribute to unquoted.
238
+ * Converts HTML text to quoted attribute. The quotation marks need to be escaped.
224
239
  */
225
- public static function convertHtmlAttrToUnquotedAttr(string $s): string
240
+ public static function convertHtmlToHtmlAttr(string $s): string
226
241
  {
227
- return '"' . $s . '"';
242
+ return self::escapeHtmlAttr($s, false);
228
243
  }
229
244
 
230
245
 
@@ -241,12 +256,28 @@ class Filters
241
256
  /**
242
257
  * Sanitizes string for use inside href attribute.
243
258
  */
244
- public static function safeUrl(string|HtmlStringable $s): string
259
+ public static function safeUrl($s): string
245
260
  {
246
- if ($s instanceof HtmlStringable) {
247
- $s = self::convertHtmlToText((string) $s);
248
- }
261
+ $s = $s instanceof HtmlStringable
262
+ ? self::convertHtmlToText((string) $s)
263
+ : (string) $s;
249
264
 
250
265
  return preg_match('~^(?:(?:https?|ftp)://[^@]+(?:/.*)?|(?:mailto|tel|sms):.+|[/?#].*|[^:]+)$~Di', $s) ? $s : '';
251
266
  }
267
+
268
+
269
+ /**
270
+ * Validates HTML tag name.
271
+ */
272
+ public static function safeTag(mixed $name, bool $xml = false): string
273
+ {
274
+ if (!is_string($name)) {
275
+ throw new Latte\RuntimeException('Tag name must be string, ' . get_debug_type($name) . ' given');
276
+ } elseif (!preg_match('~' . Latte\Compiler\TemplateLexer::ReTagName . '$~DA', $name)) {
277
+ throw new Latte\RuntimeException("Invalid tag name '$name'");
278
+ } elseif (!$xml && in_array(strtolower($name), ['style', 'script'], true)) {
279
+ throw new Latte\RuntimeException("Forbidden variable tag name <$name>");
280
+ }
281
+ return $name;
282
+ }
252
283
  }
@@ -0,0 +1,68 @@
1
+ <?php
2
+
3
+ /**
4
+ * This file is part of the Latte (https://latte.nette.org)
5
+ * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
6
+ */
7
+
8
+ declare(strict_types=1);
9
+
10
+ namespace Latte\Runtime;
11
+
12
+ use Latte\Helpers;
13
+
14
+
15
+ /**
16
+ * Functions executor.
17
+ * @internal
18
+ */
19
+ #[\AllowDynamicProperties]
20
+ class FunctionExecutor
21
+ {
22
+ /** @var callable[] */
23
+ private array $_list = [];
24
+
25
+ /** @var bool[] */
26
+ private array $_aware = [];
27
+
28
+
29
+ /**
30
+ * Registers run-time function.
31
+ */
32
+ public function add(string $name, callable $callback): static
33
+ {
34
+ $this->_list[$name] = $callback;
35
+ unset($this->$name, $this->_aware[$name]);
36
+ return $this;
37
+ }
38
+
39
+
40
+ /**
41
+ * Returns all run-time functions.
42
+ * @return callable[]
43
+ */
44
+ public function getAll(): array
45
+ {
46
+ return $this->_list;
47
+ }
48
+
49
+
50
+ public function __get(string $name): callable
51
+ {
52
+ $callback = $this->_list[$name] ?? null;
53
+ if (!$callback) {
54
+ $hint = ($t = Helpers::getSuggestion(array_keys($this->_list), $name))
55
+ ? ", did you mean '$t'?"
56
+ : '.';
57
+ throw new \LogicException("Function '$name' is not defined$hint");
58
+
59
+ } elseif (!isset($this->_aware[$name])) {
60
+ $params = Helpers::toReflection($callback)->getParameters();
61
+ $this->_aware[$name] = $params && (string) $params[0]->getType() === Template::class;
62
+ }
63
+
64
+ return $this->$name = $this->_aware[$name]
65
+ ? $callback
66
+ : fn($info, ...$args) => $callback(...$args);
67
+ }
68
+ }
@@ -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
 
@@ -19,8 +19,6 @@ use Latte\Engine;
19
19
  */
20
20
  class Template
21
21
  {
22
- use Latte\Strict;
23
-
24
22
  public const
25
23
  LayerTop = 0,
26
24
  LayerSnippet = 'snippet',
@@ -28,6 +26,8 @@ class Template
28
26
 
29
27
  public const ContentType = Latte\ContentType::Html;
30
28
 
29
+ public const Source = null;
30
+
31
31
  public const Blocks = [];
32
32
 
33
33
  /** global accumulators for intermediate results */
@@ -142,8 +142,6 @@ class Template
142
142
  $this->parentName = ($this->global->coreParentFinder)($this);
143
143
  }
144
144
 
145
- Filters::$xml = static::ContentType === Latte\ContentType::Xml;
146
-
147
145
  if ($this->referenceType === 'import') {
148
146
  if ($this->parentName) {
149
147
  throw new Latte\RuntimeException('Imported template cannot use {extends} or {layout}, use {import}');
@@ -172,7 +170,7 @@ class Template
172
170
  $name = $this->engine->getLoader()->getReferredName($name, $this->name);
173
171
  $referred = $referenceType === 'sandbox'
174
172
  ? (clone $this->engine)->setSandboxMode()->createTemplate($name, $params)
175
- : $this->engine->createTemplate($name, $params);
173
+ : $this->engine->createTemplate($name, $params, clearCache: false);
176
174
 
177
175
  $referred->referringTemplate = $this;
178
176
  $referred->referenceType = $referenceType;
@@ -25,6 +25,7 @@ class FunctionCallNode extends Expression\FunctionCallNode
25
25
  {
26
26
  return '$this->global->sandbox->call('
27
27
  . $context->memberAsString($this->name) . ', '
28
- . Expression\ArrayNode::fromArguments($this->args)->print($context) . ')';
28
+ . $context->argumentsAsArray($this->args) . ')';
29
+
29
30
  }
30
31
  }
@@ -26,7 +26,7 @@ class MethodCallNode extends Expression\MethodCallNode
26
26
  return '$this->global->sandbox->callMethod('
27
27
  . $this->object->print($context) . ', '
28
28
  . $context->memberAsString($this->name) . ', '
29
- . Expression\ArrayNode::fromArguments($this->args)->print($context)
29
+ . $context->argumentsAsArray($this->args)
30
30
  . ', ' . var_export($this->nullsafe, true) . ')';
31
31
  }
32
32
  }