first commit

This commit is contained in:
2025-07-18 16:20:14 +07:00
commit 98af45c018
16382 changed files with 3148096 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\NodeVisitor;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
class Scope
{
private ?self $parent;
private array $data = [];
private bool $left = false;
public function __construct(?self $parent = null)
{
$this->parent = $parent;
}
/**
* Opens a new child scope.
*/
public function enter(): self
{
return new self($this);
}
/**
* Closes current scope and returns parent one.
*/
public function leave(): ?self
{
$this->left = true;
return $this->parent;
}
/**
* Stores data into current scope.
*
* @return $this
*
* @throws \LogicException
*/
public function set(string $key, mixed $value): static
{
if ($this->left) {
throw new \LogicException('Left scope is not mutable.');
}
$this->data[$key] = $value;
return $this;
}
/**
* Tests if a data is visible from current scope.
*/
public function has(string $key): bool
{
if (\array_key_exists($key, $this->data)) {
return true;
}
if (null === $this->parent) {
return false;
}
return $this->parent->has($key);
}
/**
* Returns data visible from current scope.
*/
public function get(string $key, mixed $default = null): mixed
{
if (\array_key_exists($key, $this->data)) {
return $this->data[$key];
}
if (null === $this->parent) {
return $default;
}
return $this->parent->get($key, $default);
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\NodeVisitor;
use Symfony\Bridge\Twig\Node\TransDefaultDomainNode;
use Symfony\Bridge\Twig\Node\TransNode;
use Twig\Environment;
use Twig\Node\BlockNode;
use Twig\Node\EmptyNode;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\AssignNameExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\Variable\AssignContextVariable;
use Twig\Node\Expression\Variable\ContextVariable;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\Node\Nodes;
use Twig\Node\SetNode;
use Twig\NodeVisitor\NodeVisitorInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class TranslationDefaultDomainNodeVisitor implements NodeVisitorInterface
{
private Scope $scope;
public function __construct()
{
$this->scope = new Scope();
}
public function enterNode(Node $node, Environment $env): Node
{
if ($node instanceof BlockNode || $node instanceof ModuleNode) {
$this->scope = $this->scope->enter();
}
if ($node instanceof TransDefaultDomainNode) {
if ($node->getNode('expr') instanceof ConstantExpression) {
$this->scope->set('domain', $node->getNode('expr'));
return $node;
} else {
$var = $this->getVarName();
$name = class_exists(AssignContextVariable::class) ? new AssignContextVariable($var, $node->getTemplateLine()) : new AssignNameExpression($var, $node->getTemplateLine());
$this->scope->set('domain', class_exists(ContextVariable::class) ? new ContextVariable($var, $node->getTemplateLine()) : new NameExpression($var, $node->getTemplateLine()));
if (class_exists(Nodes::class)) {
return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine());
} else {
return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine());
}
}
}
if (!$this->scope->has('domain')) {
return $node;
}
if ($node instanceof FilterExpression && 'trans' === ($node->hasAttribute('twig_callable') ? $node->getAttribute('twig_callable')->getName() : $node->getNode('filter')->getAttribute('value'))) {
$arguments = $node->getNode('arguments');
if ($arguments instanceof EmptyNode) {
$arguments = new Nodes();
$node->setNode('arguments', $arguments);
}
if ($this->isNamedArguments($arguments)) {
if (!$arguments->hasNode('domain') && !$arguments->hasNode(1)) {
$arguments->setNode('domain', $this->scope->get('domain'));
}
} elseif (!$arguments->hasNode(1)) {
if (!$arguments->hasNode(0)) {
$arguments->setNode(0, new ArrayExpression([], $node->getTemplateLine()));
}
$arguments->setNode(1, $this->scope->get('domain'));
}
} elseif ($node instanceof TransNode) {
if (!$node->hasNode('domain')) {
$node->setNode('domain', $this->scope->get('domain'));
}
}
return $node;
}
public function leaveNode(Node $node, Environment $env): ?Node
{
if ($node instanceof TransDefaultDomainNode) {
return null;
}
if ($node instanceof BlockNode || $node instanceof ModuleNode) {
$this->scope = $this->scope->leave();
}
return $node;
}
public function getPriority(): int
{
return -10;
}
private function isNamedArguments(Node $arguments): bool
{
foreach ($arguments as $name => $node) {
if (!\is_int($name)) {
return true;
}
}
return false;
}
private function getVarName(): string
{
return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false));
}
}

View File

@@ -0,0 +1,194 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\NodeVisitor;
use Symfony\Bridge\Twig\Node\TransNode;
use Twig\Environment;
use Twig\Node\Expression\Binary\ConcatBinary;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
/**
* TranslationNodeVisitor extracts translation messages.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class TranslationNodeVisitor implements NodeVisitorInterface
{
public const UNDEFINED_DOMAIN = '_undefined';
private bool $enabled = false;
private array $messages = [];
public function enable(): void
{
$this->enabled = true;
$this->messages = [];
}
public function disable(): void
{
$this->enabled = false;
$this->messages = [];
}
public function getMessages(): array
{
return $this->messages;
}
public function enterNode(Node $node, Environment $env): Node
{
if (!$this->enabled) {
return $node;
}
if (
$node instanceof FilterExpression
&& 'trans' === ($node->hasAttribute('twig_callable') ? $node->getAttribute('twig_callable')->getName() : $node->getNode('filter')->getAttribute('value'))
&& $node->getNode('node') instanceof ConstantExpression
) {
// extract constant nodes with a trans filter
$this->messages[] = [
$node->getNode('node')->getAttribute('value'),
$this->getReadDomainFromArguments($node->getNode('arguments'), 1),
];
} elseif (
$node instanceof FunctionExpression
&& 't' === $node->getAttribute('name')
) {
$nodeArguments = $node->getNode('arguments');
if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) {
$this->messages[] = [
$this->getReadMessageFromArguments($nodeArguments, 0),
$this->getReadDomainFromArguments($nodeArguments, 2),
];
}
} elseif ($node instanceof TransNode) {
// extract trans nodes
$this->messages[] = [
$node->getNode('body')->getAttribute('data'),
$node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null,
];
} elseif (
$node instanceof FilterExpression
&& 'trans' === ($node->hasAttribute('twig_callable') ? $node->getAttribute('twig_callable')->getName() : $node->getNode('filter')->getAttribute('value'))
&& $node->getNode('node') instanceof ConcatBinary
&& $message = $this->getConcatValueFromNode($node->getNode('node'), null)
) {
$this->messages[] = [
$message,
$this->getReadDomainFromArguments($node->getNode('arguments'), 1),
];
}
return $node;
}
public function leaveNode(Node $node, Environment $env): ?Node
{
return $node;
}
public function getPriority(): int
{
return 0;
}
private function getReadMessageFromArguments(Node $arguments, int $index): ?string
{
if ($arguments->hasNode('message')) {
$argument = $arguments->getNode('message');
} elseif ($arguments->hasNode($index)) {
$argument = $arguments->getNode($index);
} else {
return null;
}
return $this->getReadMessageFromNode($argument);
}
private function getReadMessageFromNode(Node $node): ?string
{
if ($node instanceof ConstantExpression) {
return $node->getAttribute('value');
}
return null;
}
private function getReadDomainFromArguments(Node $arguments, int $index): ?string
{
if ($arguments->hasNode('domain')) {
$argument = $arguments->getNode('domain');
} elseif ($arguments->hasNode($index)) {
$argument = $arguments->getNode($index);
} else {
return null;
}
return $this->getReadDomainFromNode($argument);
}
private function getReadDomainFromNode(Node $node): ?string
{
if ($node instanceof ConstantExpression) {
return $node->getAttribute('value');
}
if (
$node instanceof FunctionExpression
&& 'constant' === $node->getAttribute('name')
) {
$nodeArguments = $node->getNode('arguments');
if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) {
$constantName = $nodeArguments->getIterator()->current()->getAttribute('value');
if (\defined($constantName)) {
$value = \constant($constantName);
if (\is_string($value)) {
return $value;
}
}
}
}
return self::UNDEFINED_DOMAIN;
}
private function getConcatValueFromNode(Node $node, ?string $value): ?string
{
if ($node instanceof ConcatBinary) {
foreach ($node as $nextNode) {
if ($nextNode instanceof ConcatBinary) {
$nextValue = $this->getConcatValueFromNode($nextNode, $value);
if (null === $nextValue) {
return null;
}
$value .= $nextValue;
} elseif ($nextNode instanceof ConstantExpression) {
$value .= $nextNode->getAttribute('value');
} else {
// this is a node we cannot process (variable, or translation in translation)
return null;
}
}
} elseif ($node instanceof ConstantExpression) {
$value .= $node->getAttribute('value');
}
return $value;
}
}