mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-25 03:28:45 +02:00
N°6934 - Symfony 6.4 - upgrade Symfony bundles to 6.4 (#580)
* Update Symfony lib to version ~6.4.0 * Update code missing return type * Add an iTop general configuration entry to store application secret (Symfony mandatory parameter) * Use dependency injection in ExceptionListener & UserProvider classes
This commit is contained in:
@@ -13,10 +13,11 @@ namespace Symfony\Bridge\Twig;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Translation\LocaleSwitcher;
|
||||
|
||||
/**
|
||||
* Exposes some Symfony parameters and services as an "app" global variable.
|
||||
@@ -25,78 +26,89 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
||||
*/
|
||||
class AppVariable
|
||||
{
|
||||
private $tokenStorage;
|
||||
private $requestStack;
|
||||
private $environment;
|
||||
private $debug;
|
||||
private TokenStorageInterface $tokenStorage;
|
||||
private RequestStack $requestStack;
|
||||
private string $environment;
|
||||
private bool $debug;
|
||||
private LocaleSwitcher $localeSwitcher;
|
||||
private array $enabledLocales;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setTokenStorage(TokenStorageInterface $tokenStorage)
|
||||
{
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setRequestStack(RequestStack $requestStack)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setEnvironment(string $environment)
|
||||
{
|
||||
$this->environment = $environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setDebug(bool $debug)
|
||||
{
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
public function setLocaleSwitcher(LocaleSwitcher $localeSwitcher): void
|
||||
{
|
||||
$this->localeSwitcher = $localeSwitcher;
|
||||
}
|
||||
|
||||
public function setEnabledLocales(array $enabledLocales): void
|
||||
{
|
||||
$this->enabledLocales = $enabledLocales;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current token.
|
||||
*
|
||||
* @return TokenInterface|null
|
||||
*
|
||||
* @throws \RuntimeException When the TokenStorage is not available
|
||||
*/
|
||||
public function getToken()
|
||||
public function getToken(): ?TokenInterface
|
||||
{
|
||||
if (null === $tokenStorage = $this->tokenStorage) {
|
||||
if (!isset($this->tokenStorage)) {
|
||||
throw new \RuntimeException('The "app.token" variable is not available.');
|
||||
}
|
||||
|
||||
return $tokenStorage->getToken();
|
||||
return $this->tokenStorage->getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current user.
|
||||
*
|
||||
* @return UserInterface|null
|
||||
*
|
||||
* @see TokenInterface::getUser()
|
||||
*/
|
||||
public function getUser()
|
||||
public function getUser(): ?UserInterface
|
||||
{
|
||||
if (null === $tokenStorage = $this->tokenStorage) {
|
||||
if (!isset($this->tokenStorage)) {
|
||||
throw new \RuntimeException('The "app.user" variable is not available.');
|
||||
}
|
||||
|
||||
if (!$token = $tokenStorage->getToken()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = $token->getUser();
|
||||
|
||||
// @deprecated since Symfony 5.4, $user will always be a UserInterface instance
|
||||
return \is_object($user) ? $user : null;
|
||||
return $this->tokenStorage->getToken()?->getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current request.
|
||||
*
|
||||
* @return Request|null
|
||||
*/
|
||||
public function getRequest()
|
||||
public function getRequest(): ?Request
|
||||
{
|
||||
if (null === $this->requestStack) {
|
||||
if (!isset($this->requestStack)) {
|
||||
throw new \RuntimeException('The "app.request" variable is not available.');
|
||||
}
|
||||
|
||||
@@ -105,27 +117,23 @@ class AppVariable
|
||||
|
||||
/**
|
||||
* Returns the current session.
|
||||
*
|
||||
* @return Session|null
|
||||
*/
|
||||
public function getSession()
|
||||
public function getSession(): ?SessionInterface
|
||||
{
|
||||
if (null === $this->requestStack) {
|
||||
if (!isset($this->requestStack)) {
|
||||
throw new \RuntimeException('The "app.session" variable is not available.');
|
||||
}
|
||||
$request = $this->getRequest();
|
||||
|
||||
return $request && $request->hasSession() ? $request->getSession() : null;
|
||||
return $request?->hasSession() ? $request->getSession() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current app environment.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEnvironment()
|
||||
public function getEnvironment(): string
|
||||
{
|
||||
if (null === $this->environment) {
|
||||
if (!isset($this->environment)) {
|
||||
throw new \RuntimeException('The "app.environment" variable is not available.');
|
||||
}
|
||||
|
||||
@@ -134,33 +142,53 @@ class AppVariable
|
||||
|
||||
/**
|
||||
* Returns the current app debug mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getDebug()
|
||||
public function getDebug(): bool
|
||||
{
|
||||
if (null === $this->debug) {
|
||||
if (!isset($this->debug)) {
|
||||
throw new \RuntimeException('The "app.debug" variable is not available.');
|
||||
}
|
||||
|
||||
return $this->debug;
|
||||
}
|
||||
|
||||
public function getLocale(): string
|
||||
{
|
||||
if (!isset($this->localeSwitcher)) {
|
||||
throw new \RuntimeException('The "app.locale" variable is not available.');
|
||||
}
|
||||
|
||||
return $this->localeSwitcher->getLocale();
|
||||
}
|
||||
|
||||
public function getEnabled_locales(): array
|
||||
{
|
||||
if (!isset($this->enabledLocales)) {
|
||||
throw new \RuntimeException('The "app.enabled_locales" variable is not available.');
|
||||
}
|
||||
|
||||
return $this->enabledLocales;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns some or all the existing flash messages:
|
||||
* * getFlashes() returns all the flash messages
|
||||
* * getFlashes('notice') returns a simple array with flash messages of that type
|
||||
* * getFlashes(['notice', 'error']) returns a nested array of type => messages.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFlashes($types = null)
|
||||
public function getFlashes(string|array $types = null): array
|
||||
{
|
||||
try {
|
||||
if (null === $session = $this->getSession()) {
|
||||
return [];
|
||||
}
|
||||
} catch (\RuntimeException $e) {
|
||||
} catch (\RuntimeException) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// In 7.0 (when symfony/http-foundation: 6.4 is required) this can be updated to
|
||||
// check if the session is an instance of FlashBagAwareSessionInterface
|
||||
if (!method_exists($session, 'getFlashBag')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -179,4 +207,25 @@ class AppVariable
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getCurrent_route(): ?string
|
||||
{
|
||||
if (!isset($this->requestStack)) {
|
||||
throw new \RuntimeException('The "app.current_route" variable is not available.');
|
||||
}
|
||||
|
||||
return $this->getRequest()?->attributes->get('_route');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getCurrent_route_parameters(): array
|
||||
{
|
||||
if (!isset($this->requestStack)) {
|
||||
throw new \RuntimeException('The "app.current_route_parameters" variable is not available.');
|
||||
}
|
||||
|
||||
return $this->getRequest()?->attributes->get('_route_params') ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
34
lib/symfony/twig-bridge/Attribute/Template.php
Normal file
34
lib/symfony/twig-bridge/Attribute/Template.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION)]
|
||||
class Template
|
||||
{
|
||||
public function __construct(
|
||||
/**
|
||||
* The name of the template to render.
|
||||
*/
|
||||
public string $template,
|
||||
|
||||
/**
|
||||
* The controller method arguments to pass to the template.
|
||||
*/
|
||||
public ?array $vars = null,
|
||||
|
||||
/**
|
||||
* Enables streaming the template.
|
||||
*/
|
||||
public bool $stream = false,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,32 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
6.4
|
||||
---
|
||||
|
||||
* Allow an array to be passed as the first argument to the `importmap()` Twig function
|
||||
* Add `TemplatedEmail::locale()` to set the locale for the email rendering
|
||||
* Add `AppVariable::getEnabledLocales()` to retrieve the enabled locales
|
||||
* Add `impersonation_path()` and `impersonation_url()` Twig functions
|
||||
|
||||
6.3
|
||||
---
|
||||
|
||||
* Add `AppVariable::getLocale()` to retrieve the current locale when using the `LocaleSwitcher`
|
||||
|
||||
6.2
|
||||
---
|
||||
|
||||
* Add `form_label_content` and `form_help_content` block to form themes
|
||||
* Add `#[Template()]` to describe how to render arrays returned by controllers
|
||||
* Add support for toggle buttons in Bootstrap 5 form theme
|
||||
* Add `app.current_route` and `app.current_route_parameters` variables
|
||||
|
||||
6.1
|
||||
---
|
||||
|
||||
* Wrap help messages on form elements in `div` instead of `p`
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Bridge\Twig\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
@@ -21,8 +22,8 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\ChainLoader;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
@@ -32,17 +33,20 @@ use Twig\Loader\FilesystemLoader;
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
#[AsCommand(name: 'debug:twig', description: 'Show a list of twig functions, filters, globals and tests')]
|
||||
class DebugCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'debug:twig';
|
||||
protected static $defaultDescription = 'Show a list of twig functions, filters, globals and tests';
|
||||
private Environment $twig;
|
||||
private ?string $projectDir;
|
||||
private array $bundlesMetadata;
|
||||
private ?string $twigDefaultPath;
|
||||
|
||||
private $twig;
|
||||
private $projectDir;
|
||||
private $bundlesMetadata;
|
||||
private $twigDefaultPath;
|
||||
private $filesystemLoaders;
|
||||
private $fileLinkFormatter;
|
||||
/**
|
||||
* @var FilesystemLoader[]
|
||||
*/
|
||||
private array $filesystemLoaders;
|
||||
|
||||
private ?FileLinkFormatter $fileLinkFormatter;
|
||||
|
||||
public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null)
|
||||
{
|
||||
@@ -55,15 +59,17 @@ class DebugCommand extends Command
|
||||
$this->fileLinkFormatter = $fileLinkFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDefinition([
|
||||
new InputArgument('name', InputArgument::OPTIONAL, 'The template name'),
|
||||
new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'),
|
||||
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'),
|
||||
new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'),
|
||||
])
|
||||
->setDescription(self::$defaultDescription)
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> command outputs a list of twig functions,
|
||||
filters, globals and tests.
|
||||
@@ -88,7 +94,7 @@ EOF
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$name = $input->getArgument('name');
|
||||
@@ -98,16 +104,11 @@ EOF
|
||||
throw new InvalidArgumentException(sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class));
|
||||
}
|
||||
|
||||
switch ($input->getOption('format')) {
|
||||
case 'text':
|
||||
$name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter);
|
||||
break;
|
||||
case 'json':
|
||||
$name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
|
||||
}
|
||||
match ($input->getOption('format')) {
|
||||
'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter),
|
||||
'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter),
|
||||
default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))),
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -119,11 +120,11 @@ EOF
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('format')) {
|
||||
$suggestions->suggestValues(['text', 'json']);
|
||||
$suggestions->suggestValues($this->getAvailableFormatOptions());
|
||||
}
|
||||
}
|
||||
|
||||
private function displayPathsText(SymfonyStyle $io, string $name)
|
||||
private function displayPathsText(SymfonyStyle $io, string $name): void
|
||||
{
|
||||
$file = new \ArrayIterator($this->findTemplateFiles($name));
|
||||
$paths = $this->getLoaderPaths($name);
|
||||
@@ -164,9 +165,7 @@ EOF
|
||||
[$namespace, $shortname] = $this->parseTemplateName($name);
|
||||
$alternatives = $this->findAlternatives($shortname, $shortnames);
|
||||
if (FilesystemLoader::MAIN_NAMESPACE !== $namespace) {
|
||||
$alternatives = array_map(function ($shortname) use ($namespace) {
|
||||
return '@'.$namespace.'/'.$shortname;
|
||||
}, $alternatives);
|
||||
$alternatives = array_map(fn ($shortname) => '@'.$namespace.'/'.$shortname, $alternatives);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +199,7 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
private function displayPathsJson(SymfonyStyle $io, string $name)
|
||||
private function displayPathsJson(SymfonyStyle $io, string $name): void
|
||||
{
|
||||
$files = $this->findTemplateFiles($name);
|
||||
$paths = $this->getLoaderPaths($name);
|
||||
@@ -218,7 +217,7 @@ EOF
|
||||
$io->writeln(json_encode($data));
|
||||
}
|
||||
|
||||
private function displayGeneralText(SymfonyStyle $io, string $filter = null)
|
||||
private function displayGeneralText(SymfonyStyle $io, string $filter = null): void
|
||||
{
|
||||
$decorated = $io->isDecorated();
|
||||
$types = ['functions', 'filters', 'tests', 'globals'];
|
||||
@@ -252,7 +251,7 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
private function displayGeneralJson(SymfonyStyle $io, ?string $filter)
|
||||
private function displayGeneralJson(SymfonyStyle $io, ?string $filter): void
|
||||
{
|
||||
$decorated = $io->isDecorated();
|
||||
$types = ['functions', 'filters', 'tests', 'globals'];
|
||||
@@ -291,7 +290,7 @@ EOF
|
||||
}
|
||||
|
||||
foreach ($namespaces as $namespace) {
|
||||
$paths = array_map([$this, 'getRelativePath'], $loader->getPaths($namespace));
|
||||
$paths = array_map($this->getRelativePath(...), $loader->getPaths($namespace));
|
||||
|
||||
if (FilesystemLoader::MAIN_NAMESPACE === $namespace) {
|
||||
$namespace = '(None)';
|
||||
@@ -306,7 +305,7 @@ EOF
|
||||
return $loaderPaths;
|
||||
}
|
||||
|
||||
private function getMetadata(string $type, $entity)
|
||||
private function getMetadata(string $type, mixed $entity): mixed
|
||||
{
|
||||
if ('globals' === $type) {
|
||||
return $entity;
|
||||
@@ -364,7 +363,7 @@ EOF
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getPrettyMetadata(string $type, $entity, bool $decorated): ?string
|
||||
private function getPrettyMetadata(string $type, mixed $entity, bool $decorated): ?string
|
||||
{
|
||||
if ('tests' === $type) {
|
||||
return '';
|
||||
@@ -381,7 +380,7 @@ EOF
|
||||
|
||||
if ('globals' === $type) {
|
||||
if (\is_object($meta)) {
|
||||
return ' = object('.\get_class($meta).')';
|
||||
return ' = object('.$meta::class.')';
|
||||
}
|
||||
|
||||
$description = substr(@json_encode($meta), 0, 50);
|
||||
@@ -545,7 +544,7 @@ EOF
|
||||
}
|
||||
|
||||
$threshold = 1e3;
|
||||
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
|
||||
$alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold);
|
||||
ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
|
||||
|
||||
return array_keys($alternatives);
|
||||
@@ -570,7 +569,7 @@ EOF
|
||||
*/
|
||||
private function getFilesystemLoaders(): array
|
||||
{
|
||||
if (null !== $this->filesystemLoaders) {
|
||||
if (isset($this->filesystemLoaders)) {
|
||||
return $this->filesystemLoaders;
|
||||
}
|
||||
$this->filesystemLoaders = [];
|
||||
@@ -597,4 +596,9 @@ EOF
|
||||
|
||||
return (string) $this->fileLinkFormatter->format($absolutePath, 1);
|
||||
}
|
||||
|
||||
private function getAvailableFormatOptions(): array
|
||||
{
|
||||
return ['text', 'json'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Bridge\Twig\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\CI\GithubActionReporter;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
@@ -35,30 +36,25 @@ use Twig\Source;
|
||||
* @author Marc Weistroff <marc.weistroff@sensiolabs.com>
|
||||
* @author Jérôme Tamarelle <jerome@tamarelle.net>
|
||||
*/
|
||||
#[AsCommand(name: 'lint:twig', description: 'Lint a Twig template and outputs encountered errors')]
|
||||
class LintCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'lint:twig';
|
||||
protected static $defaultDescription = 'Lint a Twig template and outputs encountered errors';
|
||||
private string $format;
|
||||
|
||||
private $twig;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $format;
|
||||
|
||||
public function __construct(Environment $twig)
|
||||
{
|
||||
public function __construct(
|
||||
private Environment $twig,
|
||||
private array $namePatterns = ['*.twig'],
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription(self::$defaultDescription)
|
||||
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format')
|
||||
->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())))
|
||||
->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors')
|
||||
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
|
||||
->setHelp(<<<'EOF'
|
||||
@@ -83,16 +79,12 @@ EOF
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$filenames = $input->getArgument('filename');
|
||||
$showDeprecations = $input->getOption('show-deprecations');
|
||||
$this->format = $input->getOption('format');
|
||||
|
||||
if (null === $this->format) {
|
||||
$this->format = GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt';
|
||||
}
|
||||
$this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt');
|
||||
|
||||
if (['-'] === $filenames) {
|
||||
return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]);
|
||||
@@ -151,12 +143,12 @@ EOF
|
||||
return $filesInfo;
|
||||
}
|
||||
|
||||
protected function findFiles(string $filename)
|
||||
protected function findFiles(string $filename): iterable
|
||||
{
|
||||
if (is_file($filename)) {
|
||||
return [$filename];
|
||||
} elseif (is_dir($filename)) {
|
||||
return Finder::create()->files()->in($filename)->name('*.twig');
|
||||
return Finder::create()->files()->in($filename)->name($this->namePatterns);
|
||||
}
|
||||
|
||||
throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
|
||||
@@ -180,21 +172,17 @@ EOF
|
||||
return ['template' => $template, 'file' => $file, 'valid' => true];
|
||||
}
|
||||
|
||||
private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files)
|
||||
private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files): int
|
||||
{
|
||||
switch ($this->format) {
|
||||
case 'txt':
|
||||
return $this->displayTxt($output, $io, $files);
|
||||
case 'json':
|
||||
return $this->displayJson($output, $files);
|
||||
case 'github':
|
||||
return $this->displayTxt($output, $io, $files, true);
|
||||
default:
|
||||
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
|
||||
}
|
||||
return match ($this->format) {
|
||||
'txt' => $this->displayTxt($output, $io, $files),
|
||||
'json' => $this->displayJson($output, $files),
|
||||
'github' => $this->displayTxt($output, $io, $files, true),
|
||||
default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))),
|
||||
};
|
||||
}
|
||||
|
||||
private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false)
|
||||
private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int
|
||||
{
|
||||
$errors = 0;
|
||||
$githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null;
|
||||
@@ -217,7 +205,7 @@ EOF
|
||||
return min($errors, 1);
|
||||
}
|
||||
|
||||
private function displayJson(OutputInterface $output, array $filesInfo)
|
||||
private function displayJson(OutputInterface $output, array $filesInfo): int
|
||||
{
|
||||
$errors = 0;
|
||||
|
||||
@@ -236,13 +224,11 @@ EOF
|
||||
return min($errors, 1);
|
||||
}
|
||||
|
||||
private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null)
|
||||
private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null): void
|
||||
{
|
||||
$line = $exception->getTemplateLine();
|
||||
|
||||
if ($githubReporter) {
|
||||
$githubReporter->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line);
|
||||
}
|
||||
$githubReporter?->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line);
|
||||
|
||||
if ($file) {
|
||||
$output->text(sprintf('<error> ERROR </error> in %s (line %s)', $file, $line));
|
||||
@@ -271,7 +257,7 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
private function getContext(string $template, int $line, int $context = 3)
|
||||
private function getContext(string $template, int $line, int $context = 3): array
|
||||
{
|
||||
$lines = explode("\n", $template);
|
||||
|
||||
@@ -290,7 +276,12 @@ EOF
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestOptionValuesFor('format')) {
|
||||
$suggestions->suggestValues(['txt', 'json', 'github']);
|
||||
$suggestions->suggestValues($this->getAvailableFormatOptions());
|
||||
}
|
||||
}
|
||||
|
||||
private function getAvailableFormatOptions(): array
|
||||
{
|
||||
return ['txt', 'json', 'github'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ use Twig\Profiler\Profile;
|
||||
*/
|
||||
class TwigDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||
{
|
||||
private $profile;
|
||||
private $twig;
|
||||
private $computed;
|
||||
private Profile $profile;
|
||||
private ?Environment $twig;
|
||||
private array $computed;
|
||||
|
||||
public function __construct(Profile $profile, Environment $twig = null)
|
||||
{
|
||||
@@ -38,27 +38,18 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null)
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
public function reset(): void
|
||||
{
|
||||
$this->profile->reset();
|
||||
$this->computed = null;
|
||||
unset($this->computed);
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lateCollect()
|
||||
public function lateCollect(): void
|
||||
{
|
||||
$this->data['profile'] = serialize($this->profile);
|
||||
$this->data['template_paths'] = [];
|
||||
@@ -71,7 +62,7 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
|
||||
if ($profile->isTemplate()) {
|
||||
try {
|
||||
$template = $this->twig->load($name = $profile->getName());
|
||||
} catch (LoaderError $e) {
|
||||
} catch (LoaderError) {
|
||||
$template = null;
|
||||
}
|
||||
|
||||
@@ -87,37 +78,37 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
|
||||
$templateFinder($this->profile);
|
||||
}
|
||||
|
||||
public function getTime()
|
||||
public function getTime(): float
|
||||
{
|
||||
return $this->getProfile()->getDuration() * 1000;
|
||||
}
|
||||
|
||||
public function getTemplateCount()
|
||||
public function getTemplateCount(): int
|
||||
{
|
||||
return $this->getComputedData('template_count');
|
||||
}
|
||||
|
||||
public function getTemplatePaths()
|
||||
public function getTemplatePaths(): array
|
||||
{
|
||||
return $this->data['template_paths'];
|
||||
}
|
||||
|
||||
public function getTemplates()
|
||||
public function getTemplates(): array
|
||||
{
|
||||
return $this->getComputedData('templates');
|
||||
}
|
||||
|
||||
public function getBlockCount()
|
||||
public function getBlockCount(): int
|
||||
{
|
||||
return $this->getComputedData('block_count');
|
||||
}
|
||||
|
||||
public function getMacroCount()
|
||||
public function getMacroCount(): int
|
||||
{
|
||||
return $this->getComputedData('macro_count');
|
||||
}
|
||||
|
||||
public function getHtmlCallGraph()
|
||||
public function getHtmlCallGraph(): Markup
|
||||
{
|
||||
$dumper = new HtmlDumper();
|
||||
$dump = $dumper->dump($this->getProfile());
|
||||
@@ -138,25 +129,19 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
|
||||
return new Markup($dump, 'UTF-8');
|
||||
}
|
||||
|
||||
public function getProfile()
|
||||
public function getProfile(): Profile
|
||||
{
|
||||
if (null === $this->profile) {
|
||||
$this->profile = unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]);
|
||||
}
|
||||
|
||||
return $this->profile;
|
||||
return $this->profile ??= unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', Profile::class]]);
|
||||
}
|
||||
|
||||
private function getComputedData(string $index)
|
||||
private function getComputedData(string $index): mixed
|
||||
{
|
||||
if (null === $this->computed) {
|
||||
$this->computed = $this->computeData($this->getProfile());
|
||||
}
|
||||
$this->computed ??= $this->computeData($this->getProfile());
|
||||
|
||||
return $this->computed[$index];
|
||||
}
|
||||
|
||||
private function computeData(Profile $profile)
|
||||
private function computeData(Profile $profile): array
|
||||
{
|
||||
$data = [
|
||||
'template_count' => 0,
|
||||
@@ -193,9 +178,6 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'twig';
|
||||
|
||||
@@ -25,40 +25,33 @@ use Twig\Environment;
|
||||
*/
|
||||
class TwigErrorRenderer implements ErrorRendererInterface
|
||||
{
|
||||
private $twig;
|
||||
private $fallbackErrorRenderer;
|
||||
private $debug;
|
||||
private Environment $twig;
|
||||
private HtmlErrorRenderer $fallbackErrorRenderer;
|
||||
private \Closure|bool $debug;
|
||||
|
||||
/**
|
||||
* @param bool|callable $debug The debugging mode as a boolean or a callable that should return it
|
||||
*/
|
||||
public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, $debug = false)
|
||||
public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, bool|callable $debug = false)
|
||||
{
|
||||
if (!\is_bool($debug) && !\is_callable($debug)) {
|
||||
throw new \TypeError(sprintf('Argument 3 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, get_debug_type($debug)));
|
||||
}
|
||||
|
||||
$this->twig = $twig;
|
||||
$this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer();
|
||||
$this->debug = $debug;
|
||||
$this->debug = \is_bool($debug) ? $debug : $debug(...);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(\Throwable $exception): FlattenException
|
||||
{
|
||||
$exception = $this->fallbackErrorRenderer->render($exception);
|
||||
$debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception);
|
||||
$flattenException = FlattenException::createFromThrowable($exception);
|
||||
$debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($flattenException);
|
||||
|
||||
if ($debug || !$template = $this->findTemplate($exception->getStatusCode())) {
|
||||
return $exception;
|
||||
if ($debug || !$template = $this->findTemplate($flattenException->getStatusCode())) {
|
||||
return $this->fallbackErrorRenderer->render($exception);
|
||||
}
|
||||
|
||||
return $exception->setAsString($this->twig->render($template, [
|
||||
'exception' => $exception,
|
||||
'status_code' => $exception->getStatusCode(),
|
||||
'status_text' => $exception->getStatusText(),
|
||||
return $flattenException->setAsString($this->twig->render($template, [
|
||||
'exception' => $flattenException,
|
||||
'status_code' => $flattenException->getStatusCode(),
|
||||
'status_text' => $flattenException->getStatusText(),
|
||||
]));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?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\EventListener;
|
||||
|
||||
use Symfony\Bridge\Twig\Attribute\Template;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
|
||||
use Symfony\Component\HttpKernel\Event\ViewEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Twig\Environment;
|
||||
|
||||
class TemplateAttributeListener implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Environment $twig,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function onKernelView(ViewEvent $event)
|
||||
{
|
||||
$parameters = $event->getControllerResult();
|
||||
|
||||
if (!\is_array($parameters ?? [])) {
|
||||
return;
|
||||
}
|
||||
$attribute = $event->getRequest()->attributes->get('_template');
|
||||
|
||||
if (!$attribute instanceof Template && !$attribute = $event->controllerArgumentsEvent?->getAttributes()[Template::class][0] ?? null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parameters ??= $this->resolveParameters($event->controllerArgumentsEvent, $attribute->vars);
|
||||
$status = 200;
|
||||
|
||||
foreach ($parameters as $k => $v) {
|
||||
if (!$v instanceof FormInterface) {
|
||||
continue;
|
||||
}
|
||||
if ($v->isSubmitted() && !$v->isValid()) {
|
||||
$status = 422;
|
||||
}
|
||||
$parameters[$k] = $v->createView();
|
||||
}
|
||||
|
||||
$event->setResponse($attribute->stream
|
||||
? new StreamedResponse(fn () => $this->twig->display($attribute->template, $parameters), $status)
|
||||
: new Response($this->twig->render($attribute->template, $parameters), $status)
|
||||
);
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
KernelEvents::VIEW => ['onKernelView', -128],
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveParameters(ControllerArgumentsEvent $event, ?array $vars): array
|
||||
{
|
||||
if ([] === $vars) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parameters = $event->getNamedArguments();
|
||||
|
||||
if (null !== $vars) {
|
||||
$parameters = array_intersect_key($parameters, array_flip($vars));
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
}
|
||||
@@ -22,21 +22,18 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class AssetExtension extends AbstractExtension
|
||||
{
|
||||
private $packages;
|
||||
private Packages $packages;
|
||||
|
||||
public function __construct(Packages $packages)
|
||||
{
|
||||
$this->packages = $packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('asset', [$this, 'getAssetUrl']),
|
||||
new TwigFunction('asset_version', [$this, 'getAssetVersion']),
|
||||
new TwigFunction('asset', $this->getAssetUrl(...)),
|
||||
new TwigFunction('asset_version', $this->getAssetVersion(...)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -11,47 +11,46 @@
|
||||
|
||||
namespace Symfony\Bridge\Twig\Extension;
|
||||
|
||||
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
|
||||
use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
/**
|
||||
* Twig extension relate to PHP code and used by the profiler and the default exception templates.
|
||||
*
|
||||
* This extension should only be used for debugging tools code
|
||||
* that is never executed in a production environment.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal since Symfony 6.4
|
||||
*/
|
||||
final class CodeExtension extends AbstractExtension
|
||||
{
|
||||
private $fileLinkFormat;
|
||||
private $charset;
|
||||
private $projectDir;
|
||||
private string|FileLinkFormatter|array|false $fileLinkFormat;
|
||||
private string $charset;
|
||||
private string $projectDir;
|
||||
|
||||
/**
|
||||
* @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
|
||||
*/
|
||||
public function __construct($fileLinkFormat, string $projectDir, string $charset)
|
||||
public function __construct(string|FileLinkFormatter $fileLinkFormat, string $projectDir, string $charset)
|
||||
{
|
||||
$this->fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
|
||||
$this->projectDir = str_replace('\\', '/', $projectDir).'/';
|
||||
$this->charset = $charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('abbr_class', [$this, 'abbrClass'], ['is_safe' => ['html'], 'pre_escape' => 'html']),
|
||||
new TwigFilter('abbr_method', [$this, 'abbrMethod'], ['is_safe' => ['html'], 'pre_escape' => 'html']),
|
||||
new TwigFilter('format_args', [$this, 'formatArgs'], ['is_safe' => ['html']]),
|
||||
new TwigFilter('format_args_as_text', [$this, 'formatArgsAsText']),
|
||||
new TwigFilter('file_excerpt', [$this, 'fileExcerpt'], ['is_safe' => ['html']]),
|
||||
new TwigFilter('format_file', [$this, 'formatFile'], ['is_safe' => ['html']]),
|
||||
new TwigFilter('format_file_from_text', [$this, 'formatFileFromText'], ['is_safe' => ['html']]),
|
||||
new TwigFilter('format_log_message', [$this, 'formatLogMessage'], ['is_safe' => ['html']]),
|
||||
new TwigFilter('file_link', [$this, 'getFileLink']),
|
||||
new TwigFilter('file_relative', [$this, 'getFileRelative']),
|
||||
new TwigFilter('abbr_class', $this->abbrClass(...), ['is_safe' => ['html'], 'pre_escape' => 'html']),
|
||||
new TwigFilter('abbr_method', $this->abbrMethod(...), ['is_safe' => ['html'], 'pre_escape' => 'html']),
|
||||
new TwigFilter('format_args', $this->formatArgs(...), ['is_safe' => ['html']]),
|
||||
new TwigFilter('format_args_as_text', $this->formatArgsAsText(...)),
|
||||
new TwigFilter('file_excerpt', $this->fileExcerpt(...), ['is_safe' => ['html']]),
|
||||
new TwigFilter('format_file', $this->formatFile(...), ['is_safe' => ['html']]),
|
||||
new TwigFilter('format_file_from_text', $this->formatFileFromText(...), ['is_safe' => ['html']]),
|
||||
new TwigFilter('format_log_message', $this->formatLogMessage(...), ['is_safe' => ['html']]),
|
||||
new TwigFilter('file_link', $this->getFileLink(...)),
|
||||
new TwigFilter('file_relative', $this->getFileRelative(...)),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -128,9 +127,7 @@ final class CodeExtension extends AbstractExtension
|
||||
// remove main pre/code tags
|
||||
$code = preg_replace('#^<pre.*?>\s*<code.*?>(.*)</code>\s*</pre>#s', '\\1', $code);
|
||||
// split multiline code tags
|
||||
$code = preg_replace_callback('#<code ([^>]++)>((?:[^<]*+\\n)++[^<]*+)</code>#', function ($m) {
|
||||
return "<code $m[1]>".str_replace("\n", "</code>\n<code $m[1]>", $m[2]).'</code>';
|
||||
}, $code);
|
||||
$code = preg_replace_callback('#<code ([^>]++)>((?:[^<]*+\\n)++[^<]*+)</code>#', fn ($m) => "<code $m[1]>".str_replace("\n", "</code>\n<code $m[1]>", $m[2]).'</code>', $code);
|
||||
// Convert spaces to html entities to preserve indentation when rendered
|
||||
$code = str_replace(' ', ' ', $code);
|
||||
$content = explode("\n", $code);
|
||||
@@ -138,9 +135,7 @@ final class CodeExtension extends AbstractExtension
|
||||
// remove main code/span tags
|
||||
$code = preg_replace('#^<code.*?>\s*<span.*?>(.*)</span>\s*</code>#s', '\\1', $code);
|
||||
// split multiline spans
|
||||
$code = preg_replace_callback('#<span ([^>]++)>((?:[^<]*+<br \/>)++[^<]*+)</span>#', function ($m) {
|
||||
return "<span $m[1]>".str_replace('<br />', "</span><br /><span $m[1]>", $m[2]).'</span>';
|
||||
}, $code);
|
||||
$code = preg_replace_callback('#<span ([^>]++)>((?:[^<]*+<br \/>)++[^<]*+)</span>#', fn ($m) => "<span $m[1]>".str_replace('<br />', "</span><br /><span $m[1]>", $m[2]).'</span>', $code);
|
||||
$content = explode('<br />', $code);
|
||||
}
|
||||
|
||||
@@ -188,12 +183,7 @@ final class CodeExtension extends AbstractExtension
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the link for a given file/line pair.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function getFileLink(string $file, int $line)
|
||||
public function getFileLink(string $file, int $line): string|false
|
||||
{
|
||||
if ($fmt = $this->fileLinkFormat) {
|
||||
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
|
||||
@@ -215,9 +205,7 @@ final class CodeExtension extends AbstractExtension
|
||||
|
||||
public function formatFileFromText(string $text): string
|
||||
{
|
||||
return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) {
|
||||
return 'in '.$this->formatFile($match[2], $match[3]);
|
||||
}, $text);
|
||||
return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', fn ($match) => 'in '.$this->formatFile($match[2], $match[3]), $text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,9 +20,6 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class CsrfExtension extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -19,7 +19,7 @@ use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
|
||||
*/
|
||||
final class CsrfRuntime
|
||||
{
|
||||
private $csrfTokenManager;
|
||||
private CsrfTokenManagerInterface $csrfTokenManager;
|
||||
|
||||
public function __construct(CsrfTokenManagerInterface $csrfTokenManager)
|
||||
{
|
||||
|
||||
@@ -26,8 +26,8 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class DumpExtension extends AbstractExtension
|
||||
{
|
||||
private $cloner;
|
||||
private $dumper;
|
||||
private ClonerInterface $cloner;
|
||||
private ?HtmlDumper $dumper;
|
||||
|
||||
public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null)
|
||||
{
|
||||
@@ -35,19 +35,13 @@ final class DumpExtension extends AbstractExtension
|
||||
$this->dumper = $dumper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('dump', [$this, 'dump'], ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]),
|
||||
new TwigFunction('dump', $this->dump(...), ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTokenParsers(): array
|
||||
{
|
||||
return [new DumpTokenParser()];
|
||||
@@ -74,7 +68,7 @@ final class DumpExtension extends AbstractExtension
|
||||
}
|
||||
|
||||
$dump = fopen('php://memory', 'r+');
|
||||
$this->dumper = $this->dumper ?? new HtmlDumper();
|
||||
$this->dumper ??= new HtmlDumper();
|
||||
$this->dumper->setCharset($env->getCharset());
|
||||
|
||||
foreach ($vars as $value) {
|
||||
|
||||
@@ -22,13 +22,10 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class ExpressionExtension extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('expression', [$this, 'createExpression']),
|
||||
new TwigFunction('expression', $this->createExpression(...)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
|
||||
namespace Symfony\Bridge\Twig\Extension;
|
||||
|
||||
use Symfony\Bridge\Twig\Node\RenderBlockNode;
|
||||
use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode;
|
||||
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormRenderer;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
@@ -30,16 +33,13 @@ use Twig\TwigTest;
|
||||
*/
|
||||
final class FormExtension extends AbstractExtension
|
||||
{
|
||||
private $translator;
|
||||
private ?TranslatorInterface $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator = null)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTokenParsers(): array
|
||||
{
|
||||
return [
|
||||
@@ -48,46 +48,37 @@ final class FormExtension extends AbstractExtension
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('form_widget', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_errors', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_label', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_help', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_row', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_rest', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]),
|
||||
new TwigFunction('form', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_start', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_end', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]),
|
||||
new TwigFunction('csrf_token', ['Symfony\Component\Form\FormRenderer', 'renderCsrfToken']),
|
||||
new TwigFunction('form_widget', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_errors', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_label', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_help', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_row', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_rest', null, ['node_class' => SearchAndRenderBlockNode::class, 'is_safe' => ['html']]),
|
||||
new TwigFunction('form', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_start', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]),
|
||||
new TwigFunction('form_end', null, ['node_class' => RenderBlockNode::class, 'is_safe' => ['html']]),
|
||||
new TwigFunction('csrf_token', [FormRenderer::class, 'renderCsrfToken']),
|
||||
new TwigFunction('form_parent', 'Symfony\Bridge\Twig\Extension\twig_get_form_parent'),
|
||||
new TwigFunction('field_name', [$this, 'getFieldName']),
|
||||
new TwigFunction('field_value', [$this, 'getFieldValue']),
|
||||
new TwigFunction('field_label', [$this, 'getFieldLabel']),
|
||||
new TwigFunction('field_help', [$this, 'getFieldHelp']),
|
||||
new TwigFunction('field_errors', [$this, 'getFieldErrors']),
|
||||
new TwigFunction('field_choices', [$this, 'getFieldChoices']),
|
||||
new TwigFunction('field_name', $this->getFieldName(...)),
|
||||
new TwigFunction('field_value', $this->getFieldValue(...)),
|
||||
new TwigFunction('field_label', $this->getFieldLabel(...)),
|
||||
new TwigFunction('field_help', $this->getFieldHelp(...)),
|
||||
new TwigFunction('field_errors', $this->getFieldErrors(...)),
|
||||
new TwigFunction('field_choices', $this->getFieldChoices(...)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('humanize', ['Symfony\Component\Form\FormRenderer', 'humanize']),
|
||||
new TwigFilter('form_encode_currency', ['Symfony\Component\Form\FormRenderer', 'encodeCurrency'], ['is_safe' => ['html'], 'needs_environment' => true]),
|
||||
new TwigFilter('humanize', [FormRenderer::class, 'humanize']),
|
||||
new TwigFilter('form_encode_currency', [FormRenderer::class, 'encodeCurrency'], ['is_safe' => ['html'], 'needs_environment' => true]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTests(): array
|
||||
{
|
||||
return [
|
||||
@@ -103,10 +94,7 @@ final class FormExtension extends AbstractExtension
|
||||
return $view->vars['full_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|array
|
||||
*/
|
||||
public function getFieldValue(FormView $view)
|
||||
public function getFieldValue(FormView $view): string|array
|
||||
{
|
||||
return $view->vars['value'];
|
||||
}
|
||||
@@ -158,7 +146,7 @@ final class FormExtension extends AbstractExtension
|
||||
yield from $this->createFieldChoicesList($view->vars['choices'], $view->vars['choice_translation_domain']);
|
||||
}
|
||||
|
||||
private function createFieldChoicesList(iterable $choices, $translationDomain): iterable
|
||||
private function createFieldChoicesList(iterable $choices, string|false|null $translationDomain): iterable
|
||||
{
|
||||
foreach ($choices as $choice) {
|
||||
$translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain);
|
||||
@@ -174,7 +162,7 @@ final class FormExtension extends AbstractExtension
|
||||
}
|
||||
}
|
||||
|
||||
private function createFieldTranslation(?string $value, array $parameters, $domain): ?string
|
||||
private function createFieldTranslation(?string $value, array $parameters, string|false|null $domain): ?string
|
||||
{
|
||||
if (!$this->translator || !$value || false === $domain) {
|
||||
return $value;
|
||||
@@ -189,11 +177,9 @@ final class FormExtension extends AbstractExtension
|
||||
*
|
||||
* This is a function and not callable due to performance reasons.
|
||||
*
|
||||
* @param string|array $selectedValue The selected value to compare
|
||||
*
|
||||
* @see ChoiceView::isSelected()
|
||||
*/
|
||||
function twig_is_selected_choice(ChoiceView $choice, $selectedValue): bool
|
||||
function twig_is_selected_choice(ChoiceView $choice, string|array|null $selectedValue): bool
|
||||
{
|
||||
if (\is_array($selectedValue)) {
|
||||
return \in_array($choice->value, $selectedValue, true);
|
||||
|
||||
40
lib/symfony/twig-bridge/Extension/HtmlSanitizerExtension.php
Normal file
40
lib/symfony/twig-bridge/Extension/HtmlSanitizerExtension.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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\Extension;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
/**
|
||||
* @author Titouan Galopin <galopintitouan@gmail.com>
|
||||
*/
|
||||
final class HtmlSanitizerExtension extends AbstractExtension
|
||||
{
|
||||
public function __construct(
|
||||
private ContainerInterface $sanitizers,
|
||||
private string $defaultSanitizer = 'default',
|
||||
) {
|
||||
}
|
||||
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('sanitize_html', $this->sanitize(...), ['is_safe' => ['html']]),
|
||||
];
|
||||
}
|
||||
|
||||
public function sanitize(string $html, string $sanitizer = null): string
|
||||
{
|
||||
return $this->sanitizers->get($sanitizer ?? $this->defaultSanitizer)->sanitize($html);
|
||||
}
|
||||
}
|
||||
@@ -23,21 +23,18 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class HttpFoundationExtension extends AbstractExtension
|
||||
{
|
||||
private $urlHelper;
|
||||
private UrlHelper $urlHelper;
|
||||
|
||||
public function __construct(UrlHelper $urlHelper)
|
||||
{
|
||||
$this->urlHelper = $urlHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('absolute_url', [$this, 'generateAbsoluteUrl']),
|
||||
new TwigFunction('relative_path', [$this, 'generateRelativePath']),
|
||||
new TwigFunction('absolute_url', $this->generateAbsoluteUrl(...)),
|
||||
new TwigFunction('relative_path', $this->generateRelativePath(...)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -22,16 +22,13 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class HttpKernelExtension extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('render', [HttpKernelRuntime::class, 'renderFragment'], ['is_safe' => ['html']]),
|
||||
new TwigFunction('render_*', [HttpKernelRuntime::class, 'renderFragmentStrategy'], ['is_safe' => ['html']]),
|
||||
new TwigFunction('fragment_uri', [HttpKernelRuntime::class, 'generateFragmentUri']),
|
||||
new TwigFunction('controller', static::class.'::controller'),
|
||||
new TwigFunction('controller', [self::class, 'controller']),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface;
|
||||
*/
|
||||
final class HttpKernelRuntime
|
||||
{
|
||||
private $handler;
|
||||
private $fragmentUriGenerator;
|
||||
private FragmentHandler $handler;
|
||||
private ?FragmentUriGeneratorInterface $fragmentUriGenerator;
|
||||
|
||||
public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterface $fragmentUriGenerator = null)
|
||||
{
|
||||
@@ -34,11 +34,9 @@ final class HttpKernelRuntime
|
||||
/**
|
||||
* Renders a fragment.
|
||||
*
|
||||
* @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
|
||||
*
|
||||
* @see FragmentHandler::render()
|
||||
*/
|
||||
public function renderFragment($uri, array $options = []): string
|
||||
public function renderFragment(string|ControllerReference $uri, array $options = []): string
|
||||
{
|
||||
$strategy = $options['strategy'] ?? 'inline';
|
||||
unset($options['strategy']);
|
||||
@@ -49,11 +47,9 @@ final class HttpKernelRuntime
|
||||
/**
|
||||
* Renders a fragment.
|
||||
*
|
||||
* @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
|
||||
*
|
||||
* @see FragmentHandler::render()
|
||||
*/
|
||||
public function renderFragmentStrategy(string $strategy, $uri, array $options = []): string
|
||||
public function renderFragmentStrategy(string $strategy, string|ControllerReference $uri, array $options = []): string
|
||||
{
|
||||
return $this->handler->render($uri, $strategy, $options);
|
||||
}
|
||||
|
||||
28
lib/symfony/twig-bridge/Extension/ImportMapExtension.php
Normal file
28
lib/symfony/twig-bridge/Extension/ImportMapExtension.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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\Extension;
|
||||
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <kevin@dunglas.dev>
|
||||
*/
|
||||
final class ImportMapExtension extends AbstractExtension
|
||||
{
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('importmap', [ImportMapRuntime::class, 'importmap'], ['is_safe' => ['html']]),
|
||||
];
|
||||
}
|
||||
}
|
||||
33
lib/symfony/twig-bridge/Extension/ImportMapRuntime.php
Normal file
33
lib/symfony/twig-bridge/Extension/ImportMapRuntime.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?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\Extension;
|
||||
|
||||
use Symfony\Component\AssetMapper\ImportMap\ImportMapRenderer;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <kevin@dunglas.dev>
|
||||
*/
|
||||
class ImportMapRuntime
|
||||
{
|
||||
public function __construct(private readonly ImportMapRenderer $importMapRenderer)
|
||||
{
|
||||
}
|
||||
|
||||
public function importmap(string|array|null $entryPoint = 'app', array $attributes = []): string
|
||||
{
|
||||
if (null === $entryPoint) {
|
||||
trigger_deprecation('symfony/twig-bridge', '6.4', 'Passing null as the first argument of the "importmap" Twig function is deprecated, pass an empty array if no entrypoints are desired.');
|
||||
}
|
||||
|
||||
return $this->importMapRenderer->render($entryPoint, $attributes);
|
||||
}
|
||||
}
|
||||
@@ -22,21 +22,18 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class LogoutUrlExtension extends AbstractExtension
|
||||
{
|
||||
private $generator;
|
||||
private LogoutUrlGenerator $generator;
|
||||
|
||||
public function __construct(LogoutUrlGenerator $generator)
|
||||
{
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('logout_url', [$this, 'getLogoutUrl']),
|
||||
new TwigFunction('logout_path', [$this, 'getLogoutPath']),
|
||||
new TwigFunction('logout_url', $this->getLogoutUrl(...)),
|
||||
new TwigFunction('logout_path', $this->getLogoutPath(...)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@ use Twig\Profiler\Profile;
|
||||
*/
|
||||
final class ProfilerExtension extends BaseProfilerExtension
|
||||
{
|
||||
private $stopwatch;
|
||||
private ?Stopwatch $stopwatch;
|
||||
|
||||
/**
|
||||
* @var \SplObjectStorage<Profile, StopwatchEvent>
|
||||
*/
|
||||
private $events;
|
||||
private \SplObjectStorage $events;
|
||||
|
||||
public function __construct(Profile $profile, Stopwatch $stopwatch = null)
|
||||
{
|
||||
|
||||
@@ -25,21 +25,18 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class RoutingExtension extends AbstractExtension
|
||||
{
|
||||
private $generator;
|
||||
private UrlGeneratorInterface $generator;
|
||||
|
||||
public function __construct(UrlGeneratorInterface $generator)
|
||||
{
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
|
||||
new TwigFunction('path', [$this, 'getPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
|
||||
new TwigFunction('url', $this->getUrl(...), ['is_safe_callback' => $this->isUrlGenerationSafe(...)]),
|
||||
new TwigFunction('path', $this->getPath(...), ['is_safe_callback' => $this->isUrlGenerationSafe(...)]),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -58,7 +55,7 @@ final class RoutingExtension extends AbstractExtension
|
||||
* saving the unneeded automatic escaping for performance reasons.
|
||||
*
|
||||
* The URL generation process percent encodes non-alphanumeric characters. So there is no risk
|
||||
* that malicious/invalid characters are part of the URL. The only character within an URL that
|
||||
* that malicious/invalid characters are part of the URL. The only character within a URL that
|
||||
* must be escaped in html is the ampersand ("&") which separates query params. So we cannot mark
|
||||
* the URL generation as always safe, but only when we are sure there won't be multiple query
|
||||
* params. This is the case when there are none or only one constant parameter given.
|
||||
@@ -82,8 +79,8 @@ final class RoutingExtension extends AbstractExtension
|
||||
$argsNode->hasNode(1) ? $argsNode->getNode(1) : null
|
||||
);
|
||||
|
||||
if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 &&
|
||||
(!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression)
|
||||
if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2
|
||||
&& (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression)
|
||||
) {
|
||||
return ['html'];
|
||||
}
|
||||
|
||||
@@ -25,9 +25,8 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class SecurityExtension extends AbstractExtension
|
||||
{
|
||||
private $securityChecker;
|
||||
|
||||
private $impersonateUrlGenerator;
|
||||
private ?AuthorizationCheckerInterface $securityChecker;
|
||||
private ?ImpersonateUrlGenerator $impersonateUrlGenerator;
|
||||
|
||||
public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null)
|
||||
{
|
||||
@@ -35,10 +34,7 @@ final class SecurityExtension extends AbstractExtension
|
||||
$this->impersonateUrlGenerator = $impersonateUrlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $object
|
||||
*/
|
||||
public function isGranted($role, $object = null, string $field = null): bool
|
||||
public function isGranted(mixed $role, mixed $object = null, string $field = null): bool
|
||||
{
|
||||
if (null === $this->securityChecker) {
|
||||
return false;
|
||||
@@ -50,7 +46,7 @@ final class SecurityExtension extends AbstractExtension
|
||||
|
||||
try {
|
||||
return $this->securityChecker->isGranted($role, $object);
|
||||
} catch (AuthenticationCredentialsNotFoundException $e) {
|
||||
} catch (AuthenticationCredentialsNotFoundException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -73,15 +69,32 @@ final class SecurityExtension extends AbstractExtension
|
||||
return $this->impersonateUrlGenerator->generateExitPath($exitTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getImpersonateUrl(string $identifier): string
|
||||
{
|
||||
if (null === $this->impersonateUrlGenerator) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->impersonateUrlGenerator->generateImpersonationUrl($identifier);
|
||||
}
|
||||
|
||||
public function getImpersonatePath(string $identifier): string
|
||||
{
|
||||
if (null === $this->impersonateUrlGenerator) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->impersonateUrlGenerator->generateImpersonationPath($identifier);
|
||||
}
|
||||
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('is_granted', [$this, 'isGranted']),
|
||||
new TwigFunction('impersonation_exit_url', [$this, 'getImpersonateExitUrl']),
|
||||
new TwigFunction('impersonation_exit_path', [$this, 'getImpersonateExitPath']),
|
||||
new TwigFunction('is_granted', $this->isGranted(...)),
|
||||
new TwigFunction('impersonation_exit_url', $this->getImpersonateExitUrl(...)),
|
||||
new TwigFunction('impersonation_exit_path', $this->getImpersonateExitPath(...)),
|
||||
new TwigFunction('impersonation_url', $this->getImpersonateUrl(...)),
|
||||
new TwigFunction('impersonation_path', $this->getImpersonatePath(...)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ use Twig\Extension\RuntimeExtensionInterface;
|
||||
*/
|
||||
final class SerializerRuntime implements RuntimeExtensionInterface
|
||||
{
|
||||
private $serializer;
|
||||
private SerializerInterface $serializer;
|
||||
|
||||
public function __construct(SerializerInterface $serializer)
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
public function serialize($data, string $format = 'json', array $context = []): string
|
||||
public function serialize(mixed $data, string $format = 'json', array $context = []): string
|
||||
{
|
||||
return $this->serializer->serialize($data, $format, $context);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ use Twig\TokenParser\TokenParserInterface;
|
||||
*/
|
||||
final class StopwatchExtension extends AbstractExtension
|
||||
{
|
||||
private $stopwatch;
|
||||
private $enabled;
|
||||
private ?Stopwatch $stopwatch;
|
||||
private bool $enabled;
|
||||
|
||||
public function __construct(Stopwatch $stopwatch = null, bool $enabled = true)
|
||||
{
|
||||
|
||||
@@ -34,8 +34,8 @@ class_exists(TranslatorTrait::class);
|
||||
*/
|
||||
final class TranslationExtension extends AbstractExtension
|
||||
{
|
||||
private $translator;
|
||||
private $translationNodeVisitor;
|
||||
private ?TranslatorInterface $translator;
|
||||
private ?TranslationNodeVisitor $translationNodeVisitor;
|
||||
|
||||
public function __construct(TranslatorInterface $translator = null, TranslationNodeVisitor $translationNodeVisitor = null)
|
||||
{
|
||||
@@ -58,29 +58,20 @@ final class TranslationExtension extends AbstractExtension
|
||||
return $this->translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('t', [$this, 'createTranslatable']),
|
||||
new TwigFunction('t', $this->createTranslatable(...)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('trans', [$this, 'trans']),
|
||||
new TwigFilter('trans', $this->trans(...)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTokenParsers(): array
|
||||
{
|
||||
return [
|
||||
@@ -92,9 +83,6 @@ final class TranslationExtension extends AbstractExtension
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodeVisitors(): array
|
||||
{
|
||||
return [$this->getTranslationNodeVisitor(), new TranslationDefaultDomainNodeVisitor()];
|
||||
@@ -106,10 +94,9 @@ final class TranslationExtension extends AbstractExtension
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|\Stringable|TranslatableInterface|null $message
|
||||
* @param array|string $arguments Can be the locale as a string when $message is a TranslatableInterface
|
||||
* @param array|string $arguments Can be the locale as a string when $message is a TranslatableInterface
|
||||
*/
|
||||
public function trans($message, $arguments = [], string $domain = null, string $locale = null, int $count = null): string
|
||||
public function trans(string|\Stringable|TranslatableInterface|null $message, array|string $arguments = [], string $domain = null, string $locale = null, int $count = null): string
|
||||
{
|
||||
if ($message instanceof TranslatableInterface) {
|
||||
if ([] !== $arguments && !\is_string($arguments)) {
|
||||
|
||||
@@ -24,25 +24,22 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class WebLinkExtension extends AbstractExtension
|
||||
{
|
||||
private $requestStack;
|
||||
private RequestStack $requestStack;
|
||||
|
||||
public function __construct(RequestStack $requestStack)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('link', [$this, 'link']),
|
||||
new TwigFunction('preload', [$this, 'preload']),
|
||||
new TwigFunction('dns_prefetch', [$this, 'dnsPrefetch']),
|
||||
new TwigFunction('preconnect', [$this, 'preconnect']),
|
||||
new TwigFunction('prefetch', [$this, 'prefetch']),
|
||||
new TwigFunction('prerender', [$this, 'prerender']),
|
||||
new TwigFunction('link', $this->link(...)),
|
||||
new TwigFunction('preload', $this->preload(...)),
|
||||
new TwigFunction('dns_prefetch', $this->dnsPrefetch(...)),
|
||||
new TwigFunction('preconnect', $this->preconnect(...)),
|
||||
new TwigFunction('prefetch', $this->prefetch(...)),
|
||||
new TwigFunction('prerender', $this->prerender(...)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -25,26 +25,23 @@ use Twig\TwigFunction;
|
||||
*/
|
||||
final class WorkflowExtension extends AbstractExtension
|
||||
{
|
||||
private $workflowRegistry;
|
||||
private Registry $workflowRegistry;
|
||||
|
||||
public function __construct(Registry $workflowRegistry)
|
||||
{
|
||||
$this->workflowRegistry = $workflowRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions(): array
|
||||
{
|
||||
return [
|
||||
new TwigFunction('workflow_can', [$this, 'canTransition']),
|
||||
new TwigFunction('workflow_transitions', [$this, 'getEnabledTransitions']),
|
||||
new TwigFunction('workflow_transition', [$this, 'getEnabledTransition']),
|
||||
new TwigFunction('workflow_has_marked_place', [$this, 'hasMarkedPlace']),
|
||||
new TwigFunction('workflow_marked_places', [$this, 'getMarkedPlaces']),
|
||||
new TwigFunction('workflow_metadata', [$this, 'getMetadata']),
|
||||
new TwigFunction('workflow_transition_blockers', [$this, 'buildTransitionBlockerList']),
|
||||
new TwigFunction('workflow_can', $this->canTransition(...)),
|
||||
new TwigFunction('workflow_transitions', $this->getEnabledTransitions(...)),
|
||||
new TwigFunction('workflow_transition', $this->getEnabledTransition(...)),
|
||||
new TwigFunction('workflow_has_marked_place', $this->hasMarkedPlace(...)),
|
||||
new TwigFunction('workflow_marked_places', $this->getMarkedPlaces(...)),
|
||||
new TwigFunction('workflow_metadata', $this->getMetadata(...)),
|
||||
new TwigFunction('workflow_transition_blockers', $this->buildTransitionBlockerList(...)),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -102,7 +99,7 @@ final class WorkflowExtension extends AbstractExtension
|
||||
* Use a string (the place name) to get place metadata
|
||||
* Use a Transition instance to get transition metadata
|
||||
*/
|
||||
public function getMetadata(object $subject, string $key, $metadataSubject = null, string $name = null)
|
||||
public function getMetadata(object $subject, string $key, string|Transition $metadataSubject = null, string $name = null): mixed
|
||||
{
|
||||
return $this
|
||||
->workflowRegistry
|
||||
|
||||
@@ -22,24 +22,19 @@ use Twig\TwigFilter;
|
||||
*/
|
||||
final class YamlExtension extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
return [
|
||||
new TwigFilter('yaml_encode', [$this, 'encode']),
|
||||
new TwigFilter('yaml_dump', [$this, 'dump']),
|
||||
new TwigFilter('yaml_encode', $this->encode(...)),
|
||||
new TwigFilter('yaml_dump', $this->dump(...)),
|
||||
];
|
||||
}
|
||||
|
||||
public function encode($input, int $inline = 0, int $dumpObjects = 0): string
|
||||
public function encode(mixed $input, int $inline = 0, int $dumpObjects = 0): string
|
||||
{
|
||||
static $dumper;
|
||||
|
||||
if (null === $dumper) {
|
||||
$dumper = new YamlDumper();
|
||||
}
|
||||
$dumper ??= new YamlDumper();
|
||||
|
||||
if (\defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) {
|
||||
return $dumper->dump($input, $inline, 0, $dumpObjects);
|
||||
@@ -48,7 +43,7 @@ final class YamlExtension extends AbstractExtension
|
||||
return $dumper->dump($input, $inline, 0, false, $dumpObjects);
|
||||
}
|
||||
|
||||
public function dump($value, int $inline = 0, int $dumpObjects = 0): string
|
||||
public function dump(mixed $value, int $inline = 0, int $dumpObjects = 0): string
|
||||
{
|
||||
if (\is_resource($value)) {
|
||||
return '%Resource%';
|
||||
|
||||
@@ -21,15 +21,8 @@ use Twig\Template;
|
||||
*/
|
||||
class TwigRendererEngine extends AbstractRendererEngine
|
||||
{
|
||||
/**
|
||||
* @var Environment
|
||||
*/
|
||||
private $environment;
|
||||
|
||||
/**
|
||||
* @var Template
|
||||
*/
|
||||
private $template;
|
||||
private Environment $environment;
|
||||
private Template $template;
|
||||
|
||||
public function __construct(array $defaultThemes, Environment $environment)
|
||||
{
|
||||
@@ -37,10 +30,7 @@ class TwigRendererEngine extends AbstractRendererEngine
|
||||
$this->environment = $environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderBlock(FormView $view, $resource, string $blockName, array $variables = [])
|
||||
public function renderBlock(FormView $view, mixed $resource, string $blockName, array $variables = []): string
|
||||
{
|
||||
$cacheKey = $view->vars[self::CACHE_KEY_VAR];
|
||||
|
||||
@@ -69,10 +59,8 @@ class TwigRendererEngine extends AbstractRendererEngine
|
||||
* case that the function "block()" is used in the Twig template.
|
||||
*
|
||||
* @see getResourceForBlock()
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName)
|
||||
protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName): bool
|
||||
{
|
||||
// The caller guarantees that $this->resources[$cacheKey][$block] is
|
||||
// not set, but it doesn't have to check whether $this->resources[$cacheKey]
|
||||
@@ -144,21 +132,20 @@ class TwigRendererEngine extends AbstractRendererEngine
|
||||
* to initialize the theme first. Any changes made to
|
||||
* this variable will be kept and be available upon
|
||||
* further calls to this method using the same theme.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function loadResourcesFromTheme(string $cacheKey, &$theme)
|
||||
protected function loadResourcesFromTheme(string $cacheKey, mixed &$theme)
|
||||
{
|
||||
if (!$theme instanceof Template) {
|
||||
/* @var Template $theme */
|
||||
$theme = $this->environment->load($theme)->unwrap();
|
||||
}
|
||||
|
||||
if (null === $this->template) {
|
||||
// Store the first Template instance that we find so that
|
||||
// we can call displayBlock() later on. It doesn't matter *which*
|
||||
// template we use for that, since we pass the used blocks manually
|
||||
// anyway.
|
||||
$this->template = $theme;
|
||||
}
|
||||
// Store the first Template instance that we find so that
|
||||
// we can call displayBlock() later on. It doesn't matter *which*
|
||||
// template we use for that, since we pass the used blocks manually
|
||||
// anyway.
|
||||
$this->template ??= $theme;
|
||||
|
||||
// Use a separate variable for the inheritance traversal, because
|
||||
// theme is a reference and we don't want to change it.
|
||||
|
||||
@@ -11,10 +11,14 @@
|
||||
|
||||
namespace Symfony\Bridge\Twig\Mime;
|
||||
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
use League\HTMLToMarkdown\HtmlConverterInterface;
|
||||
use Symfony\Component\Mime\BodyRendererInterface;
|
||||
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Mime\HtmlToTextConverter\DefaultHtmlToTextConverter;
|
||||
use Symfony\Component\Mime\HtmlToTextConverter\HtmlToTextConverterInterface;
|
||||
use Symfony\Component\Mime\HtmlToTextConverter\LeagueHtmlToMarkdownConverter;
|
||||
use Symfony\Component\Mime\Message;
|
||||
use Symfony\Component\Translation\LocaleSwitcher;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
@@ -22,21 +26,17 @@ use Twig\Environment;
|
||||
*/
|
||||
final class BodyRenderer implements BodyRendererInterface
|
||||
{
|
||||
private $twig;
|
||||
private $context;
|
||||
private $converter;
|
||||
private Environment $twig;
|
||||
private array $context;
|
||||
private HtmlToTextConverterInterface $converter;
|
||||
private ?LocaleSwitcher $localeSwitcher = null;
|
||||
|
||||
public function __construct(Environment $twig, array $context = [])
|
||||
public function __construct(Environment $twig, array $context = [], HtmlToTextConverterInterface $converter = null, LocaleSwitcher $localeSwitcher = null)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
$this->context = $context;
|
||||
if (class_exists(HtmlConverter::class)) {
|
||||
$this->converter = new HtmlConverter([
|
||||
'hard_break' => true,
|
||||
'strip_tags' => true,
|
||||
'remove_nodes' => 'head style',
|
||||
]);
|
||||
}
|
||||
$this->converter = $converter ?: (interface_exists(HtmlConverterInterface::class) ? new LeagueHtmlToMarkdownConverter() : new DefaultHtmlToTextConverter());
|
||||
$this->localeSwitcher = $localeSwitcher;
|
||||
}
|
||||
|
||||
public function render(Message $message): void
|
||||
@@ -45,61 +45,47 @@ final class BodyRenderer implements BodyRendererInterface
|
||||
return;
|
||||
}
|
||||
|
||||
$messageContext = $message->getContext();
|
||||
|
||||
$previousRenderingKey = $messageContext[__CLASS__] ?? null;
|
||||
unset($messageContext[__CLASS__]);
|
||||
$currentRenderingKey = $this->getFingerPrint($message);
|
||||
if ($previousRenderingKey === $currentRenderingKey) {
|
||||
if (null === $message->getTextTemplate() && null === $message->getHtmlTemplate()) {
|
||||
// email has already been rendered
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($messageContext['email'])) {
|
||||
throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message)));
|
||||
$callback = function () use ($message) {
|
||||
$messageContext = $message->getContext();
|
||||
|
||||
if (isset($messageContext['email'])) {
|
||||
throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message)));
|
||||
}
|
||||
|
||||
$vars = array_merge($this->context, $messageContext, [
|
||||
'email' => new WrappedTemplatedEmail($this->twig, $message),
|
||||
]);
|
||||
|
||||
if ($template = $message->getTextTemplate()) {
|
||||
$message->text($this->twig->render($template, $vars));
|
||||
}
|
||||
|
||||
if ($template = $message->getHtmlTemplate()) {
|
||||
$message->html($this->twig->render($template, $vars));
|
||||
}
|
||||
|
||||
$message->markAsRendered();
|
||||
|
||||
// if text body is empty, compute one from the HTML body
|
||||
if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) {
|
||||
$text = $this->converter->convert(\is_resource($html) ? stream_get_contents($html) : $html, $message->getHtmlCharset());
|
||||
$message->text($text, $message->getHtmlCharset());
|
||||
}
|
||||
};
|
||||
|
||||
$locale = $message->getLocale();
|
||||
|
||||
if ($locale && $this->localeSwitcher) {
|
||||
$this->localeSwitcher->runWithLocale($locale, $callback);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$vars = array_merge($this->context, $messageContext, [
|
||||
'email' => new WrappedTemplatedEmail($this->twig, $message),
|
||||
]);
|
||||
|
||||
if ($template = $message->getTextTemplate()) {
|
||||
$message->text($this->twig->render($template, $vars));
|
||||
}
|
||||
|
||||
if ($template = $message->getHtmlTemplate()) {
|
||||
$message->html($this->twig->render($template, $vars));
|
||||
}
|
||||
|
||||
// if text body is empty, compute one from the HTML body
|
||||
if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) {
|
||||
$message->text($this->convertHtmlToText(\is_resource($html) ? stream_get_contents($html) : $html));
|
||||
}
|
||||
$message->context($message->getContext() + [__CLASS__ => $currentRenderingKey]);
|
||||
}
|
||||
|
||||
private function getFingerPrint(TemplatedEmail $message): string
|
||||
{
|
||||
$messageContext = $message->getContext();
|
||||
unset($messageContext[__CLASS__]);
|
||||
|
||||
$payload = [$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()];
|
||||
try {
|
||||
$serialized = serialize($payload);
|
||||
} catch (\Exception $e) {
|
||||
// Serialization of 'Closure' is not allowed
|
||||
// Happens when context contain a closure, in that case, we assume that context always change.
|
||||
$serialized = random_bytes(8);
|
||||
}
|
||||
|
||||
return md5($serialized);
|
||||
}
|
||||
|
||||
private function convertHtmlToText(string $html): string
|
||||
{
|
||||
if (null !== $this->converter) {
|
||||
return $this->converter->convert($html);
|
||||
}
|
||||
|
||||
return strip_tags(preg_replace('{<(head|style)\b.*?</\1>}is', '', $html));
|
||||
$callback();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Symfony\Bridge\Twig\Mime;
|
||||
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||
use Symfony\Component\Mime\Header\Headers;
|
||||
use Symfony\Component\Mime\Part\AbstractPart;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Twig\Extra\CssInliner\CssInlinerExtension;
|
||||
use Twig\Extra\Inky\InkyExtension;
|
||||
use Twig\Extra\Markdown\MarkdownExtension;
|
||||
@@ -28,8 +29,8 @@ class NotificationEmail extends TemplatedEmail
|
||||
public const IMPORTANCE_MEDIUM = 'medium';
|
||||
public const IMPORTANCE_LOW = 'low';
|
||||
|
||||
private $theme = 'default';
|
||||
private $context = [
|
||||
private string $theme = 'default';
|
||||
private array $context = [
|
||||
'importance' => self::IMPORTANCE_LOW,
|
||||
'content' => '',
|
||||
'exception' => false,
|
||||
@@ -37,8 +38,9 @@ class NotificationEmail extends TemplatedEmail
|
||||
'action_url' => null,
|
||||
'markdown' => false,
|
||||
'raw' => false,
|
||||
'footer_text' => 'Notification e-mail sent by Symfony',
|
||||
'footer_text' => 'Notification email sent by Symfony',
|
||||
];
|
||||
private bool $rendered = false;
|
||||
|
||||
public function __construct(Headers $headers = null, AbstractPart $body = null)
|
||||
{
|
||||
@@ -52,7 +54,7 @@ class NotificationEmail extends TemplatedEmail
|
||||
}
|
||||
|
||||
if ($missingPackages) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available; try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages))));
|
||||
throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available. Try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages))));
|
||||
}
|
||||
|
||||
parent::__construct($headers, $body);
|
||||
@@ -72,7 +74,7 @@ class NotificationEmail extends TemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function markAsPublic(): self
|
||||
public function markAsPublic(): static
|
||||
{
|
||||
$this->context['importance'] = null;
|
||||
$this->context['footer_text'] = null;
|
||||
@@ -83,10 +85,10 @@ class NotificationEmail extends TemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function markdown(string $content)
|
||||
public function markdown(string $content): static
|
||||
{
|
||||
if (!class_exists(MarkdownExtension::class)) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available; try running "composer require twig/markdown-extra".', __METHOD__));
|
||||
throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available. Try running "composer require twig/markdown-extra".', __METHOD__));
|
||||
}
|
||||
|
||||
$this->context['markdown'] = true;
|
||||
@@ -97,7 +99,7 @@ class NotificationEmail extends TemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function content(string $content, bool $raw = false)
|
||||
public function content(string $content, bool $raw = false): static
|
||||
{
|
||||
$this->context['content'] = $content;
|
||||
$this->context['raw'] = $raw;
|
||||
@@ -108,7 +110,7 @@ class NotificationEmail extends TemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function action(string $text, string $url)
|
||||
public function action(string $text, string $url): static
|
||||
{
|
||||
$this->context['action_text'] = $text;
|
||||
$this->context['action_url'] = $url;
|
||||
@@ -119,7 +121,7 @@ class NotificationEmail extends TemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function importance(string $importance)
|
||||
public function importance(string $importance): static
|
||||
{
|
||||
$this->context['importance'] = $importance;
|
||||
|
||||
@@ -127,20 +129,14 @@ class NotificationEmail extends TemplatedEmail
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Throwable|FlattenException $exception
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function exception($exception)
|
||||
public function exception(\Throwable|FlattenException $exception): static
|
||||
{
|
||||
if (!$exception instanceof \Throwable && !$exception instanceof FlattenException) {
|
||||
throw new \LogicException(sprintf('"%s" accepts "%s" or "%s" instances.', __METHOD__, \Throwable::class, FlattenException::class));
|
||||
}
|
||||
|
||||
$exceptionAsString = $this->getExceptionAsString($exception);
|
||||
|
||||
$this->context['exception'] = true;
|
||||
$this->attach($exceptionAsString, 'exception.txt', 'text/plain');
|
||||
$this->addPart(new DataPart($exceptionAsString, 'exception.txt', 'text/plain'));
|
||||
$this->importance(self::IMPORTANCE_URGENT);
|
||||
|
||||
if (!$this->getSubject()) {
|
||||
@@ -153,7 +149,7 @@ class NotificationEmail extends TemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function theme(string $theme)
|
||||
public function theme(string $theme): static
|
||||
{
|
||||
$this->theme = $theme;
|
||||
|
||||
@@ -183,6 +179,18 @@ class NotificationEmail extends TemplatedEmail
|
||||
return array_merge($this->context, parent::getContext());
|
||||
}
|
||||
|
||||
public function isRendered(): bool
|
||||
{
|
||||
return $this->rendered;
|
||||
}
|
||||
|
||||
public function markAsRendered(): void
|
||||
{
|
||||
parent::markAsRendered();
|
||||
|
||||
$this->rendered = true;
|
||||
}
|
||||
|
||||
public function getPreparedHeaders(): Headers
|
||||
{
|
||||
$headers = parent::getPreparedHeaders();
|
||||
@@ -198,20 +206,15 @@ class NotificationEmail extends TemplatedEmail
|
||||
|
||||
private function determinePriority(string $importance): int
|
||||
{
|
||||
switch ($importance) {
|
||||
case self::IMPORTANCE_URGENT:
|
||||
return self::PRIORITY_HIGHEST;
|
||||
case self::IMPORTANCE_HIGH:
|
||||
return self::PRIORITY_HIGH;
|
||||
case self::IMPORTANCE_MEDIUM:
|
||||
return self::PRIORITY_NORMAL;
|
||||
case self::IMPORTANCE_LOW:
|
||||
default:
|
||||
return self::PRIORITY_LOW;
|
||||
}
|
||||
return match ($importance) {
|
||||
self::IMPORTANCE_URGENT => self::PRIORITY_HIGHEST,
|
||||
self::IMPORTANCE_HIGH => self::PRIORITY_HIGH,
|
||||
self::IMPORTANCE_MEDIUM => self::PRIORITY_NORMAL,
|
||||
default => self::PRIORITY_LOW,
|
||||
};
|
||||
}
|
||||
|
||||
private function getExceptionAsString($exception): string
|
||||
private function getExceptionAsString(\Throwable|FlattenException $exception): string
|
||||
{
|
||||
if (class_exists(FlattenException::class)) {
|
||||
$exception = $exception instanceof FlattenException ? $exception : FlattenException::createFromThrowable($exception);
|
||||
@@ -219,7 +222,7 @@ class NotificationEmail extends TemplatedEmail
|
||||
return $exception->getAsString();
|
||||
}
|
||||
|
||||
$message = \get_class($exception);
|
||||
$message = $exception::class;
|
||||
if ('' !== $exception->getMessage()) {
|
||||
$message .= ': '.$exception->getMessage();
|
||||
}
|
||||
@@ -235,7 +238,7 @@ class NotificationEmail extends TemplatedEmail
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [$this->context, $this->theme, parent::__serialize()];
|
||||
return [$this->context, $this->theme, $this->rendered, parent::__serialize()];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,7 +246,9 @@ class NotificationEmail extends TemplatedEmail
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
if (3 === \count($data)) {
|
||||
if (4 === \count($data)) {
|
||||
[$this->context, $this->theme, $this->rendered, $parentData] = $data;
|
||||
} elseif (3 === \count($data)) {
|
||||
[$this->context, $this->theme, $parentData] = $data;
|
||||
} else {
|
||||
// Backwards compatibility for deserializing data structures that were serialized without the theme
|
||||
|
||||
@@ -18,14 +18,15 @@ use Symfony\Component\Mime\Email;
|
||||
*/
|
||||
class TemplatedEmail extends Email
|
||||
{
|
||||
private $htmlTemplate;
|
||||
private $textTemplate;
|
||||
private $context = [];
|
||||
private ?string $htmlTemplate = null;
|
||||
private ?string $textTemplate = null;
|
||||
private ?string $locale = null;
|
||||
private array $context = [];
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function textTemplate(?string $template)
|
||||
public function textTemplate(?string $template): static
|
||||
{
|
||||
$this->textTemplate = $template;
|
||||
|
||||
@@ -35,13 +36,23 @@ class TemplatedEmail extends Email
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function htmlTemplate(?string $template)
|
||||
public function htmlTemplate(?string $template): static
|
||||
{
|
||||
$this->htmlTemplate = $template;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function locale(?string $locale): static
|
||||
{
|
||||
$this->locale = $locale;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTextTemplate(): ?string
|
||||
{
|
||||
return $this->textTemplate;
|
||||
@@ -52,10 +63,15 @@ class TemplatedEmail extends Email
|
||||
return $this->htmlTemplate;
|
||||
}
|
||||
|
||||
public function getLocale(): ?string
|
||||
{
|
||||
return $this->locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function context(array $context)
|
||||
public function context(array $context): static
|
||||
{
|
||||
$this->context = $context;
|
||||
|
||||
@@ -67,12 +83,24 @@ class TemplatedEmail extends Email
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function isRendered(): bool
|
||||
{
|
||||
return null === $this->htmlTemplate && null === $this->textTemplate;
|
||||
}
|
||||
|
||||
public function markAsRendered(): void
|
||||
{
|
||||
$this->textTemplate = null;
|
||||
$this->htmlTemplate = null;
|
||||
$this->context = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [$this->htmlTemplate, $this->textTemplate, $this->context, parent::__serialize()];
|
||||
return [$this->htmlTemplate, $this->textTemplate, $this->context, parent::__serialize(), $this->locale];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +109,7 @@ class TemplatedEmail extends Email
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
[$this->htmlTemplate, $this->textTemplate, $this->context, $parentData] = $data;
|
||||
$this->locale = $data[4] ?? null;
|
||||
|
||||
parent::__unserialize($parentData);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
namespace Symfony\Bridge\Twig\Mime;
|
||||
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\File;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
@@ -21,8 +23,8 @@ use Twig\Environment;
|
||||
*/
|
||||
final class WrappedTemplatedEmail
|
||||
{
|
||||
private $twig;
|
||||
private $message;
|
||||
private Environment $twig;
|
||||
private TemplatedEmail $message;
|
||||
|
||||
public function __construct(Environment $twig, TemplatedEmail $message)
|
||||
{
|
||||
@@ -44,11 +46,8 @@ final class WrappedTemplatedEmail
|
||||
public function image(string $image, string $contentType = null): string
|
||||
{
|
||||
$file = $this->twig->getLoader()->getSourceContext($image);
|
||||
if ($path = $file->getPath()) {
|
||||
$this->message->embedFromPath($path, $image, $contentType);
|
||||
} else {
|
||||
$this->message->embed($file->getCode(), $image, $contentType);
|
||||
}
|
||||
$body = $file->getPath() ? new File($file->getPath()) : $file->getCode();
|
||||
$this->message->addPart((new DataPart($body, $image, $contentType))->asInline());
|
||||
|
||||
return 'cid:'.$image;
|
||||
}
|
||||
@@ -63,17 +62,14 @@ final class WrappedTemplatedEmail
|
||||
public function attach(string $file, string $name = null, string $contentType = null): void
|
||||
{
|
||||
$file = $this->twig->getLoader()->getSourceContext($file);
|
||||
if ($path = $file->getPath()) {
|
||||
$this->message->attachFromPath($path, $name, $contentType);
|
||||
} else {
|
||||
$this->message->attach($file->getCode(), $name, $contentType);
|
||||
}
|
||||
$body = $file->getPath() ? new File($file->getPath()) : $file->getCode();
|
||||
$this->message->addPart(new DataPart($body, $name, $contentType));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setSubject(string $subject): self
|
||||
public function setSubject(string $subject): static
|
||||
{
|
||||
$this->message->subject($subject);
|
||||
|
||||
@@ -88,7 +84,7 @@ final class WrappedTemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setReturnPath(string $address): self
|
||||
public function setReturnPath(string $address): static
|
||||
{
|
||||
$this->message->returnPath($address);
|
||||
|
||||
@@ -103,7 +99,7 @@ final class WrappedTemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addFrom(string $address, string $name = ''): self
|
||||
public function addFrom(string $address, string $name = ''): static
|
||||
{
|
||||
$this->message->addFrom(new Address($address, $name));
|
||||
|
||||
@@ -121,7 +117,7 @@ final class WrappedTemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addReplyTo(string $address): self
|
||||
public function addReplyTo(string $address): static
|
||||
{
|
||||
$this->message->addReplyTo($address);
|
||||
|
||||
@@ -139,7 +135,7 @@ final class WrappedTemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addTo(string $address, string $name = ''): self
|
||||
public function addTo(string $address, string $name = ''): static
|
||||
{
|
||||
$this->message->addTo(new Address($address, $name));
|
||||
|
||||
@@ -157,7 +153,7 @@ final class WrappedTemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addCc(string $address, string $name = ''): self
|
||||
public function addCc(string $address, string $name = ''): static
|
||||
{
|
||||
$this->message->addCc(new Address($address, $name));
|
||||
|
||||
@@ -175,7 +171,7 @@ final class WrappedTemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addBcc(string $address, string $name = ''): self
|
||||
public function addBcc(string $address, string $name = ''): static
|
||||
{
|
||||
$this->message->addBcc(new Address($address, $name));
|
||||
|
||||
@@ -193,7 +189,7 @@ final class WrappedTemplatedEmail
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setPriority(int $priority): self
|
||||
public function setPriority(int $priority): static
|
||||
{
|
||||
$this->message->priority($priority);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ use Twig\Node\Node;
|
||||
*/
|
||||
final class DumpNode extends Node
|
||||
{
|
||||
private $varPrefix;
|
||||
private string $varPrefix;
|
||||
|
||||
public function __construct(string $varPrefix, ?Node $values, int $lineno, string $tag = null)
|
||||
{
|
||||
|
||||
@@ -16,9 +16,9 @@ namespace Symfony\Bridge\Twig\NodeVisitor;
|
||||
*/
|
||||
class Scope
|
||||
{
|
||||
private $parent;
|
||||
private $data = [];
|
||||
private $left = false;
|
||||
private ?self $parent;
|
||||
private array $data = [];
|
||||
private bool $left = false;
|
||||
|
||||
public function __construct(self $parent = null)
|
||||
{
|
||||
@@ -27,20 +27,16 @@ class Scope
|
||||
|
||||
/**
|
||||
* Opens a new child scope.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function enter()
|
||||
public function enter(): self
|
||||
{
|
||||
return new self($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes current scope and returns parent one.
|
||||
*
|
||||
* @return self|null
|
||||
*/
|
||||
public function leave()
|
||||
public function leave(): ?self
|
||||
{
|
||||
$this->left = true;
|
||||
|
||||
@@ -54,7 +50,7 @@ class Scope
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function set(string $key, $value)
|
||||
public function set(string $key, mixed $value): static
|
||||
{
|
||||
if ($this->left) {
|
||||
throw new \LogicException('Left scope is not mutable.');
|
||||
@@ -67,10 +63,8 @@ class Scope
|
||||
|
||||
/**
|
||||
* Tests if a data is visible from current scope.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $key)
|
||||
public function has(string $key): bool
|
||||
{
|
||||
if (\array_key_exists($key, $this->data)) {
|
||||
return true;
|
||||
@@ -85,10 +79,8 @@ class Scope
|
||||
|
||||
/**
|
||||
* Returns data visible from current scope.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key, $default = null)
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
if (\array_key_exists($key, $this->data)) {
|
||||
return $this->data[$key];
|
||||
|
||||
@@ -30,16 +30,13 @@ use Twig\NodeVisitor\AbstractNodeVisitor;
|
||||
*/
|
||||
final class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
|
||||
{
|
||||
private $scope;
|
||||
private Scope $scope;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->scope = new Scope();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doEnterNode(Node $node, Environment $env): Node
|
||||
{
|
||||
if ($node instanceof BlockNode || $node instanceof ModuleNode) {
|
||||
@@ -86,9 +83,6 @@ final class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doLeaveNode(Node $node, Environment $env): ?Node
|
||||
{
|
||||
if ($node instanceof TransDefaultDomainNode) {
|
||||
@@ -102,9 +96,6 @@ final class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return -10;
|
||||
|
||||
@@ -29,8 +29,8 @@ final class TranslationNodeVisitor extends AbstractNodeVisitor
|
||||
{
|
||||
public const UNDEFINED_DOMAIN = '_undefined';
|
||||
|
||||
private $enabled = false;
|
||||
private $messages = [];
|
||||
private bool $enabled = false;
|
||||
private array $messages = [];
|
||||
|
||||
public function enable(): void
|
||||
{
|
||||
@@ -49,9 +49,6 @@ final class TranslationNodeVisitor extends AbstractNodeVisitor
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doEnterNode(Node $node, Environment $env): Node
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
@@ -59,9 +56,9 @@ final class TranslationNodeVisitor extends AbstractNodeVisitor
|
||||
}
|
||||
|
||||
if (
|
||||
$node instanceof FilterExpression &&
|
||||
'trans' === $node->getNode('filter')->getAttribute('value') &&
|
||||
$node->getNode('node') instanceof ConstantExpression
|
||||
$node instanceof FilterExpression
|
||||
&& 'trans' === $node->getNode('filter')->getAttribute('value')
|
||||
&& $node->getNode('node') instanceof ConstantExpression
|
||||
) {
|
||||
// extract constant nodes with a trans filter
|
||||
$this->messages[] = [
|
||||
@@ -69,8 +66,8 @@ final class TranslationNodeVisitor extends AbstractNodeVisitor
|
||||
$this->getReadDomainFromArguments($node->getNode('arguments'), 1),
|
||||
];
|
||||
} elseif (
|
||||
$node instanceof FunctionExpression &&
|
||||
't' === $node->getAttribute('name')
|
||||
$node instanceof FunctionExpression
|
||||
&& 't' === $node->getAttribute('name')
|
||||
) {
|
||||
$nodeArguments = $node->getNode('arguments');
|
||||
|
||||
@@ -87,10 +84,10 @@ final class TranslationNodeVisitor extends AbstractNodeVisitor
|
||||
$node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null,
|
||||
];
|
||||
} elseif (
|
||||
$node instanceof FilterExpression &&
|
||||
'trans' === $node->getNode('filter')->getAttribute('value') &&
|
||||
$node->getNode('node') instanceof ConcatBinary &&
|
||||
$message = $this->getConcatValueFromNode($node->getNode('node'), null)
|
||||
$node instanceof FilterExpression
|
||||
&& 'trans' === $node->getNode('filter')->getAttribute('value')
|
||||
&& $node->getNode('node') instanceof ConcatBinary
|
||||
&& $message = $this->getConcatValueFromNode($node->getNode('node'), null)
|
||||
) {
|
||||
$this->messages[] = [
|
||||
$message,
|
||||
@@ -101,17 +98,11 @@ final class TranslationNodeVisitor extends AbstractNodeVisitor
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doLeaveNode(Node $node, Environment $env): ?Node
|
||||
{
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
|
||||
@@ -233,30 +233,8 @@
|
||||
{% if required -%}
|
||||
{% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %}
|
||||
{%- endif -%}
|
||||
{% if label is empty -%}
|
||||
{%- if label_format is not empty -%}
|
||||
{% set label = label_format|replace({
|
||||
'%name%': name,
|
||||
'%id%': id,
|
||||
}) %}
|
||||
{%- else -%}
|
||||
{% set label = name|humanize %}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
<{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}>
|
||||
{%- if translation_domain is same as(false) -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label -}}
|
||||
{%- else -%}
|
||||
{{- label|raw -}}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain) -}}
|
||||
{%- else -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain)|raw -}}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{{- block('form_label_content') -}}
|
||||
{% block form_label_errors %}{{- form_errors(form) -}}{% endblock form_label_errors %}</{{ element|default('label') }}>
|
||||
{%- else -%}
|
||||
{%- if errors|length > 0 -%}
|
||||
@@ -287,33 +265,11 @@
|
||||
{% set embed_label_classes = parent_label_class|split(' ')|filter(class => class in ['checkbox-inline', 'radio-inline']) %}
|
||||
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ embed_label_classes|join(' '))|trim}) -%}
|
||||
{% endif %}
|
||||
{%- if label is not same as(false) and label is empty -%}
|
||||
{%- if label_format is not empty -%}
|
||||
{%- set label = label_format|replace({
|
||||
'%name%': name,
|
||||
'%id%': id,
|
||||
}) -%}
|
||||
{%- else -%}
|
||||
{%- set label = name|humanize -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
{{ widget|raw }}
|
||||
<label{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
|
||||
{%- if label is not same as(false) -%}
|
||||
{%- if translation_domain is same as(false) -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label -}}
|
||||
{%- else -%}
|
||||
{{- label|raw -}}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain) -}}
|
||||
{%- else -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain)|raw -}}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{{- block('form_label_content') -}}
|
||||
{%- endif -%}
|
||||
{{- form_errors(form) -}}
|
||||
</label>
|
||||
@@ -357,19 +313,7 @@
|
||||
{%- if help is not empty -%}
|
||||
{%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' form-text text-muted')|trim}) -%}
|
||||
<small id="{{ id }}_help"{% with { attr: help_attr } %}{{ block('attributes') }}{% endwith %}>
|
||||
{%- if translation_domain is same as(false) -%}
|
||||
{%- if help_html is same as(false) -%}
|
||||
{{- help -}}
|
||||
{%- else -%}
|
||||
{{- help|raw -}}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- if help_html is same as(false) -%}
|
||||
{{- help|trans(help_translation_parameters, translation_domain) -}}
|
||||
{%- else -%}
|
||||
{{- help|trans(help_translation_parameters, translation_domain)|raw -}}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{{- block('form_help_content') -}}
|
||||
</small>
|
||||
{%- endif -%}
|
||||
{%- endblock form_help %}
|
||||
|
||||
@@ -209,30 +209,48 @@
|
||||
{%- endblock submit_widget %}
|
||||
|
||||
{%- block checkbox_widget -%}
|
||||
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%}
|
||||
{%- set attr_class = attr_class|default(attr.class|default('')) -%}
|
||||
{%- set row_class = '' -%}
|
||||
{%- if 'btn-check' not in attr_class -%}
|
||||
{%- set attr_class = attr_class ~ ' form-check-input' -%}
|
||||
{%- set row_class = 'form-check' -%}
|
||||
{%- endif -%}
|
||||
{%- set attr = attr|merge({class: attr_class|trim}) -%}
|
||||
{%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%}
|
||||
{%- set row_class = 'form-check' -%}
|
||||
{%- if 'checkbox-inline' in parent_label_class %}
|
||||
{%- set row_class = row_class ~ ' form-check-inline' -%}
|
||||
{% endif -%}
|
||||
{%- if 'checkbox-switch' in parent_label_class %}
|
||||
{%- set row_class = row_class ~ ' form-switch' -%}
|
||||
{% endif -%}
|
||||
<div class="{{ row_class }}">
|
||||
{{- form_label(form, null, { widget: parent() }) -}}
|
||||
</div>
|
||||
{%- if row_class is not empty -%}
|
||||
<div class="{{ row_class }}">
|
||||
{%- endif -%}
|
||||
{{- form_label(form, null, { widget: parent() }) -}}
|
||||
{%- if row_class is not empty -%}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- endblock checkbox_widget %}
|
||||
|
||||
{%- block radio_widget -%}
|
||||
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%}
|
||||
{%- set attr_class = attr_class|default(attr.class|default('')) -%}
|
||||
{%- set row_class = '' -%}
|
||||
{%- if 'btn-check' not in attr_class -%}
|
||||
{%- set attr_class = attr_class ~ ' form-check-input' -%}
|
||||
{%- set row_class = 'form-check' -%}
|
||||
{%- endif -%}
|
||||
{%- set attr = attr|merge({class: attr_class|trim}) -%}
|
||||
{%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%}
|
||||
{%- set row_class = 'form-check' -%}
|
||||
{%- if 'radio-inline' in parent_label_class -%}
|
||||
{%- set row_class = row_class ~ ' form-check-inline' -%}
|
||||
{%- endif -%}
|
||||
<div class="{{ row_class }}">
|
||||
{{- form_label(form, null, { widget: parent() }) -}}
|
||||
</div>
|
||||
{%- if row_class is not empty -%}
|
||||
<div class="{{ row_class }}">
|
||||
{%- endif -%}
|
||||
{{- form_label(form, null, { widget: parent() }) -}}
|
||||
{%- if row_class is not empty -%}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- endblock radio_widget %}
|
||||
|
||||
{%- block choice_widget_collapsed -%}
|
||||
@@ -276,7 +294,11 @@
|
||||
{%- block checkbox_radio_label -%}
|
||||
{#- Do not display the label if widget is not defined in order to prevent double label rendering -#}
|
||||
{%- if widget is defined -%}
|
||||
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%}
|
||||
{%- set label_attr_class = label_attr_class|default(label_attr.class|default('')) -%}
|
||||
{%- if 'btn' not in label_attr_class -%}
|
||||
{%- set label_attr_class = label_attr_class ~ ' form-check-label' -%}
|
||||
{%- endif -%}
|
||||
{%- set label_attr = label_attr|merge({class: label_attr_class|trim}) -%}
|
||||
{%- if not compound -%}
|
||||
{% set label_attr = label_attr|merge({'for': id}) %}
|
||||
{%- endif -%}
|
||||
@@ -286,33 +308,11 @@
|
||||
{%- if parent_label_class is defined -%}
|
||||
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%}
|
||||
{%- endif -%}
|
||||
{%- if label is not same as(false) and label is empty -%}
|
||||
{%- if label_format is not empty -%}
|
||||
{%- set label = label_format|replace({
|
||||
'%name%': name,
|
||||
'%id%': id,
|
||||
}) -%}
|
||||
{%- else -%}
|
||||
{%- set label = name|humanize -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
{{ widget|raw }}
|
||||
<label{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
|
||||
{%- if label is not same as(false) -%}
|
||||
{%- if translation_domain is same as(false) -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label -}}
|
||||
{%- else -%}
|
||||
{{- label|raw -}}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain) -}}
|
||||
{%- else -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain)|raw -}}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{{- block('form_label_content') -}}
|
||||
{%- endif -%}
|
||||
</label>
|
||||
{%- endif -%}
|
||||
@@ -353,7 +353,7 @@
|
||||
{%- block form_errors -%}
|
||||
{%- if errors|length > 0 -%}
|
||||
{%- for error in errors -%}
|
||||
<div class="invalid-feedback d-block">{{ error.message }}</div>
|
||||
<div class="{% if form is not rootform %}invalid-feedback{% else %}alert alert-danger{% endif %} d-block">{{ error.message }}</div>
|
||||
{%- endfor -%}
|
||||
{%- endif %}
|
||||
{%- endblock form_errors %}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{# Attribute "required" is not supported #}
|
||||
{%- set required = false -%}
|
||||
{%- endif -%}
|
||||
<input type="{{ type }}" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
|
||||
<input type="{{ type }}" {{ block('widget_attributes') }}{% if value is not empty %} value="{{ value }}"{% endif %}>
|
||||
{%- endblock form_widget_simple -%}
|
||||
|
||||
{%- block form_widget_compound -%}
|
||||
@@ -61,7 +61,7 @@
|
||||
{%- endif -%}
|
||||
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
|
||||
{%- if placeholder is not none -%}
|
||||
<option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ placeholder != '' ? (translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain)) }}</option>
|
||||
<option value=""{% if placeholder_attr|default({}) %}{% with { attr: placeholder_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if required and value is empty %} selected="selected"{% endif %}>{{ placeholder != '' ? (translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain)) }}</option>
|
||||
{%- endif -%}
|
||||
{%- if preferred_choices|length > 0 -%}
|
||||
{% set options = preferred_choices %}
|
||||
@@ -91,11 +91,11 @@
|
||||
{%- endblock choice_widget_options -%}
|
||||
|
||||
{%- block checkbox_widget -%}
|
||||
<input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
|
||||
<input type="checkbox" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %}>
|
||||
{%- endblock checkbox_widget -%}
|
||||
|
||||
{%- block radio_widget -%}
|
||||
<input type="radio" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
|
||||
<input type="radio" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %}>
|
||||
{%- endblock radio_widget -%}
|
||||
|
||||
{%- block datetime_widget -%}
|
||||
@@ -290,34 +290,38 @@
|
||||
{% if required -%}
|
||||
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
|
||||
{%- endif -%}
|
||||
{% if label is empty -%}
|
||||
{%- if label_format is not empty -%}
|
||||
{% set label = label_format|replace({
|
||||
'%name%': name,
|
||||
'%id%': id,
|
||||
}) %}
|
||||
{%- else -%}
|
||||
{% set label = name|humanize %}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
<{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}>
|
||||
{%- if translation_domain is same as(false) -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label -}}
|
||||
{%- else -%}
|
||||
{{- label|raw -}}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain) -}}
|
||||
{%- else -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain)|raw -}}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{{- block('form_label_content') -}}
|
||||
</{{ element|default('label') }}>
|
||||
{%- endif -%}
|
||||
{%- endblock form_label -%}
|
||||
|
||||
{%- block form_label_content -%}
|
||||
{%- if label is empty -%}
|
||||
{%- if label_format is not empty -%}
|
||||
{% set label = label_format|replace({
|
||||
'%name%': name,
|
||||
'%id%': id,
|
||||
}) %}
|
||||
{%- else -%}
|
||||
{% set label = name|humanize %}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- if translation_domain is same as(false) -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label -}}
|
||||
{%- else -%}
|
||||
{{- label|raw -}}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- if label_html is same as(false) -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain) -}}
|
||||
{%- else -%}
|
||||
{{- label|trans(label_translation_parameters, translation_domain)|raw -}}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endblock form_label_content -%}
|
||||
|
||||
{%- block button_label -%}{%- endblock -%}
|
||||
|
||||
{# Help #}
|
||||
@@ -325,24 +329,28 @@
|
||||
{% block form_help -%}
|
||||
{%- if help is not empty -%}
|
||||
{%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-text')|trim}) -%}
|
||||
<p id="{{ id }}_help"{% with { attr: help_attr } %}{{ block('attributes') }}{% endwith %}>
|
||||
{%- if translation_domain is same as(false) -%}
|
||||
{%- if help_html is same as(false) -%}
|
||||
{{- help -}}
|
||||
{%- else -%}
|
||||
{{- help|raw -}}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- if help_html is same as(false) -%}
|
||||
{{- help|trans(help_translation_parameters, translation_domain) -}}
|
||||
{%- else -%}
|
||||
{{- help|trans(help_translation_parameters, translation_domain)|raw -}}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
</p>
|
||||
<{{ element|default('div') }} id="{{ id }}_help"{% with { attr: help_attr } %}{{ block('attributes') }}{% endwith %}>
|
||||
{{- block('form_help_content') -}}
|
||||
</{{ element|default('div') }}>
|
||||
{%- endif -%}
|
||||
{%- endblock form_help %}
|
||||
|
||||
{% block form_help_content -%}
|
||||
{%- if translation_domain is same as(false) -%}
|
||||
{%- if help_html is same as(false) -%}
|
||||
{{- help -}}
|
||||
{%- else -%}
|
||||
{{- help|raw -}}
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
{%- if help_html is same as(false) -%}
|
||||
{{- help|trans(help_translation_parameters, translation_domain) -}}
|
||||
{%- else -%}
|
||||
{{- help|trans(help_translation_parameters, translation_domain)|raw -}}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- endblock form_help_content %}
|
||||
|
||||
{# Rows #}
|
||||
|
||||
{%- block repeated_row -%}
|
||||
@@ -394,7 +402,7 @@
|
||||
{%- endif -%}
|
||||
<form{% if name != '' %} name="{{ name }}"{% endif %} method="{{ form_method|lower }}"{% if action != '' %} action="{{ action }}"{% endif %}{{ block('attributes') }}{% if multipart %} enctype="multipart/form-data"{% endif %}>
|
||||
{%- if form_method != method -%}
|
||||
<input type="hidden" name="_method" value="{{ method }}" />
|
||||
<input type="hidden" name="_method" value="{{ method }}">
|
||||
{%- endif -%}
|
||||
{%- endblock form_start -%}
|
||||
|
||||
@@ -432,7 +440,7 @@
|
||||
{%- endif -%}
|
||||
|
||||
{%- if form_method != method -%}
|
||||
<input type="hidden" name="_method" value="{{ method }}" />
|
||||
<input type="hidden" name="_method" value="{{ method }}">
|
||||
{%- endif -%}
|
||||
{% endif -%}
|
||||
{% endblock form_rest %}
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
{%- endif -%}
|
||||
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple" data-customforms="disabled"{% endif %}>
|
||||
{% if placeholder is not none -%}
|
||||
<option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain) }}</option>
|
||||
<option value=""{% if placeholder_attr|default({}) %}{% with { attr: placeholder_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if required and value is empty %} selected="selected"{% endif %}>{{ translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain) }}</option>
|
||||
{%- endif %}
|
||||
{%- if preferred_choices|length > 0 -%}
|
||||
{% set options = preferred_choices %}
|
||||
|
||||
@@ -29,9 +29,6 @@ use Twig\TokenParser\AbstractTokenParser;
|
||||
*/
|
||||
final class DumpTokenParser extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(Token $token): Node
|
||||
{
|
||||
$values = null;
|
||||
@@ -43,9 +40,6 @@ final class DumpTokenParser extends AbstractTokenParser
|
||||
return new DumpNode($this->parser->getVarName(), $values, $token->getLine(), $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTag(): string
|
||||
{
|
||||
return 'dump';
|
||||
|
||||
@@ -24,9 +24,6 @@ use Twig\TokenParser\AbstractTokenParser;
|
||||
*/
|
||||
final class FormThemeTokenParser extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(Token $token): Node
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
@@ -54,9 +51,6 @@ final class FormThemeTokenParser extends AbstractTokenParser
|
||||
return new FormThemeNode($form, $resources, $lineno, $this->getTag(), $only);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTag(): string
|
||||
{
|
||||
return 'form_theme';
|
||||
|
||||
@@ -24,7 +24,7 @@ use Twig\TokenParser\AbstractTokenParser;
|
||||
*/
|
||||
final class StopwatchTokenParser extends AbstractTokenParser
|
||||
{
|
||||
protected $stopwatchIsAvailable;
|
||||
private bool $stopwatchIsAvailable;
|
||||
|
||||
public function __construct(bool $stopwatchIsAvailable)
|
||||
{
|
||||
@@ -42,7 +42,7 @@ final class StopwatchTokenParser extends AbstractTokenParser
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
// {% endstopwatch %}
|
||||
$body = $this->parser->subparse([$this, 'decideStopwatchEnd'], true);
|
||||
$body = $this->parser->subparse($this->decideStopwatchEnd(...), true);
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
if ($this->stopwatchIsAvailable) {
|
||||
|
||||
@@ -23,9 +23,6 @@ use Twig\TokenParser\AbstractTokenParser;
|
||||
*/
|
||||
final class TransDefaultDomainTokenParser extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(Token $token): Node
|
||||
{
|
||||
$expr = $this->parser->getExpressionParser()->parseExpression();
|
||||
@@ -35,9 +32,6 @@ final class TransDefaultDomainTokenParser extends AbstractTokenParser
|
||||
return new TransDefaultDomainNode($expr, $token->getLine(), $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTag(): string
|
||||
{
|
||||
return 'trans_default_domain';
|
||||
|
||||
@@ -27,9 +27,6 @@ use Twig\TokenParser\AbstractTokenParser;
|
||||
*/
|
||||
final class TransTokenParser extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(Token $token): Node
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
@@ -69,7 +66,7 @@ final class TransTokenParser extends AbstractTokenParser
|
||||
|
||||
// {% trans %}message{% endtrans %}
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse([$this, 'decideTransFork'], true);
|
||||
$body = $this->parser->subparse($this->decideTransFork(...), true);
|
||||
|
||||
if (!$body instanceof TextNode && !$body instanceof AbstractExpression) {
|
||||
throw new SyntaxError('A message inside a trans tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext());
|
||||
@@ -85,9 +82,6 @@ final class TransTokenParser extends AbstractTokenParser
|
||||
return $token->test(['endtrans']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTag(): string
|
||||
{
|
||||
return 'trans';
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Bridge\Twig\Translation;
|
||||
|
||||
use Symfony\Bridge\Twig\Extension\TranslationExtension;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Translation\Extractor\AbstractFileExtractor;
|
||||
use Symfony\Component\Translation\Extractor\ExtractorInterface;
|
||||
@@ -29,19 +30,15 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* Default domain for found messages.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $defaultDomain = 'messages';
|
||||
private string $defaultDomain = 'messages';
|
||||
|
||||
/**
|
||||
* Prefix for found message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $prefix = '';
|
||||
private string $prefix = '';
|
||||
|
||||
private $twig;
|
||||
private Environment $twig;
|
||||
|
||||
public function __construct(Environment $twig)
|
||||
{
|
||||
@@ -49,30 +46,33 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
public function extract($resource, MessageCatalogue $catalogue)
|
||||
{
|
||||
foreach ($this->extractFiles($resource) as $file) {
|
||||
try {
|
||||
$this->extractTemplate(file_get_contents($file->getPathname()), $catalogue);
|
||||
} catch (Error $e) {
|
||||
} catch (Error) {
|
||||
// ignore errors, these should be fixed by using the linter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return void
|
||||
*/
|
||||
public function setPrefix(string $prefix)
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
protected function extractTemplate(string $template, MessageCatalogue $catalogue)
|
||||
{
|
||||
$visitor = $this->twig->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->getTranslationNodeVisitor();
|
||||
$visitor = $this->twig->getExtension(TranslationExtension::class)->getTranslationNodeVisitor();
|
||||
$visitor->enable();
|
||||
|
||||
$this->twig->parse($this->twig->tokenize(new Source($template, '')));
|
||||
@@ -84,18 +84,12 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface
|
||||
$visitor->disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function canBeExtracted(string $file)
|
||||
protected function canBeExtracted(string $file): bool
|
||||
{
|
||||
return $this->isFile($file) && 'twig' === pathinfo($file, \PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function extractFromDirectory($directory)
|
||||
protected function extractFromDirectory($directory): iterable
|
||||
{
|
||||
$finder = new Finder();
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@ class UndefinedCallableHandler
|
||||
{
|
||||
private const FILTER_COMPONENTS = [
|
||||
'humanize' => 'form',
|
||||
'form_encode_currency' => 'form',
|
||||
'trans' => 'translation',
|
||||
'sanitize_html' => 'html-sanitizer',
|
||||
'yaml_encode' => 'yaml',
|
||||
'yaml_dump' => 'yaml',
|
||||
];
|
||||
@@ -32,6 +34,7 @@ class UndefinedCallableHandler
|
||||
private const FUNCTION_COMPONENTS = [
|
||||
'asset' => 'asset',
|
||||
'asset_version' => 'asset',
|
||||
'importmap' => 'asset-mapper',
|
||||
'dump' => 'debug-bundle',
|
||||
'encore_entry_link_tags' => 'webpack-encore-bundle',
|
||||
'encore_entry_script_tags' => 'webpack-encore-bundle',
|
||||
@@ -46,6 +49,13 @@ class UndefinedCallableHandler
|
||||
'form_start' => 'form',
|
||||
'form_end' => 'form',
|
||||
'csrf_token' => 'form',
|
||||
'form_parent' => 'form',
|
||||
'field_name' => 'form',
|
||||
'field_value' => 'form',
|
||||
'field_label' => 'form',
|
||||
'field_help' => 'form',
|
||||
'field_errors' => 'form',
|
||||
'field_choices' => 'form',
|
||||
'logout_url' => 'security-http',
|
||||
'logout_path' => 'security-http',
|
||||
'is_granted' => 'security-core',
|
||||
@@ -57,11 +67,15 @@ class UndefinedCallableHandler
|
||||
'prerender' => 'web-link',
|
||||
'workflow_can' => 'workflow',
|
||||
'workflow_transitions' => 'workflow',
|
||||
'workflow_transition' => 'workflow',
|
||||
'workflow_has_marked_place' => 'workflow',
|
||||
'workflow_marked_places' => 'workflow',
|
||||
'workflow_metadata' => 'workflow',
|
||||
'workflow_transition_blockers' => 'workflow',
|
||||
];
|
||||
|
||||
private const FULL_STACK_ENABLE = [
|
||||
'html-sanitizer' => 'enable "framework.html_sanitizer"',
|
||||
'form' => 'enable "framework.form"',
|
||||
'security-core' => 'add the "SecurityBundle"',
|
||||
'security-http' => 'add the "SecurityBundle"',
|
||||
@@ -69,10 +83,7 @@ class UndefinedCallableHandler
|
||||
'workflow' => 'enable "framework.workflows"',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return TwigFilter|false
|
||||
*/
|
||||
public static function onUndefinedFilter(string $name)
|
||||
public static function onUndefinedFilter(string $name): TwigFilter|false
|
||||
{
|
||||
if (!isset(self::FILTER_COMPONENTS[$name])) {
|
||||
return false;
|
||||
@@ -81,17 +92,14 @@ class UndefinedCallableHandler
|
||||
throw new SyntaxError(self::onUndefined($name, 'filter', self::FILTER_COMPONENTS[$name]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TwigFunction|false
|
||||
*/
|
||||
public static function onUndefinedFunction(string $name)
|
||||
public static function onUndefinedFunction(string $name): TwigFunction|false
|
||||
{
|
||||
if (!isset(self::FUNCTION_COMPONENTS[$name])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ('webpack-encore-bundle' === self::FUNCTION_COMPONENTS[$name]) {
|
||||
return new TwigFunction($name, static function () { return ''; });
|
||||
return new TwigFunction($name, static fn () => '');
|
||||
}
|
||||
|
||||
throw new SyntaxError(self::onUndefined($name, 'function', self::FUNCTION_COMPONENTS[$name]));
|
||||
|
||||
@@ -16,38 +16,40 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-php80": "^1.16",
|
||||
"symfony/translation-contracts": "^1.1|^2|^3",
|
||||
"php": ">=8.1",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/translation-contracts": "^2.5|^3",
|
||||
"twig/twig": "^2.13|^3.0.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.12|^2",
|
||||
"egulias/email-validator": "^2.1.10|^3|^4",
|
||||
"league/html-to-markdown": "^5.0",
|
||||
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
|
||||
"symfony/asset": "^4.4|^5.0|^6.0",
|
||||
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
|
||||
"symfony/finder": "^4.4|^5.0|^6.0",
|
||||
"symfony/form": "^5.4.21|^6.2.7",
|
||||
"symfony/http-foundation": "^5.3|^6.0",
|
||||
"symfony/http-kernel": "^4.4|^5.0|^6.0",
|
||||
"symfony/intl": "^4.4|^5.0|^6.0",
|
||||
"symfony/mime": "^5.2|^6.0",
|
||||
"symfony/asset": "^5.4|^6.0|^7.0",
|
||||
"symfony/asset-mapper": "^6.3|^7.0",
|
||||
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
|
||||
"symfony/finder": "^5.4|^6.0|^7.0",
|
||||
"symfony/form": "^6.4|^7.0",
|
||||
"symfony/html-sanitizer": "^6.1|^7.0",
|
||||
"symfony/http-foundation": "^5.4|^6.0|^7.0",
|
||||
"symfony/http-kernel": "^6.4|^7.0",
|
||||
"symfony/intl": "^5.4|^6.0|^7.0",
|
||||
"symfony/mime": "^6.2|^7.0",
|
||||
"symfony/polyfill-intl-icu": "~1.0",
|
||||
"symfony/property-info": "^4.4|^5.1|^6.0",
|
||||
"symfony/routing": "^4.4|^5.0|^6.0",
|
||||
"symfony/translation": "^5.2|^6.0",
|
||||
"symfony/yaml": "^4.4|^5.0|^6.0",
|
||||
"symfony/property-info": "^5.4|^6.0|^7.0",
|
||||
"symfony/routing": "^5.4|^6.0|^7.0",
|
||||
"symfony/translation": "^6.1|^7.0",
|
||||
"symfony/yaml": "^5.4|^6.0|^7.0",
|
||||
"symfony/security-acl": "^2.8|^3.0",
|
||||
"symfony/security-core": "^4.4|^5.0|^6.0",
|
||||
"symfony/security-csrf": "^4.4|^5.0|^6.0",
|
||||
"symfony/security-http": "^4.4|^5.0|^6.0",
|
||||
"symfony/serializer": "^5.2|^6.0",
|
||||
"symfony/stopwatch": "^4.4|^5.0|^6.0",
|
||||
"symfony/console": "^5.3|^6.0",
|
||||
"symfony/expression-language": "^4.4|^5.0|^6.0",
|
||||
"symfony/web-link": "^4.4|^5.0|^6.0",
|
||||
"symfony/workflow": "^5.2|^6.0",
|
||||
"symfony/security-core": "^5.4|^6.0|^7.0",
|
||||
"symfony/security-csrf": "^5.4|^6.0|^7.0",
|
||||
"symfony/security-http": "^5.4|^6.0|^7.0",
|
||||
"symfony/serializer": "^6.4|^7.0",
|
||||
"symfony/stopwatch": "^5.4|^6.0|^7.0",
|
||||
"symfony/console": "^5.4|^6.0|^7.0",
|
||||
"symfony/expression-language": "^5.4|^6.0|^7.0",
|
||||
"symfony/web-link": "^5.4|^6.0|^7.0",
|
||||
"symfony/workflow": "^5.4|^6.0|^7.0",
|
||||
"twig/cssinliner-extra": "^2.12|^3",
|
||||
"twig/inky-extra": "^2.12|^3",
|
||||
"twig/markdown-extra": "^2.12|^3"
|
||||
@@ -55,28 +57,14 @@
|
||||
"conflict": {
|
||||
"phpdocumentor/reflection-docblock": "<3.2.2",
|
||||
"phpdocumentor/type-resolver": "<1.4.0",
|
||||
"symfony/console": "<5.3",
|
||||
"symfony/form": "<5.4.21|>=6,<6.2.7",
|
||||
"symfony/http-foundation": "<5.3",
|
||||
"symfony/http-kernel": "<4.4",
|
||||
"symfony/translation": "<5.2",
|
||||
"symfony/workflow": "<5.2"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/finder": "",
|
||||
"symfony/asset": "For using the AssetExtension",
|
||||
"symfony/form": "For using the FormExtension",
|
||||
"symfony/http-kernel": "For using the HttpKernelExtension",
|
||||
"symfony/routing": "For using the RoutingExtension",
|
||||
"symfony/translation": "For using the TranslationExtension",
|
||||
"symfony/yaml": "For using the YamlExtension",
|
||||
"symfony/security-core": "For using the SecurityExtension",
|
||||
"symfony/security-csrf": "For using the CsrfExtension",
|
||||
"symfony/security-http": "For using the LogoutUrlExtension",
|
||||
"symfony/stopwatch": "For using the StopwatchExtension",
|
||||
"symfony/var-dumper": "For using the DumpExtension",
|
||||
"symfony/expression-language": "For using the ExpressionExtension",
|
||||
"symfony/web-link": "For using the WebLinkExtension"
|
||||
"symfony/console": "<5.4",
|
||||
"symfony/form": "<6.3",
|
||||
"symfony/http-foundation": "<5.4",
|
||||
"symfony/http-kernel": "<6.4",
|
||||
"symfony/mime": "<6.2",
|
||||
"symfony/serializer": "<6.4",
|
||||
"symfony/translation": "<5.4",
|
||||
"symfony/workflow": "<5.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Bridge\\Twig\\": "" },
|
||||
|
||||
Reference in New Issue
Block a user