Augmentation vers version 3.3.0

This commit is contained in:
Gauvain Boiché
2020-03-31 15:31:03 +02:00
parent d926806907
commit a1864c0414
2618 changed files with 406015 additions and 31377 deletions

View File

@@ -11,12 +11,11 @@
namespace Symfony\Bridge\Twig;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* Exposes some Symfony parameters and services as an "app" global variable.
@@ -25,20 +24,11 @@ use Symfony\Component\Security\Core\SecurityContext;
*/
class AppVariable
{
private $container;
private $tokenStorage;
private $requestStack;
private $environment;
private $debug;
/**
* @deprecated since version 2.7, to be removed in 3.0.
*/
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
}
public function setTokenStorage(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
@@ -60,52 +50,41 @@ class AppVariable
}
/**
* Returns the security context service.
* Returns the current token.
*
* @deprecated since version 2.6, to be removed in 3.0.
* @return TokenInterface|null
*
* @return SecurityContext|null The security context
* @throws \RuntimeException When the TokenStorage is not available
*/
public function getSecurity()
public function getToken()
{
@trigger_error('The "app.security" variable is deprecated since Symfony 2.6 and will be removed in 3.0.', E_USER_DEPRECATED);
if (null === $this->container) {
throw new \RuntimeException('The "app.security" variable is not available.');
if (null === $tokenStorage = $this->tokenStorage) {
throw new \RuntimeException('The "app.token" variable is not available.');
}
if ($this->container->has('security.context')) {
return $this->container->get('security.context');
}
return $tokenStorage->getToken();
}
/**
* Returns the current user.
*
* @return mixed
* @return object|null
*
* @see TokenInterface::getUser()
*/
public function getUser()
{
if (null === $this->tokenStorage) {
if (null === $this->container) {
throw new \RuntimeException('The "app.user" variable is not available.');
} elseif (!$this->container->has('security.context')) {
return;
}
$this->tokenStorage = $this->container->get('security.context');
if (null === $tokenStorage = $this->tokenStorage) {
throw new \RuntimeException('The "app.user" variable is not available.');
}
if (!$token = $this->tokenStorage->getToken()) {
return;
if (!$token = $tokenStorage->getToken()) {
return null;
}
$user = $token->getUser();
if (\is_object($user)) {
return $user;
}
return \is_object($user) ? $user : null;
}
/**
@@ -116,11 +95,7 @@ class AppVariable
public function getRequest()
{
if (null === $this->requestStack) {
if (null === $this->container) {
throw new \RuntimeException('The "app.request" variable is not available.');
}
$this->requestStack = $this->container->get('request_stack');
throw new \RuntimeException('The "app.request" variable is not available.');
}
return $this->requestStack->getCurrentRequest();
@@ -133,13 +108,11 @@ class AppVariable
*/
public function getSession()
{
if (null === $this->requestStack && null === $this->container) {
if (null === $this->requestStack) {
throw new \RuntimeException('The "app.session" variable is not available.');
}
if ($request = $this->getRequest()) {
return $request->getSession();
}
return ($request = $this->getRequest()) ? $request->getSession() : null;
}
/**
@@ -169,4 +142,39 @@ class AppVariable
return $this->debug;
}
/**
* 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)
{
try {
$session = $this->getSession();
if (null === $session) {
return [];
}
} catch (\RuntimeException $e) {
return [];
}
if (null === $types || '' === $types || [] === $types) {
return $session->getFlashBag()->all();
}
if (\is_string($types)) {
return $session->getFlashBag()->get($types);
}
$result = [];
foreach ($types as $type) {
$result[$type] = $session->getFlashBag()->get($type);
}
return $result;
}
}

View File

@@ -12,12 +12,14 @@
namespace Symfony\Bridge\Twig\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputArgument;
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 Twig\Environment;
use Twig\Loader\FilesystemLoader;
/**
* Lists twig functions, filters, globals and tests present in the current project.
@@ -26,18 +28,35 @@ use Twig\Environment;
*/
class DebugCommand extends Command
{
protected static $defaultName = 'debug:twig';
private $twig;
private $projectDir;
/**
* {@inheritdoc}
* @param Environment $twig
* @param string|null $projectDir
*/
public function __construct($name = 'debug:twig')
public function __construct($twig = null, $projectDir = null)
{
parent::__construct($name);
if (!$twig instanceof Environment) {
@trigger_error(sprintf('Passing a command name as the first argument of "%s()" is deprecated since Symfony 3.4 and support for it will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED);
parent::__construct($twig);
return;
}
parent::__construct();
$this->twig = $twig;
$this->projectDir = $projectDir;
}
public function setTwigEnvironment(Environment $twig)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
$this->twig = $twig;
}
@@ -46,16 +65,18 @@ class DebugCommand extends Command
*/
protected function getTwigEnvironment()
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
return $this->twig;
}
protected function configure()
{
$this
->setDefinition(array(
->setDefinition([
new InputArgument('filter', InputArgument::OPTIONAL, 'Show details for all entries matching this filter'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'),
))
])
->setDescription('Shows a list of twig functions, filters, globals and tests')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command outputs a list of twig functions,
@@ -80,36 +101,50 @@ EOF
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$twig = $this->getTwigEnvironment();
$decorated = $io->isDecorated();
if (null === $twig) {
$io->error('The Twig environment needs to be set.');
// BC to be removed in 4.0
if (__CLASS__ !== \get_class($this)) {
$r = new \ReflectionMethod($this, 'getTwigEnvironment');
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
@trigger_error(sprintf('Usage of method "%s" is deprecated since Symfony 3.4 and will no longer be supported in 4.0. Construct the command with its required arguments instead.', \get_class($this).'::getTwigEnvironment'), E_USER_DEPRECATED);
return 1;
$this->twig = $this->getTwigEnvironment();
}
}
if (null === $this->twig) {
throw new \RuntimeException('The Twig environment needs to be set.');
}
$types = array('functions', 'filters', 'tests', 'globals');
$filter = $input->getArgument('filter');
$types = ['functions', 'filters', 'tests', 'globals'];
if ('json' === $input->getOption('format')) {
$data = array();
$data = [];
foreach ($types as $type) {
foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) {
$data[$type][$name] = $this->getMetadata($type, $entity);
foreach ($this->twig->{'get'.ucfirst($type)}() as $name => $entity) {
if (!$filter || false !== strpos($name, $filter)) {
$data[$type][$name] = $this->getMetadata($type, $entity);
}
}
}
$data['tests'] = array_keys($data['tests']);
$io->writeln(json_encode($data));
if (isset($data['tests'])) {
$data['tests'] = array_keys($data['tests']);
}
$data['loader_paths'] = $this->getLoaderPaths();
$data = json_encode($data, JSON_PRETTY_PRINT);
$io->writeln($decorated ? OutputFormatter::escape($data) : $data);
return 0;
}
$filter = $input->getArgument('filter');
foreach ($types as $index => $type) {
$items = array();
foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) {
$items = [];
foreach ($this->twig->{'get'.ucfirst($type)}() as $name => $entity) {
if (!$filter || false !== strpos($name, $filter)) {
$items[$name] = $name.$this->getPrettyMetadata($type, $entity);
$items[$name] = $name.$this->getPrettyMetadata($type, $entity, $decorated);
}
}
@@ -123,25 +158,78 @@ EOF
$io->listing($items);
}
$rows = [];
$firstNamespace = true;
$prevHasSeparator = false;
foreach ($this->getLoaderPaths() as $namespace => $paths) {
if (!$firstNamespace && !$prevHasSeparator && \count($paths) > 1) {
$rows[] = ['', ''];
}
$firstNamespace = false;
foreach ($paths as $path) {
$rows[] = [$namespace, $path.\DIRECTORY_SEPARATOR];
$namespace = '';
}
if (\count($paths) > 1) {
$rows[] = ['', ''];
$prevHasSeparator = true;
} else {
$prevHasSeparator = false;
}
}
if ($prevHasSeparator) {
array_pop($rows);
}
$io->section('Loader Paths');
$io->table(['Namespace', 'Paths'], $rows);
return 0;
}
private function getLoaderPaths()
{
if (!($loader = $this->twig->getLoader()) instanceof FilesystemLoader) {
return [];
}
$loaderPaths = [];
foreach ($loader->getNamespaces() as $namespace) {
$paths = array_map(function ($path) {
if (null !== $this->projectDir && 0 === strpos($path, $this->projectDir)) {
$path = ltrim(substr($path, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR);
}
return $path;
}, $loader->getPaths($namespace));
if (FilesystemLoader::MAIN_NAMESPACE === $namespace) {
$namespace = '(None)';
} else {
$namespace = '@'.$namespace;
}
$loaderPaths[$namespace] = $paths;
}
return $loaderPaths;
}
private function getMetadata($type, $entity)
{
if ('globals' === $type) {
return $entity;
}
if ('tests' === $type) {
return;
return null;
}
if ('functions' === $type || 'filters' === $type) {
$cb = $entity->getCallable();
if (null === $cb) {
return;
return null;
}
if (\is_array($cb)) {
if (!method_exists($cb[0], $cb[1])) {
return;
return null;
}
$refl = new \ReflectionMethod($cb[0], $cb[1]);
} elseif (\is_object($cb) && method_exists($cb, '__invoke')) {
@@ -180,9 +268,11 @@ EOF
return $args;
}
return null;
}
private function getPrettyMetadata($type, $entity)
private function getPrettyMetadata($type, $entity, $decorated)
{
if ('tests' === $type) {
return '';
@@ -194,7 +284,7 @@ EOF
return '(unknown?)';
}
} catch (\UnexpectedValueException $e) {
return ' <error>'.$e->getMessage().'</error>';
return sprintf(' <error>%s</error>', $decorated ? OutputFormatter::escape($e->getMessage()) : $e->getMessage());
}
if ('globals' === $type) {
@@ -202,7 +292,9 @@ EOF
return ' = object('.\get_class($meta).')';
}
return ' = '.substr(@json_encode($meta), 0, 50);
$description = substr(@json_encode($meta), 0, 50);
return sprintf(' = %s', $decorated ? OutputFormatter::escape($description) : $description);
}
if ('functions' === $type) {
@@ -212,5 +304,7 @@ EOF
if ('filters' === $type) {
return $meta ? '('.implode(', ', $meta).')' : '';
}
return null;
}
}

View File

@@ -12,6 +12,8 @@
namespace Symfony\Bridge\Twig\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -31,18 +33,32 @@ use Twig\Source;
*/
class LintCommand extends Command
{
protected static $defaultName = 'lint:twig';
private $twig;
/**
* {@inheritdoc}
* @param Environment $twig
*/
public function __construct($name = 'lint:twig')
public function __construct($twig = null)
{
parent::__construct($name);
if (!$twig instanceof Environment) {
@trigger_error(sprintf('Passing a command name as the first argument of "%s()" is deprecated since Symfony 3.4 and support for it will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), E_USER_DEPRECATED);
parent::__construct($twig);
return;
}
parent::__construct();
$this->twig = $twig;
}
public function setTwigEnvironment(Environment $twig)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
$this->twig = $twig;
}
@@ -51,13 +67,14 @@ class LintCommand extends Command
*/
protected function getTwigEnvironment()
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
return $this->twig;
}
protected function configure()
{
$this
->setAliases(array('twig:lint'))
->setDescription('Lints a template and outputs encountered errors')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
->addArgument('filename', InputArgument::IS_ARRAY)
@@ -87,21 +104,24 @@ EOF
{
$io = new SymfonyStyle($input, $output);
if (false !== strpos($input->getFirstArgument(), ':l')) {
$io->caution('The use of "twig:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:twig" instead.');
// BC to be removed in 4.0
if (__CLASS__ !== \get_class($this)) {
$r = new \ReflectionMethod($this, 'getTwigEnvironment');
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
@trigger_error(sprintf('Usage of method "%s" is deprecated since Symfony 3.4 and will no longer be supported in 4.0. Construct the command with its required arguments instead.', \get_class($this).'::getTwigEnvironment'), E_USER_DEPRECATED);
$this->twig = $this->getTwigEnvironment();
}
}
if (null === $twig = $this->getTwigEnvironment()) {
$io->error('The Twig environment needs to be set.');
return 1;
if (null === $this->twig) {
throw new \RuntimeException('The Twig environment needs to be set.');
}
$filenames = $input->getArgument('filename');
if (0 === \count($filenames)) {
if (0 !== ftell(STDIN)) {
throw new \RuntimeException('Please provide a filename or pipe template content to STDIN.');
throw new RuntimeException('Please provide a filename or pipe template content to STDIN.');
}
$template = '';
@@ -109,20 +129,20 @@ EOF
$template .= fread(STDIN, 1024);
}
return $this->display($input, $output, $io, array($this->validate($twig, $template, uniqid('sf_', true))));
return $this->display($input, $output, $io, [$this->validate($template, uniqid('sf_', true))]);
}
$filesInfo = $this->getFilesInfo($twig, $filenames);
$filesInfo = $this->getFilesInfo($filenames);
return $this->display($input, $output, $io, $filesInfo);
}
private function getFilesInfo(Environment $twig, array $filenames)
private function getFilesInfo(array $filenames)
{
$filesInfo = array();
$filesInfo = [];
foreach ($filenames as $filename) {
foreach ($this->findFiles($filename) as $file) {
$filesInfo[] = $this->validate($twig, file_get_contents($file), $file);
$filesInfo[] = $this->validate(file_get_contents($file), $file);
}
}
@@ -132,30 +152,30 @@ EOF
protected function findFiles($filename)
{
if (is_file($filename)) {
return array($filename);
return [$filename];
} elseif (is_dir($filename)) {
return Finder::create()->files()->in($filename)->name('*.twig');
}
throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename));
throw new RuntimeException(sprintf('File or directory "%s" is not readable', $filename));
}
private function validate(Environment $twig, $template, $file)
private function validate($template, $file)
{
$realLoader = $twig->getLoader();
$realLoader = $this->twig->getLoader();
try {
$temporaryLoader = new ArrayLoader(array((string) $file => $template));
$twig->setLoader($temporaryLoader);
$nodeTree = $twig->parse($twig->tokenize(new Source($template, (string) $file)));
$twig->compile($nodeTree);
$twig->setLoader($realLoader);
$temporaryLoader = new ArrayLoader([(string) $file => $template]);
$this->twig->setLoader($temporaryLoader);
$nodeTree = $this->twig->parse($this->twig->tokenize(new Source($template, (string) $file)));
$this->twig->compile($nodeTree);
$this->twig->setLoader($realLoader);
} catch (Error $e) {
$twig->setLoader($realLoader);
$this->twig->setLoader($realLoader);
return array('template' => $template, 'file' => $file, 'valid' => false, 'exception' => $e);
return ['template' => $template, 'file' => $file, 'line' => $e->getTemplateLine(), 'valid' => false, 'exception' => $e];
}
return array('template' => $template, 'file' => $file, 'valid' => true);
return ['template' => $template, 'file' => $file, 'valid' => true];
}
private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, $files)
@@ -166,7 +186,7 @@ EOF
case 'json':
return $this->displayJson($output, $files);
default:
throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
}
}
@@ -206,7 +226,7 @@ EOF
}
});
$output->writeln(json_encode($filesInfo, \defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES : 0));
$output->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
return min($errors, 1);
}
@@ -241,7 +261,7 @@ EOF
$position = max(0, $line - $context);
$max = min(\count($lines), $line - 1 + $context);
$result = array();
$result = [];
while ($position < $max) {
$result[$position + 1] = $lines[$position];
++$position;

View File

@@ -15,6 +15,8 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Markup;
use Twig\Profiler\Dumper\HtmlDumper;
use Twig\Profiler\Profile;
@@ -27,11 +29,13 @@ use Twig\Profiler\Profile;
class TwigDataCollector extends DataCollector implements LateDataCollectorInterface
{
private $profile;
private $twig;
private $computed;
public function __construct(Profile $profile)
public function __construct(Profile $profile, Environment $twig = null)
{
$this->profile = $profile;
$this->twig = $twig;
}
/**
@@ -41,12 +45,46 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
{
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->profile->reset();
$this->computed = null;
$this->data = [];
}
/**
* {@inheritdoc}
*/
public function lateCollect()
{
$this->data['profile'] = serialize($this->profile);
$this->data['template_paths'] = [];
if (null === $this->twig) {
return;
}
$templateFinder = function (Profile $profile) use (&$templateFinder) {
if ($profile->isTemplate()) {
try {
$template = $this->twig->load($name = $profile->getName());
} catch (LoaderError $e) {
$template = null;
}
if (null !== $template && '' !== $path = $template->getSourceContext()->getPath()) {
$this->data['template_paths'][$name] = $path;
}
}
foreach ($profile as $p) {
$templateFinder($p);
}
};
$templateFinder($this->profile);
}
public function getTime()
@@ -59,6 +97,11 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
return $this->getComputedData('template_count');
}
public function getTemplatePaths()
{
return $this->data['template_paths'];
}
public function getTemplates()
{
return $this->getComputedData('templates');
@@ -80,15 +123,15 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
$dump = $dumper->dump($this->getProfile());
// needed to remove the hardcoded CSS styles
$dump = str_replace(array(
$dump = str_replace([
'<span style="background-color: #ffd">',
'<span style="color: #d44">',
'<span style="background-color: #dfd">',
), array(
], [
'<span class="status-warning">',
'<span class="status-error">',
'<span class="status-success">',
), $dump);
], $dump);
return new Markup($dump, 'UTF-8');
}
@@ -96,7 +139,11 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
public function getProfile()
{
if (null === $this->profile) {
$this->profile = unserialize($this->data['profile']);
if (\PHP_VERSION_ID >= 70000) {
$this->profile = unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]);
} else {
$this->profile = unserialize($this->data['profile']);
}
}
return $this->profile;
@@ -113,13 +160,13 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf
private function computeData(Profile $profile)
{
$data = array(
$data = [
'template_count' => 0,
'block_count' => 0,
'macro_count' => 0,
);
];
$templates = array();
$templates = [];
foreach ($profile as $p) {
$d = $this->computeData($p);

View File

@@ -12,7 +12,6 @@
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
@@ -24,16 +23,10 @@ use Twig\TwigFunction;
class AssetExtension extends AbstractExtension
{
private $packages;
private $foundationExtension;
/**
* Passing an HttpFoundationExtension instance as a second argument must not be relied on
* as it's only there to maintain BC with older Symfony version. It will be removed in 3.0.
*/
public function __construct(Packages $packages, HttpFoundationExtension $foundationExtension = null)
public function __construct(Packages $packages)
{
$this->packages = $packages;
$this->foundationExtension = $foundationExtension;
}
/**
@@ -41,11 +34,10 @@ class AssetExtension extends AbstractExtension
*/
public function getFunctions()
{
return array(
new TwigFunction('asset', array($this, 'getAssetUrl')),
new TwigFunction('asset_version', array($this, 'getAssetVersion')),
new TwigFunction('assets_version', array($this, 'getAssetsVersion'), array('deprecated' => true, 'alternative' => 'asset_version')),
);
return [
new TwigFunction('asset', [$this, 'getAssetUrl']),
new TwigFunction('asset_version', [$this, 'getAssetVersion']),
];
}
/**
@@ -59,20 +51,8 @@ class AssetExtension extends AbstractExtension
*
* @return string The public path of the asset
*/
public function getAssetUrl($path, $packageName = null, $absolute = false, $version = null)
public function getAssetUrl($path, $packageName = null)
{
// BC layer to be removed in 3.0
if (2 < $count = \func_num_args()) {
@trigger_error('Generating absolute URLs with the Twig asset() function was deprecated in 2.7 and will be removed in 3.0. Please use absolute_url() instead.', E_USER_DEPRECATED);
if (4 === $count) {
@trigger_error('Forcing a version with the Twig asset() function was deprecated in 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
}
$args = \func_get_args();
return $this->getLegacyAssetUrl($path, $packageName, $args[2], isset($args[3]) ? $args[3] : null);
}
return $this->packages->getUrl($path, $packageName);
}
@@ -89,56 +69,6 @@ class AssetExtension extends AbstractExtension
return $this->packages->getVersion($path, $packageName);
}
public function getAssetsVersion($packageName = null)
{
@trigger_error('The Twig assets_version() function was deprecated in 2.7 and will be removed in 3.0. Please use asset_version() instead.', E_USER_DEPRECATED);
return $this->packages->getVersion('/', $packageName);
}
private function getLegacyAssetUrl($path, $packageName = null, $absolute = false, $version = null)
{
if ($version) {
$package = $this->packages->getPackage($packageName);
$v = new \ReflectionProperty('Symfony\Component\Asset\Package', 'versionStrategy');
$v->setAccessible(true);
$currentVersionStrategy = $v->getValue($package);
if (property_exists($currentVersionStrategy, 'format')) {
$f = new \ReflectionProperty($currentVersionStrategy, 'format');
$f->setAccessible(true);
$format = $f->getValue($currentVersionStrategy);
$v->setValue($package, new StaticVersionStrategy($version, $format));
} else {
$v->setValue($package, new StaticVersionStrategy($version));
}
}
try {
$url = $this->packages->getUrl($path, $packageName);
} catch (\Exception $e) {
if ($version) {
$v->setValue($package, $currentVersionStrategy);
}
throw $e;
}
if ($version) {
$v->setValue($package, $currentVersionStrategy);
}
if ($absolute) {
return $this->foundationExtension->generateAbsoluteUrl($url);
}
return $url;
}
/**
* Returns the name of the extension.
*

View File

@@ -11,6 +11,7 @@
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
@@ -26,9 +27,9 @@ class CodeExtension extends AbstractExtension
private $charset;
/**
* @param string $fileLinkFormat The format for links to source files
* @param string $rootDir The project root directory
* @param string $charset The charset
* @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
* @param string $rootDir The project root directory
* @param string $charset The charset
*/
public function __construct($fileLinkFormat, $rootDir, $charset)
{
@@ -42,16 +43,17 @@ class CodeExtension extends AbstractExtension
*/
public function getFilters()
{
return array(
new TwigFilter('abbr_class', array($this, 'abbrClass'), array('is_safe' => array('html'))),
new TwigFilter('abbr_method', array($this, 'abbrMethod'), array('is_safe' => array('html'))),
new TwigFilter('format_args', array($this, 'formatArgs'), array('is_safe' => array('html'))),
new TwigFilter('format_args_as_text', array($this, 'formatArgsAsText')),
new TwigFilter('file_excerpt', array($this, 'fileExcerpt'), array('is_safe' => array('html'))),
new TwigFilter('format_file', array($this, 'formatFile'), array('is_safe' => array('html'))),
new TwigFilter('format_file_from_text', array($this, 'formatFileFromText'), array('is_safe' => array('html'))),
new TwigFilter('file_link', array($this, 'getFileLink')),
);
return [
new TwigFilter('abbr_class', [$this, 'abbrClass'], ['is_safe' => ['html']]),
new TwigFilter('abbr_method', [$this, 'abbrMethod'], ['is_safe' => ['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']),
];
}
public function abbrClass($class)
@@ -85,7 +87,7 @@ class CodeExtension extends AbstractExtension
*/
public function formatArgs($args)
{
$result = array();
$result = [];
foreach ($args as $key => $item) {
if ('object' === $item[0]) {
$parts = explode('\\', $item[1]);
@@ -93,8 +95,6 @@ class CodeExtension extends AbstractExtension
$formattedValue = sprintf('<em>object</em>(<abbr title="%s">%s</abbr>)', $item[1], $short);
} elseif ('array' === $item[0]) {
$formattedValue = sprintf('<em>array</em>(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
} elseif ('string' === $item[0]) {
$formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES, $this->charset));
} elseif ('null' === $item[0]) {
$formattedValue = '<em>null</em>';
} elseif ('boolean' === $item[0]) {
@@ -102,7 +102,7 @@ class CodeExtension extends AbstractExtension
} elseif ('resource' === $item[0]) {
$formattedValue = '<em>resource</em>';
} else {
$formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES, $this->charset), true));
$formattedValue = str_replace("\n", '', htmlspecialchars(var_export($item[1], true), ENT_COMPAT | ENT_SUBSTITUTE, $this->charset));
}
$result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
@@ -126,28 +126,39 @@ class CodeExtension extends AbstractExtension
/**
* Returns an excerpt of a code file around the given line number.
*
* @param string $file A file path
* @param int $line The selected line number
* @param string $file A file path
* @param int $line The selected line number
* @param int $srcContext The number of displayed lines around or -1 for the whole file
*
* @return string An HTML string
*/
public function fileExcerpt($file, $line)
public function fileExcerpt($file, $line, $srcContext = 3)
{
if (is_readable($file)) {
if (is_file($file) && is_readable($file)) {
// highlight_file could throw warnings
// see https://bugs.php.net/bug.php?id=25725
// see https://bugs.php.net/25725
$code = @highlight_file($file, true);
// 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);
$content = explode('<br />', $code);
$lines = array();
for ($i = max($line - 3, 1), $max = min($line + 3, \count($content)); $i <= $max; ++$i) {
$lines[] = '<li'.($i == $line ? ' class="selected"' : '').'><code>'.self::fixCodeMarkup($content[$i - 1]).'</code></li>';
$lines = [];
if (0 > $srcContext) {
$srcContext = \count($content);
}
return '<ol start="'.max($line - 3, 1).'">'.implode("\n", $lines).'</ol>';
for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) {
$lines[] = '<li'.($i == $line ? ' class="selected"' : '').'><a class="anchor" name="line'.$i.'"></a><code>'.self::fixCodeMarkup($content[$i - 1]).'</code></li>';
}
return '<ol start="'.max($line - $srcContext, 1).'">'.implode("\n", $lines).'</ol>';
}
return null;
}
/**
@@ -172,16 +183,12 @@ class CodeExtension extends AbstractExtension
}
}
$text = "$text at line $line";
if (0 < $line) {
$text .= ' at line '.$line;
}
if (false !== $link = $this->getFileLink($file, $line)) {
if (\PHP_VERSION_ID >= 50400) {
$flags = ENT_QUOTES | ENT_SUBSTITUTE;
} else {
$flags = ENT_QUOTES;
}
return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', htmlspecialchars($link, $flags, $this->charset), $text);
return sprintf('<a href="%s" title="Click to open this file" class="file_link">%s</a>', htmlspecialchars($link, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset), $text);
}
return $text;
@@ -197,8 +204,8 @@ class CodeExtension extends AbstractExtension
*/
public function getFileLink($file, $line)
{
if ($this->fileLinkFormat && is_file($file)) {
return strtr($this->fileLinkFormat, array('%f' => $file, '%l' => $line));
if ($fmt = $this->fileLinkFormat) {
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
}
return false;
@@ -206,13 +213,32 @@ class CodeExtension extends AbstractExtension
public function formatFileFromText($text)
{
$that = $this;
return preg_replace_callback('/in ("|&quot;)?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) use ($that) {
return 'in '.$that->formatFile($match[2], $match[3]);
return preg_replace_callback('/in ("|&quot;)?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) {
return 'in '.$this->formatFile($match[2], $match[3]);
}, $text);
}
/**
* @internal
*/
public function formatLogMessage($message, array $context)
{
if ($context && false !== strpos($message, '{')) {
$replacements = [];
foreach ($context as $key => $val) {
if (is_scalar($val)) {
$replacements['{'.$key.'}'] = $val;
}
}
if ($replacements) {
$message = strtr($message, $replacements);
}
}
return htmlspecialchars($message, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset);
}
/**
* {@inheritdoc}
*/
@@ -237,6 +263,6 @@ class CodeExtension extends AbstractExtension
$line .= '</span>';
}
return $line;
return trim($line);
}
}

View File

@@ -27,22 +27,24 @@ use Twig\TwigFunction;
class DumpExtension extends AbstractExtension
{
private $cloner;
private $dumper;
public function __construct(ClonerInterface $cloner)
public function __construct(ClonerInterface $cloner, HtmlDumper $dumper = null)
{
$this->cloner = $cloner;
$this->dumper = $dumper;
}
public function getFunctions()
{
return array(
new TwigFunction('dump', array($this, 'dump'), array('is_safe' => array('html'), 'needs_context' => true, 'needs_environment' => true)),
);
return [
new TwigFunction('dump', [$this, 'dump'], ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]),
];
}
public function getTokenParsers()
{
return array(new DumpTokenParser());
return [new DumpTokenParser()];
}
public function getName()
@@ -53,31 +55,31 @@ class DumpExtension extends AbstractExtension
public function dump(Environment $env, $context)
{
if (!$env->isDebug()) {
return;
return null;
}
if (2 === \func_num_args()) {
$vars = array();
$vars = [];
foreach ($context as $key => $value) {
if (!$value instanceof Template) {
$vars[$key] = $value;
}
}
$vars = array($vars);
$vars = [$vars];
} else {
$vars = \func_get_args();
unset($vars[0], $vars[1]);
}
$dump = fopen('php://memory', 'r+b');
$dumper = new HtmlDumper($dump, $env->getCharset());
$this->dumper = $this->dumper ?: new HtmlDumper();
$this->dumper->setCharset($env->getCharset());
foreach ($vars as $value) {
$dumper->dump($this->cloner->cloneVar($value));
$this->dumper->dump($this->cloner->cloneVar($value), $dump);
}
rewind($dump);
return stream_get_contents($dump);
return stream_get_contents($dump, -1, 0);
}
}

View File

@@ -27,9 +27,9 @@ class ExpressionExtension extends AbstractExtension
*/
public function getFunctions()
{
return array(
new TwigFunction('expression', array($this, 'createExpression')),
);
return [
new TwigFunction('expression', [$this, 'createExpression']),
];
}
public function createExpression($expression)

View File

@@ -13,11 +13,11 @@ namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\Form\TwigRendererInterface;
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\FormView;
use Twig\Environment;
use Twig\Extension\AbstractExtension;
use Twig\Extension\InitRuntimeInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
@@ -31,24 +31,32 @@ use Twig\TwigTest;
class FormExtension extends AbstractExtension implements InitRuntimeInterface
{
/**
* This property is public so that it can be accessed directly from compiled
* templates without having to call a getter, which slightly decreases performance.
*
* @var TwigRendererInterface
* @deprecated since version 3.2, to be removed in 4.0 alongside with magic methods below
*/
public $renderer;
private $renderer;
public function __construct(TwigRendererInterface $renderer)
public function __construct($renderer = null)
{
if ($renderer instanceof TwigRendererInterface) {
@trigger_error(sprintf('Passing a Twig Form Renderer to the "%s" constructor is deprecated since Symfony 3.2 and won\'t be possible in 4.0. Pass the Twig\Environment to the TwigRendererEngine constructor instead.', static::class), E_USER_DEPRECATED);
} elseif (null !== $renderer && !(\is_array($renderer) && isset($renderer[0], $renderer[1]) && $renderer[0] instanceof ContainerInterface)) {
throw new \InvalidArgumentException(sprintf('Passing any arguments to the constructor of %s is reserved for internal use.', __CLASS__));
}
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*
* To be removed in 4.0
*/
public function initRuntime(Environment $environment)
{
$this->renderer->setEnvironment($environment);
if ($this->renderer instanceof TwigRendererInterface) {
$this->renderer->setEnvironment($environment);
} elseif (\is_array($this->renderer)) {
$this->renderer[2] = $environment;
}
}
/**
@@ -56,10 +64,10 @@ class FormExtension extends AbstractExtension implements InitRuntimeInterface
*/
public function getTokenParsers()
{
return array(
return [
// {% form_theme form "SomeBundle::widgets.twig" %}
new FormThemeTokenParser(),
);
];
}
/**
@@ -67,18 +75,17 @@ class FormExtension extends AbstractExtension implements InitRuntimeInterface
*/
public function getFunctions()
{
return array(
new TwigFunction('form_enctype', null, array('node_class' => 'Symfony\Bridge\Twig\Node\FormEnctypeNode', 'is_safe' => array('html'), 'deprecated' => true, 'alternative' => 'form_start')),
new TwigFunction('form_widget', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))),
new TwigFunction('form_errors', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))),
new TwigFunction('form_label', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))),
new TwigFunction('form_row', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))),
new TwigFunction('form_rest', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))),
new TwigFunction('form', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))),
new TwigFunction('form_start', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))),
new TwigFunction('form_end', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))),
new TwigFunction('csrf_token', array($this, 'renderCsrfToken')),
);
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_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']),
];
}
/**
@@ -86,10 +93,10 @@ class FormExtension extends AbstractExtension implements InitRuntimeInterface
*/
public function getFilters()
{
return array(
new TwigFilter('humanize', array($this, 'humanize')),
new TwigFilter('form_encode_currency', array($this, 'encodeCurrency'), array('is_safe' => array('html'), 'needs_environment' => true)),
);
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]),
];
}
/**
@@ -97,90 +104,66 @@ class FormExtension extends AbstractExtension implements InitRuntimeInterface
*/
public function getTests()
{
return array(
new TwigTest('selectedchoice', array($this, 'isSelectedChoice')),
new TwigTest('rootform', array($this, 'isRootForm')),
);
}
/**
* Renders a CSRF token.
*
* @param string $intention The intention of the protected action
*
* @return string A CSRF token
*/
public function renderCsrfToken($intention)
{
return $this->renderer->renderCsrfToken($intention);
}
/**
* Makes a technical name human readable.
*
* @param string $text The text to humanize
*
* @return string The humanized text
*/
public function humanize($text)
{
return $this->renderer->humanize($text);
}
/**
* Returns whether a choice is selected for a given form value.
*
* Unfortunately Twig does not support an efficient way to execute the
* "is_selected" closure passed to the template by ChoiceType. It is faster
* to implement the logic here (around 65ms for a specific form).
*
* Directly implementing the logic here is also faster than doing so in
* ChoiceView (around 30ms).
*
* The worst option tested so far is to implement the logic in ChoiceView
* and access the ChoiceView method directly in the template. Doing so is
* around 220ms slower than doing the method call here in the filter. Twig
* seems to be much more efficient at executing filters than at executing
* methods of an object.
*
* @param ChoiceView $choice The choice to check
* @param string|array $selectedValue The selected value to compare
*
* @return bool Whether the choice is selected
*
* @see ChoiceView::isSelected()
*/
public function isSelectedChoice(ChoiceView $choice, $selectedValue)
{
if (\is_array($selectedValue)) {
return \in_array($choice->value, $selectedValue, true);
}
return $choice->value === $selectedValue;
return [
new TwigTest('selectedchoice', 'Symfony\Bridge\Twig\Extension\twig_is_selected_choice'),
new TwigTest('rootform', 'Symfony\Bridge\Twig\Extension\twig_is_root_form'),
];
}
/**
* @internal
*/
public function isRootForm(FormView $formView)
public function __get($name)
{
return null === $formView->parent;
if ('renderer' === $name) {
@trigger_error(sprintf('Using the "%s::$renderer" property is deprecated since Symfony 3.2 as it will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED);
if (\is_array($this->renderer)) {
$renderer = $this->renderer[0]->get($this->renderer[1]);
if (isset($this->renderer[2]) && $renderer instanceof TwigRendererInterface) {
$renderer->setEnvironment($this->renderer[2]);
}
$this->renderer = $renderer;
}
}
return $this->$name;
}
/**
* @internal
*/
public function encodeCurrency(Environment $environment, $text, $widget = '')
public function __set($name, $value)
{
if ('UTF-8' === $charset = $environment->getCharset()) {
$text = htmlspecialchars($text, ENT_QUOTES | (\defined('ENT_SUBSTITUTE') ? ENT_SUBSTITUTE : 0), 'UTF-8');
} else {
$text = htmlentities($text, ENT_QUOTES | (\defined('ENT_SUBSTITUTE') ? ENT_SUBSTITUTE : 0), 'UTF-8');
$text = iconv('UTF-8', $charset, $text);
$widget = iconv('UTF-8', $charset, $widget);
if ('renderer' === $name) {
@trigger_error(sprintf('Using the "%s::$renderer" property is deprecated since Symfony 3.2 as it will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED);
}
return str_replace('{{ widget }}', $widget, $text);
$this->$name = $value;
}
/**
* @internal
*/
public function __isset($name)
{
if ('renderer' === $name) {
@trigger_error(sprintf('Using the "%s::$renderer" property is deprecated since Symfony 3.2 as it will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED);
}
return isset($this->$name);
}
/**
* @internal
*/
public function __unset($name)
{
if ('renderer' === $name) {
@trigger_error(sprintf('Using the "%s::$renderer" property is deprecated since Symfony 3.2 as it will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED);
}
unset($this->$name);
}
/**
@@ -191,3 +174,31 @@ class FormExtension extends AbstractExtension implements InitRuntimeInterface
return 'form';
}
}
/**
* Returns whether a choice is selected for a given form value.
*
* This is a function and not callable due to performance reasons.
*
* @param string|array $selectedValue The selected value to compare
*
* @return bool Whether the choice is selected
*
* @see ChoiceView::isSelected()
*/
function twig_is_selected_choice(ChoiceView $choice, $selectedValue)
{
if (\is_array($selectedValue)) {
return \in_array($choice->value, $selectedValue, true);
}
return $choice->value === $selectedValue;
}
/**
* @internal
*/
function twig_is_root_form(FormView $formView)
{
return null === $formView->parent;
}

View File

@@ -38,10 +38,10 @@ class HttpFoundationExtension extends AbstractExtension
*/
public function getFunctions()
{
return array(
new TwigFunction('absolute_url', array($this, 'generateAbsoluteUrl')),
new TwigFunction('relative_path', array($this, 'generateRelativePath')),
);
return [
new TwigFunction('absolute_url', [$this, 'generateAbsoluteUrl']),
new TwigFunction('relative_path', [$this, 'generateRelativePath']),
];
}
/**

View File

@@ -12,7 +12,6 @@
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
@@ -23,57 +22,16 @@ use Twig\TwigFunction;
*/
class HttpKernelExtension extends AbstractExtension
{
private $handler;
public function __construct(FragmentHandler $handler)
{
$this->handler = $handler;
}
public function getFunctions()
{
return array(
new TwigFunction('render', array($this, 'renderFragment'), array('is_safe' => array('html'))),
new TwigFunction('render_*', array($this, 'renderFragmentStrategy'), array('is_safe' => array('html'))),
new TwigFunction('controller', array($this, 'controller')),
);
return [
new TwigFunction('render', [HttpKernelRuntime::class, 'renderFragment'], ['is_safe' => ['html']]),
new TwigFunction('render_*', [HttpKernelRuntime::class, 'renderFragmentStrategy'], ['is_safe' => ['html']]),
new TwigFunction('controller', static::class.'::controller'),
];
}
/**
* Renders a fragment.
*
* @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
* @param array $options An array of options
*
* @return string The fragment content
*
* @see FragmentHandler::render()
*/
public function renderFragment($uri, $options = array())
{
$strategy = isset($options['strategy']) ? $options['strategy'] : 'inline';
unset($options['strategy']);
return $this->handler->render($uri, $strategy, $options);
}
/**
* Renders a fragment.
*
* @param string $strategy A strategy name
* @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
* @param array $options An array of options
*
* @return string The fragment content
*
* @see FragmentHandler::render()
*/
public function renderFragmentStrategy($strategy, $uri, $options = array())
{
return $this->handler->render($uri, $strategy, $options);
}
public function controller($controller, $attributes = array(), $query = array())
public static function controller($controller, $attributes = [], $query = [])
{
return new ControllerReference($controller, $attributes, $query);
}

View File

@@ -0,0 +1,64 @@
<?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\HttpKernel\Controller\ControllerReference;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
/**
* Provides integration with the HttpKernel component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HttpKernelRuntime
{
private $handler;
public function __construct(FragmentHandler $handler)
{
$this->handler = $handler;
}
/**
* Renders a fragment.
*
* @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
* @param array $options An array of options
*
* @return string The fragment content
*
* @see FragmentHandler::render()
*/
public function renderFragment($uri, $options = [])
{
$strategy = isset($options['strategy']) ? $options['strategy'] : 'inline';
unset($options['strategy']);
return $this->handler->render($uri, $strategy, $options);
}
/**
* Renders a fragment.
*
* @param string $strategy A strategy name
* @param string|ControllerReference $uri A URI as a string or a ControllerReference instance
* @param array $options An array of options
*
* @return string The fragment content
*
* @see FragmentHandler::render()
*/
public function renderFragmentStrategy($strategy, $uri, $options = [])
{
return $this->handler->render($uri, $strategy, $options);
}
}

View File

@@ -0,0 +1,23 @@
<?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\InitRuntimeInterface as TwigInitRuntimeInterface;
/**
* @deprecated to be removed in 4.x
*
* @internal to be removed in 4.x
*/
interface InitRuntimeInterface extends TwigInitRuntimeInterface
{
}

View File

@@ -34,10 +34,10 @@ class LogoutUrlExtension extends AbstractExtension
*/
public function getFunctions()
{
return array(
new TwigFunction('logout_url', array($this, 'getLogoutUrl')),
new TwigFunction('logout_path', array($this, 'getLogoutPath')),
);
return [
new TwigFunction('logout_url', [$this, 'getLogoutUrl']),
new TwigFunction('logout_path', [$this, 'getLogoutPath']),
];
}
/**

View File

@@ -33,16 +33,14 @@ class RoutingExtension extends AbstractExtension
}
/**
* Returns a list of functions to add to the existing list.
*
* @return array An array of functions
* {@inheritdoc}
*/
public function getFunctions()
{
return array(
new TwigFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
new TwigFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
);
return [
new TwigFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
new TwigFunction('path', [$this, 'getPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
];
}
/**
@@ -52,7 +50,7 @@ class RoutingExtension extends AbstractExtension
*
* @return string
*/
public function getPath($name, $parameters = array(), $relative = false)
public function getPath($name, $parameters = [], $relative = false)
{
return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH);
}
@@ -64,7 +62,7 @@ class RoutingExtension extends AbstractExtension
*
* @return string
*/
public function getUrl($name, $parameters = array(), $schemeRelative = false)
public function getUrl($name, $parameters = [], $schemeRelative = false)
{
return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL);
}
@@ -91,7 +89,7 @@ class RoutingExtension extends AbstractExtension
*
* @return array An array with the contexts the URL is safe
*
* To be made @final in 3.4, and the type-hint be changed to "\Twig\Node\Node" in 4.0.
* @final since version 3.4, type-hint to be changed to "\Twig\Node\Node" in 4.0
*/
public function isUrlGenerationSafe(\Twig_Node $argsNode)
{
@@ -103,10 +101,10 @@ class RoutingExtension extends AbstractExtension
if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 &&
(!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression)
) {
return array('html');
return ['html'];
}
return array();
return [];
}
/**

View File

@@ -53,9 +53,9 @@ class SecurityExtension extends AbstractExtension
*/
public function getFunctions()
{
return array(
new TwigFunction('is_granted', array($this, 'isGranted')),
);
return [
new TwigFunction('is_granted', [$this, 'isGranted']),
];
}
/**

View File

@@ -38,14 +38,14 @@ class StopwatchExtension extends AbstractExtension
public function getTokenParsers()
{
return array(
return [
/*
* {% stopwatch foo %}
* Some stuff which will be recorded on the timeline
* {% endstopwatch %}
*/
new StopwatchTokenParser(null !== $this->stopwatch && $this->enabled),
);
];
}
public function getName()

View File

@@ -32,12 +32,8 @@ class TranslationExtension extends AbstractExtension
private $translator;
private $translationNodeVisitor;
public function __construct(TranslatorInterface $translator, NodeVisitorInterface $translationNodeVisitor = null)
public function __construct(TranslatorInterface $translator = null, NodeVisitorInterface $translationNodeVisitor = null)
{
if (!$translationNodeVisitor) {
$translationNodeVisitor = new TranslationNodeVisitor();
}
$this->translator = $translator;
$this->translationNodeVisitor = $translationNodeVisitor;
}
@@ -52,10 +48,10 @@ class TranslationExtension extends AbstractExtension
*/
public function getFilters()
{
return array(
new TwigFilter('trans', array($this, 'trans')),
new TwigFilter('transchoice', array($this, 'transchoice')),
);
return [
new TwigFilter('trans', [$this, 'trans']),
new TwigFilter('transchoice', [$this, 'transchoice']),
];
}
/**
@@ -65,7 +61,7 @@ class TranslationExtension extends AbstractExtension
*/
public function getTokenParsers()
{
return array(
return [
// {% trans %}Symfony is great!{% endtrans %}
new TransTokenParser(),
@@ -76,7 +72,7 @@ class TranslationExtension extends AbstractExtension
// {% trans_default_domain "foobar" %}
new TransDefaultDomainTokenParser(),
);
];
}
/**
@@ -84,22 +80,30 @@ class TranslationExtension extends AbstractExtension
*/
public function getNodeVisitors()
{
return array($this->translationNodeVisitor, new TranslationDefaultDomainNodeVisitor());
return [$this->getTranslationNodeVisitor(), new TranslationDefaultDomainNodeVisitor()];
}
public function getTranslationNodeVisitor()
{
return $this->translationNodeVisitor;
return $this->translationNodeVisitor ?: $this->translationNodeVisitor = new TranslationNodeVisitor();
}
public function trans($message, array $arguments = array(), $domain = null, $locale = null)
public function trans($message, array $arguments = [], $domain = null, $locale = null)
{
if (null === $this->translator) {
return strtr($message, $arguments);
}
return $this->translator->trans($message, $arguments, $domain, $locale);
}
public function transchoice($message, $count, array $arguments = array(), $domain = null, $locale = null)
public function transchoice($message, $count, array $arguments = [], $domain = null, $locale = null)
{
return $this->translator->transChoice($message, $count, array_merge(array('%count%' => $count), $arguments), $domain, $locale);
if (null === $this->translator) {
return strtr($message, $arguments);
}
return $this->translator->transChoice($message, $count, array_merge(['%count%' => $count], $arguments), $domain, $locale);
}
/**

View File

@@ -0,0 +1,139 @@
<?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 Fig\Link\GenericLinkProvider;
use Fig\Link\Link;
use Symfony\Component\HttpFoundation\RequestStack;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
/**
* Twig extension for the Symfony WebLink component.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class WebLinkExtension extends AbstractExtension
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
/**
* {@inheritdoc}
*/
public function getFunctions()
{
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']),
];
}
/**
* Adds a "Link" HTTP header.
*
* @param string $uri The relation URI
* @param string $rel The relation type (e.g. "preload", "prefetch", "prerender" or "dns-prefetch")
* @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The relation URI
*/
public function link($uri, $rel, array $attributes = [])
{
if (!$request = $this->requestStack->getMasterRequest()) {
return $uri;
}
$link = new Link($rel, $uri);
foreach ($attributes as $key => $value) {
$link = $link->withAttribute($key, $value);
}
$linkProvider = $request->attributes->get('_links', new GenericLinkProvider());
$request->attributes->set('_links', $linkProvider->withLink($link));
return $uri;
}
/**
* Preloads a resource.
*
* @param string $uri A public path
* @param array $attributes The attributes of this link (e.g. "['as' => true]", "['crossorigin' => 'use-credentials']")
*
* @return string The path of the asset
*/
public function preload($uri, array $attributes = [])
{
return $this->link($uri, 'preload', $attributes);
}
/**
* Resolves a resource origin as early as possible.
*
* @param string $uri A public path
* @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The path of the asset
*/
public function dnsPrefetch($uri, array $attributes = [])
{
return $this->link($uri, 'dns-prefetch', $attributes);
}
/**
* Initiates a early connection to a resource (DNS resolution, TCP handshake, TLS negotiation).
*
* @param string $uri A public path
* @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The path of the asset
*/
public function preconnect($uri, array $attributes = [])
{
return $this->link($uri, 'preconnect', $attributes);
}
/**
* Indicates to the client that it should prefetch this resource.
*
* @param string $uri A public path
* @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The path of the asset
*/
public function prefetch($uri, array $attributes = [])
{
return $this->link($uri, 'prefetch', $attributes);
}
/**
* Indicates to the client that it should prerender this resource .
*
* @param string $uri A public path
* @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]")
*
* @return string The path of the asset
*/
public function prerender($uri, array $attributes = [])
{
return $this->link($uri, 'prerender', $attributes);
}
}

View File

@@ -0,0 +1,108 @@
<?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\Workflow\Registry;
use Symfony\Component\Workflow\Transition;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
/**
* WorkflowExtension.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class WorkflowExtension extends AbstractExtension
{
private $workflowRegistry;
public function __construct(Registry $workflowRegistry)
{
$this->workflowRegistry = $workflowRegistry;
}
public function getFunctions()
{
return [
new TwigFunction('workflow_can', [$this, 'canTransition']),
new TwigFunction('workflow_transitions', [$this, 'getEnabledTransitions']),
new TwigFunction('workflow_has_marked_place', [$this, 'hasMarkedPlace']),
new TwigFunction('workflow_marked_places', [$this, 'getMarkedPlaces']),
];
}
/**
* Returns true if the transition is enabled.
*
* @param object $subject A subject
* @param string $transitionName A transition
* @param string $name A workflow name
*
* @return bool true if the transition is enabled
*/
public function canTransition($subject, $transitionName, $name = null)
{
return $this->workflowRegistry->get($subject, $name)->can($subject, $transitionName);
}
/**
* Returns all enabled transitions.
*
* @param object $subject A subject
* @param string $name A workflow name
*
* @return Transition[] All enabled transitions
*/
public function getEnabledTransitions($subject, $name = null)
{
return $this->workflowRegistry->get($subject, $name)->getEnabledTransitions($subject);
}
/**
* Returns true if the place is marked.
*
* @param object $subject A subject
* @param string $placeName A place name
* @param string $name A workflow name
*
* @return bool true if the transition is enabled
*/
public function hasMarkedPlace($subject, $placeName, $name = null)
{
return $this->workflowRegistry->get($subject, $name)->getMarking($subject)->has($placeName);
}
/**
* Returns marked places.
*
* @param object $subject A subject
* @param bool $placesNameOnly If true, returns only places name. If false returns the raw representation
* @param string $name A workflow name
*
* @return string[]|int[]
*/
public function getMarkedPlaces($subject, $placesNameOnly = true, $name = null)
{
$places = $this->workflowRegistry->get($subject, $name)->getMarking($subject)->getPlaces();
if ($placesNameOnly) {
return array_keys($places);
}
return $places;
}
public function getName()
{
return 'workflow';
}
}

View File

@@ -28,13 +28,13 @@ class YamlExtension extends AbstractExtension
*/
public function getFilters()
{
return array(
new TwigFilter('yaml_encode', array($this, 'encode')),
new TwigFilter('yaml_dump', array($this, 'dump')),
);
return [
new TwigFilter('yaml_encode', [$this, 'encode']),
new TwigFilter('yaml_dump', [$this, 'dump']),
];
}
public function encode($input, $inline = 0, $dumpObjects = false)
public function encode($input, $inline = 0, $dumpObjects = 0)
{
static $dumper;
@@ -43,7 +43,15 @@ class YamlExtension extends AbstractExtension
}
if (\defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) {
return $dumper->dump($input, $inline, 0, \is_bool($dumpObjects) ? Yaml::DUMP_OBJECT : 0);
if (\is_bool($dumpObjects)) {
@trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', E_USER_DEPRECATED);
$flags = $dumpObjects ? Yaml::DUMP_OBJECT : 0;
} else {
$flags = $dumpObjects;
}
return $dumper->dump($input, $inline, 0, $flags);
}
return $dumper->dump($input, $inline, 0, false, $dumpObjects);

View File

@@ -12,14 +12,19 @@
namespace Symfony\Bridge\Twig\Form;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Twig\Environment;
@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use %s instead.', TwigRenderer::class, FormRenderer::class), E_USER_DEPRECATED);
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\Form\FormRenderer instead.
*/
class TwigRenderer extends FormRenderer implements TwigRendererInterface
{
public function __construct(TwigRendererEngineInterface $engine, $csrfTokenManager = null)
public function __construct(TwigRendererEngineInterface $engine, CsrfTokenManagerInterface $csrfTokenManager = null)
{
parent::__construct($engine, $csrfTokenManager);
}

View File

@@ -31,18 +31,34 @@ class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererE
*/
private $template;
public function __construct(array $defaultThemes = [], Environment $environment = null)
{
if (null === $environment) {
@trigger_error(sprintf('Not passing a Twig Environment as the second argument for "%s" constructor is deprecated since Symfony 3.2 and won\'t be possible in 4.0.', static::class), E_USER_DEPRECATED);
}
parent::__construct($defaultThemes);
$this->environment = $environment;
}
/**
* {@inheritdoc}
*
* @deprecated since version 3.3, to be removed in 4.0
*/
public function setEnvironment(Environment $environment)
{
if ($this->environment) {
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Pass the Twig Environment as second argument of the constructor instead.', __METHOD__), E_USER_DEPRECATED);
}
$this->environment = $environment;
}
/**
* {@inheritdoc}
*/
public function renderBlock(FormView $view, $resource, $blockName, array $variables = array())
public function renderBlock(FormView $view, $resource, $blockName, array $variables = [])
{
$cacheKey = $view->vars[self::CACHE_KEY_VAR];
@@ -108,9 +124,11 @@ class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererE
// Check the default themes once we reach the root view without success
if (!$view->parent) {
for ($i = \count($this->defaultThemes) - 1; $i >= 0; --$i) {
$this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]);
// CONTINUE LOADING (see doc comment)
if (!isset($this->useDefaultThemes[$cacheKey]) || $this->useDefaultThemes[$cacheKey]) {
for ($i = \count($this->defaultThemes) - 1; $i >= 0; --$i) {
$this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]);
// CONTINUE LOADING (see doc comment)
}
}
}
@@ -169,7 +187,7 @@ class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererE
// theme is a reference and we don't want to change it.
$currentTheme = $theme;
$context = $this->environment->mergeGlobals(array());
$context = $this->environment->mergeGlobals([]);
// The do loop takes care of template inheritance.
// Add blocks from all templates in the inheritance tree, but avoid

View File

@@ -19,6 +19,8 @@ class_exists('Twig\Environment');
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since version 3.2, to be removed in 4.0.
*/
interface TwigRendererEngineInterface extends FormRendererEngineInterface
{

View File

@@ -19,6 +19,8 @@ class_exists('Twig\Environment');
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since version 3.2, to be removed in 4.0.
*/
interface TwigRendererInterface extends FormRendererInterface
{

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2018 Fabien Potencier
Copyright (c) 2004-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -23,12 +23,12 @@ class DumpNode extends Node
public function __construct($varPrefix, Node $values = null, $lineno, $tag = null)
{
$nodes = array();
$nodes = [];
if (null !== $values) {
$nodes['values'] = $values;
}
parent::__construct($nodes, array(), $lineno, $tag);
parent::__construct($nodes, [], $lineno, $tag);
$this->varPrefix = $varPrefix;
}
@@ -44,7 +44,7 @@ class DumpNode extends Node
if (!$this->hasNode('values')) {
// remove embedded templates (macros) from the context
$compiler
->write(sprintf('$%svars = array();'."\n", $this->varPrefix))
->write(sprintf('$%svars = [];'."\n", $this->varPrefix))
->write(sprintf('foreach ($context as $%1$skey => $%1$sval) {'."\n", $this->varPrefix))
->indent()
->write(sprintf('if (!$%sval instanceof \Twig\Template) {'."\n", $this->varPrefix))
@@ -65,7 +65,7 @@ class DumpNode extends Node
} else {
$compiler
->addDebugInfo($this)
->write('\Symfony\Component\VarDumper\VarDumper::dump(array('."\n")
->write('\Symfony\Component\VarDumper\VarDumper::dump(['."\n")
->indent();
foreach ($values as $node) {
$compiler->write('');
@@ -80,7 +80,7 @@ class DumpNode extends Node
}
$compiler
->outdent()
->write("));\n");
->write("]);\n");
}
$compiler

View File

@@ -11,7 +11,10 @@
namespace Symfony\Bridge\Twig\Node;
use Symfony\Bridge\Twig\Form\TwigRenderer;
use Symfony\Component\Form\FormRenderer;
use Twig\Compiler;
use Twig\Error\RuntimeError;
use Twig\Node\Node;
/**
@@ -19,19 +22,30 @@ use Twig\Node\Node;
*/
class FormThemeNode extends Node
{
public function __construct(Node $form, Node $resources, $lineno, $tag = null)
public function __construct(Node $form, Node $resources, $lineno, $tag = null, $only = false)
{
parent::__construct(array('form' => $form, 'resources' => $resources), array(), $lineno, $tag);
parent::__construct(['form' => $form, 'resources' => $resources], ['only' => (bool) $only], $lineno, $tag);
}
public function compile(Compiler $compiler)
{
try {
$compiler->getEnvironment()->getRuntime(FormRenderer::class);
$renderer = FormRenderer::class;
} catch (RuntimeError $e) {
$renderer = TwigRenderer::class;
}
$compiler
->addDebugInfo($this)
->write('$this->env->getExtension(\'Symfony\Bridge\Twig\Extension\FormExtension\')->renderer->setTheme(')
->write('$this->env->getRuntime(')
->string($renderer)
->raw(')->setTheme(')
->subcompile($this->getNode('form'))
->raw(', ')
->subcompile($this->getNode('resources'))
->raw(', ')
->raw(false === $this->getAttribute('only') ? 'true' : 'false')
->raw(");\n");
}
}

View File

@@ -28,7 +28,7 @@ class RenderBlockNode extends FunctionExpression
{
$compiler->addDebugInfo($this);
$arguments = iterator_to_array($this->getNode('arguments'));
$compiler->write('$this->env->getExtension(\'Symfony\Bridge\Twig\Extension\FormExtension\')->renderer->renderBlock(');
$compiler->write('$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->renderBlock(');
if (isset($arguments[0])) {
$compiler->subcompile($arguments[0]);

View File

@@ -24,11 +24,10 @@ class SearchAndRenderBlockNode extends FunctionExpression
public function compile(Compiler $compiler)
{
$compiler->addDebugInfo($this);
$compiler->raw('$this->env->getExtension(\'Symfony\Bridge\Twig\Extension\FormExtension\')->renderer->searchAndRenderBlock(');
$compiler->raw('$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(');
preg_match('/_([^_]+)$/', $this->getAttribute('name'), $matches);
$label = null;
$arguments = iterator_to_array($this->getNode('arguments'));
$blockNameSuffix = $matches[1];
@@ -53,7 +52,7 @@ class SearchAndRenderBlockNode extends FunctionExpression
// Only insert the label into the array if it is not empty
if (!twig_test_empty($label->getAttribute('value'))) {
$originalVariables = $variables;
$variables = new ArrayExpression(array(), $lineno);
$variables = new ArrayExpression([], $lineno);
$labelKey = new ConstantExpression('label', $lineno);
if (null !== $originalVariables) {
@@ -100,7 +99,7 @@ class SearchAndRenderBlockNode extends FunctionExpression
// If not, add it to the array at runtime.
$compiler->raw('(twig_test_empty($_label_ = ');
$compiler->subcompile($label);
$compiler->raw(') ? array() : array("label" => $_label_))');
$compiler->raw(') ? [] : ["label" => $_label_])');
}
}
}

View File

@@ -24,7 +24,7 @@ class StopwatchNode extends Node
{
public function __construct(Node $name, Node $body, AssignNameExpression $var, $lineno = 0, $tag = null)
{
parent::__construct(array('body' => $body, 'name' => $name, 'var' => $var), array(), $lineno, $tag);
parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag);
}
public function compile(Compiler $compiler)

View File

@@ -22,7 +22,7 @@ class TransDefaultDomainNode extends Node
{
public function __construct(AbstractExpression $expr, $lineno = 0, $tag = null)
{
parent::__construct(array('expr' => $expr), array(), $lineno, $tag);
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}
public function compile(Compiler $compiler)

View File

@@ -29,7 +29,7 @@ class TransNode extends Node
{
public function __construct(Node $body, Node $domain = null, AbstractExpression $count = null, AbstractExpression $vars = null, AbstractExpression $locale = null, $lineno = 0, $tag = null)
{
$nodes = array('body' => $body);
$nodes = ['body' => $body];
if (null !== $domain) {
$nodes['domain'] = $domain;
}
@@ -43,14 +43,14 @@ class TransNode extends Node
$nodes['locale'] = $locale;
}
parent::__construct($nodes, array(), $lineno, $tag);
parent::__construct($nodes, [], $lineno, $tag);
}
public function compile(Compiler $compiler)
{
$compiler->addDebugInfo($this);
$defaults = new ArrayExpression(array(), -1);
$defaults = new ArrayExpression([], -1);
if ($this->hasNode('vars') && ($vars = $this->getNode('vars')) instanceof ArrayExpression) {
$defaults = $this->getNode('vars');
$vars = null;
@@ -109,7 +109,7 @@ class TransNode extends Node
} elseif ($body instanceof TextNode) {
$msg = $body->getAttribute('data');
} else {
return array($body, $vars);
return [$body, $vars];
}
preg_match_all('/(?<!%)%([^%]+)%/', $msg, $matches);
@@ -127,6 +127,6 @@ class TransNode extends Node
}
}
return array(new ConstantExpression(str_replace('%%', '%', trim($msg)), $body->getTemplateLine()), $vars);
return [new ConstantExpression(str_replace('%%', '%', trim($msg)), $body->getTemplateLine()), $vars];
}
}

View File

@@ -17,10 +17,10 @@ namespace Symfony\Bridge\Twig\NodeVisitor;
class Scope
{
private $parent;
private $data = array();
private $data = [];
private $left = false;
public function __construct(Scope $parent = null)
public function __construct(self $parent = null)
{
$this->parent = $parent;
}
@@ -77,7 +77,7 @@ class Scope
*/
public function has($key)
{
if (array_key_exists($key, $this->data)) {
if (\array_key_exists($key, $this->data)) {
return true;
}
@@ -98,7 +98,7 @@ class Scope
*/
public function get($key, $default = null)
{
if (array_key_exists($key, $this->data)) {
if (\array_key_exists($key, $this->data)) {
return $this->data[$key];
}

View File

@@ -56,7 +56,7 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
$name = new AssignNameExpression($var, $node->getTemplateLine());
$this->scope->set('domain', new NameExpression($var, $node->getTemplateLine()));
return new SetNode(false, new Node(array($name)), new Node(array($node->getNode('expr'))), $node->getTemplateLine());
return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine());
}
}
@@ -64,7 +64,7 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
return $node;
}
if ($node instanceof FilterExpression && \in_array($node->getNode('filter')->getAttribute('value'), array('trans', 'transchoice'))) {
if ($node instanceof FilterExpression && \in_array($node->getNode('filter')->getAttribute('value'), ['trans', 'transchoice'])) {
$arguments = $node->getNode('arguments');
$ind = 'trans' === $node->getNode('filter')->getAttribute('value') ? 1 : 2;
if ($this->isNamedArguments($arguments)) {
@@ -74,7 +74,7 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
} else {
if (!$arguments->hasNode($ind)) {
if (!$arguments->hasNode($ind - 1)) {
$arguments->setNode($ind - 1, new ArrayExpression(array(), $node->getTemplateLine()));
$arguments->setNode($ind - 1, new ArrayExpression([], $node->getTemplateLine()));
}
$arguments->setNode($ind, $this->scope->get('domain'));
@@ -95,7 +95,7 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
protected function doLeaveNode(Node $node, Environment $env)
{
if ($node instanceof TransDefaultDomainNode) {
return false;
return null;
}
if ($node instanceof BlockNode || $node instanceof ModuleNode) {
@@ -107,6 +107,8 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor
/**
* {@inheritdoc}
*
* @return int
*/
public function getPriority()
{

View File

@@ -28,18 +28,18 @@ class TranslationNodeVisitor extends AbstractNodeVisitor
const UNDEFINED_DOMAIN = '_undefined';
private $enabled = false;
private $messages = array();
private $messages = [];
public function enable()
{
$this->enabled = true;
$this->messages = array();
$this->messages = [];
}
public function disable()
{
$this->enabled = false;
$this->messages = array();
$this->messages = [];
}
public function getMessages()
@@ -62,26 +62,26 @@ class TranslationNodeVisitor extends AbstractNodeVisitor
$node->getNode('node') instanceof ConstantExpression
) {
// extract constant nodes with a trans filter
$this->messages[] = array(
$this->messages[] = [
$node->getNode('node')->getAttribute('value'),
$this->getReadDomainFromArguments($node->getNode('arguments'), 1),
);
];
} elseif (
$node instanceof FilterExpression &&
'transchoice' === $node->getNode('filter')->getAttribute('value') &&
$node->getNode('node') instanceof ConstantExpression
) {
// extract constant nodes with a trans filter
$this->messages[] = array(
$this->messages[] = [
$node->getNode('node')->getAttribute('value'),
$this->getReadDomainFromArguments($node->getNode('arguments'), 2),
);
];
} elseif ($node instanceof TransNode) {
// extract trans nodes
$this->messages[] = array(
$this->messages[] = [
$node->getNode('body')->getAttribute('data'),
$node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null,
);
];
}
return $node;
@@ -97,6 +97,8 @@ class TranslationNodeVisitor extends AbstractNodeVisitor
/**
* {@inheritdoc}
*
* @return int
*/
public function getPriority()
{
@@ -104,8 +106,7 @@ class TranslationNodeVisitor extends AbstractNodeVisitor
}
/**
* @param Node $arguments
* @param int $index
* @param int $index
*
* @return string|null
*/
@@ -116,7 +117,7 @@ class TranslationNodeVisitor extends AbstractNodeVisitor
} elseif ($arguments->hasNode($index)) {
$argument = $arguments->getNode($index);
} else {
return;
return null;
}
return $this->getReadDomainFromNode($argument);

View File

@@ -8,14 +8,12 @@
{# Labels #}
{% block form_label -%}
{% spaceless %}
{% if label is same as(false) %}
{%- if label is same as(false) -%}
<div class="{{ block('form_label_class') }}"></div>
{% else %}
{% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) %}
{%- else -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%}
{{- parent() -}}
{% endif %}
{% endspaceless %}
{%- endif -%}
{%- endblock form_label %}
{% block form_label_class -%}
@@ -34,48 +32,34 @@ col-sm-2
{##}</div>
{%- endblock form_row %}
{% block checkbox_row -%}
{{- block('checkbox_radio_row') -}}
{%- endblock checkbox_row %}
{% block radio_row -%}
{{- block('checkbox_radio_row') -}}
{%- endblock radio_row %}
{% block checkbox_radio_row -%}
{% spaceless %}
<div class="form-group{% if not valid %} has-error{% endif %}">
<div class="{{ block('form_label_class') }}"></div>
<div class="{{ block('form_group_class') }}">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
</div>
{% endspaceless %}
{%- endblock checkbox_radio_row %}
{% block submit_row -%}
{% spaceless %}
<div class="form-group">
<div class="{{ block('form_label_class') }}"></div>
<div class="form-group">{#--#}
<div class="{{ block('form_label_class') }}"></div>{#--#}
<div class="{{ block('form_group_class') }}">
{{ form_widget(form) }}
</div>
{{- form_widget(form) -}}
</div>{#--#}
</div>
{% endspaceless %}
{% endblock submit_row %}
{%- endblock submit_row %}
{% block reset_row -%}
{% spaceless %}
<div class="form-group">
<div class="{{ block('form_label_class') }}"></div>
<div class="form-group">{#--#}
<div class="{{ block('form_label_class') }}"></div>{#--#}
<div class="{{ block('form_group_class') }}">
{{ form_widget(form) }}
</div>
{{- form_widget(form) -}}
</div>{#--#}
</div>
{% endspaceless %}
{% endblock reset_row %}
{%- endblock reset_row %}
{% block form_group_class -%}
col-sm-10
{%- endblock form_group_class %}
{% block checkbox_row -%}
<div class="form-group{% if not valid %} has-error{% endif %}">{#--#}
<div class="{{ block('form_label_class') }}"></div>{#--#}
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</div>{#--#}
</div>
{%- endblock checkbox_row %}

View File

@@ -1,4 +1,4 @@
{% use "form_div_layout.html.twig" %}
{% use "bootstrap_base_layout.html.twig" %}
{# Widgets #}
@@ -9,15 +9,10 @@
{{- parent() -}}
{%- endblock form_widget_simple %}
{% block textarea_widget -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %}
{{- parent() -}}
{%- endblock textarea_widget %}
{% block button_widget -%}
{% set attr = attr|merge({class: (attr.class|default('btn-default') ~ ' btn')|trim}) %}
{%- set attr = attr|merge({class: (attr.class|default('btn-default') ~ ' btn')|trim}) -%}
{{- parent() -}}
{%- endblock %}
{%- endblock button_widget %}
{% block money_widget -%}
{% set prepend = not (money_pattern starts with '{{') %}
@@ -37,86 +32,6 @@
{% endif %}
{%- endblock money_widget %}
{% block percent_widget -%}
<div class="input-group">
{{- block('form_widget_simple') -}}
<span class="input-group-addon">%</span>
</div>
{%- endblock percent_widget %}
{% block datetime_widget -%}
{% if widget == 'single_text' %}
{{- block('form_widget_simple') -}}
{% else -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
<div {{ block('widget_container_attributes') }}>
{{- form_errors(form.date) -}}
{{- form_errors(form.time) -}}
{{- form_widget(form.date, { datetime: true } ) -}}
{{- form_widget(form.time, { datetime: true } ) -}}
</div>
{%- endif %}
{%- endblock datetime_widget %}
{% block date_widget -%}
{% if widget == 'single_text' %}
{{- block('form_widget_simple') -}}
{% else -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
{% if datetime is not defined or not datetime -%}
<div {{ block('widget_container_attributes') -}}>
{%- endif %}
{{- date_pattern|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day),
})|raw -}}
{% if datetime is not defined or not datetime -%}
</div>
{%- endif -%}
{% endif %}
{%- endblock date_widget %}
{% block time_widget -%}
{% if widget == 'single_text' %}
{{- block('form_widget_simple') -}}
{% else -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
{% if datetime is not defined or false == datetime -%}
<div {{ block('widget_container_attributes') -}}>
{%- endif -%}
{{- form_widget(form.hour) }}{% if with_minutes %}:{{ form_widget(form.minute) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second) }}{% endif %}
{% if datetime is not defined or false == datetime -%}
</div>
{%- endif -%}
{% endif %}
{%- endblock time_widget %}
{% block choice_widget_collapsed -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %}
{{- parent() -}}
{%- endblock %}
{% block choice_widget_expanded -%}
{% if '-inline' in label_attr.class|default('') -%}
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{% endfor -%}
{%- else -%}
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{% endfor -%}
</div>
{%- endif %}
{%- endblock choice_widget_expanded %}
{% block checkbox_widget -%}
{%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%}
{% if 'checkbox-inline' in parent_label_class %}
@@ -125,18 +40,18 @@
<div class="checkbox">
{{- form_label(form, null, { widget: parent() }) -}}
</div>
{%- endif %}
{%- endif -%}
{%- endblock checkbox_widget %}
{% block radio_widget -%}
{%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%}
{% if 'radio-inline' in parent_label_class %}
{%- if 'radio-inline' in parent_label_class -%}
{{- form_label(form, null, { widget: parent() }) -}}
{% else -%}
{%- else -%}
<div class="radio">
{{- form_label(form, null, { widget: parent() }) -}}
</div>
{%- endif %}
{%- endif -%}
{%- endblock radio_widget %}
{# Labels #}
@@ -164,39 +79,39 @@
{{- block('checkbox_radio_label') -}}
{%- endblock radio_label %}
{% block checkbox_radio_label %}
{% 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 %}
{% if required %}
{% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %}
{% endif %}
{% if parent_label_class is defined %}
{% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|trim}) %}
{% endif %}
{% if label is not same as(false) and label is empty %}
{%- if widget is defined -%}
{%- if required -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%}
{%- endif -%}
{%- if parent_label_class is defined -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|trim}) -%}
{%- endif -%}
{%- if label is not same as(false) and label is empty -%}
{%- if label_format is not empty -%}
{% set label = label_format|replace({
{%- set label = label_format|replace({
'%name%': name,
'%id%': id,
}) %}
}) -%}
{%- else -%}
{% set label = name|humanize %}
{%- endif -%}
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{%- endif -%}
<label{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
{{- widget|raw }} {{ label is not same as(false) ? (translation_domain is same as(false) ? label : label|trans({}, translation_domain)) -}}
</label>
{% endif %}
{% endblock checkbox_radio_label %}
{%- endif -%}
{%- endblock checkbox_radio_label %}
{# Rows #}
{% block form_row -%}
<div class="form-group{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
{{- form_label(form) -}}
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</div>
{{- form_label(form) }} {# -#}
{{ form_widget(form) }} {# -#}
{{ form_errors(form) }} {# -#}
</div> {# -#}
{%- endblock form_row %}
{% block button_row -%}

View File

@@ -0,0 +1,76 @@
{% use "bootstrap_4_layout.html.twig" %}
{# Labels #}
{% block form_label -%}
{%- if label is same as(false) -%}
<div class="{{ block('form_label_class') }}"></div>
{%- else -%}
{%- if expanded is not defined or not expanded -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%}
{%- endif -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%}
{{- parent() -}}
{%- endif -%}
{%- endblock form_label %}
{% block form_label_class -%}
col-sm-2
{%- endblock form_label_class %}
{# Rows #}
{% block form_row -%}
{%- if expanded is defined and expanded -%}
{{ block('fieldset_form_row') }}
{%- else -%}
<div class="form-group row{% if (not compound or force_error|default(false)) and not valid %} is-invalid{% endif %}">
{{- form_label(form) -}}
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
</div>
{##}</div>
{%- endif -%}
{%- endblock form_row %}
{% block fieldset_form_row -%}
<fieldset class="form-group">
<div class="row{% if (not compound or force_error|default(false)) and not valid %} is-invalid{% endif %}">
{{- form_label(form) -}}
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
</div>
</div>
{##}</fieldset>
{%- endblock fieldset_form_row %}
{% block submit_row -%}
<div class="form-group row">{#--#}
<div class="{{ block('form_label_class') }}"></div>{#--#}
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
</div>{#--#}
</div>
{%- endblock submit_row %}
{% block reset_row -%}
<div class="form-group row">{#--#}
<div class="{{ block('form_label_class') }}"></div>{#--#}
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
</div>{#--#}
</div>
{%- endblock reset_row %}
{% block form_group_class -%}
col-sm-10
{%- endblock form_group_class %}
{% block checkbox_row -%}
<div class="form-group row">{#--#}
<div class="{{ block('form_label_class') }}"></div>{#--#}
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
</div>{#--#}
</div>
{%- endblock checkbox_row %}

View File

@@ -0,0 +1,282 @@
{% use "bootstrap_base_layout.html.twig" %}
{# Widgets #}
{% block money_widget -%}
{%- set prepend = not (money_pattern starts with '{{') -%}
{%- set append = not (money_pattern ends with '}}') -%}
{%- if prepend or append -%}
<div class="input-group{{ group_class|default('') }}">
{%- if prepend -%}
<div class="input-group-prepend">
<span class="input-group-text">{{ money_pattern|form_encode_currency }}</span>
</div>
{%- endif -%}
{{- block('form_widget_simple') -}}
{%- if append -%}
<div class="input-group-append">
<span class="input-group-text">{{ money_pattern|form_encode_currency }}</span>
</div>
{%- endif -%}
</div>
{%- else -%}
{{- block('form_widget_simple') -}}
{%- endif -%}
{%- endblock money_widget %}
{% block datetime_widget -%}
{%- if widget != 'single_text' and not valid -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%}
{% set valid = true %}
{%- endif -%}
{{- parent() -}}
{%- endblock datetime_widget %}
{% block date_widget -%}
{%- if widget != 'single_text' and not valid -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%}
{% set valid = true %}
{%- endif -%}
{{- parent() -}}
{%- endblock date_widget %}
{% block time_widget -%}
{%- if widget != 'single_text' and not valid -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%}
{% set valid = true %}
{%- endif -%}
{{- parent() -}}
{%- endblock time_widget %}
{% block dateinterval_widget -%}
{%- if widget != 'single_text' and not valid -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control is-invalid')|trim}) -%}
{% set valid = true %}
{%- endif -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
<div {{ block('widget_container_attributes') }}>
{%- if with_years -%}
<div class="col-auto">
{{ form_label(form.years) }}
{{ form_widget(form.years) }}
</div>
{%- endif -%}
{%- if with_months -%}
<div class="col-auto">
{{ form_label(form.months) }}
{{ form_widget(form.months) }}
</div>
{%- endif -%}
{%- if with_weeks -%}
<div class="col-auto">
{{ form_label(form.weeks) }}
{{ form_widget(form.weeks) }}
</div>
{%- endif -%}
{%- if with_days -%}
<div class="col-auto">
{{ form_label(form.days) }}
{{ form_widget(form.days) }}
</div>
{%- endif -%}
{%- if with_hours -%}
<div class="col-auto">
{{ form_label(form.hours) }}
{{ form_widget(form.hours) }}
</div>
{%- endif -%}
{%- if with_minutes -%}
<div class="col-auto">
{{ form_label(form.minutes) }}
{{ form_widget(form.minutes) }}
</div>
{%- endif -%}
{%- if with_seconds -%}
<div class="col-auto">
{{ form_label(form.seconds) }}
{{ form_widget(form.seconds) }}
</div>
{%- endif -%}
{%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%}
</div>
{%- endif -%}
{%- endblock dateinterval_widget %}
{% block percent_widget -%}
<div class="input-group">
{{- block('form_widget_simple') -}}
<div class="input-group-append">
<span class="input-group-text">%</span>
</div>
</div>
{%- endblock percent_widget %}
{% block form_widget_simple -%}
{% if type is not defined or type != 'hidden' %}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control' ~ (type|default('') == 'file' ? '-file' : ''))|trim}) -%}
{% endif %}
{%- if type is defined and (type == 'range' or type == 'color') %}
{# Attribute "required" is not supported #}
{%- set required = false -%}
{% endif %}
{{- parent() -}}
{%- endblock form_widget_simple %}
{%- block widget_attributes -%}
{%- if not valid %}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) %}
{% endif -%}
{{ parent() }}
{%- endblock widget_attributes -%}
{% block button_widget -%}
{%- set attr = attr|merge({class: (attr.class|default('btn-secondary') ~ ' btn')|trim}) -%}
{{- parent() -}}
{%- endblock button_widget %}
{% block submit_widget -%}
{%- set attr = attr|merge({class: (attr.class|default('btn-primary'))|trim}) -%}
{{- parent() -}}
{%- endblock submit_widget %}
{% block checkbox_widget -%}
{%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%}
{%- if 'checkbox-custom' in parent_label_class -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%}
<div class="custom-control custom-checkbox{{ 'checkbox-inline' in parent_label_class ? ' custom-control-inline' }}">
{{- form_label(form, null, { widget: parent() }) -}}
</div>
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%}
<div class="form-check{{ 'checkbox-inline' in parent_label_class ? ' form-check-inline' }}">
{{- form_label(form, null, { widget: parent() }) -}}
</div>
{%- endif -%}
{%- endblock checkbox_widget %}
{% block radio_widget -%}
{%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%}
{%- if 'radio-custom' in parent_label_class -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%}
<div class="custom-control custom-radio{{ 'radio-inline' in parent_label_class ? ' custom-control-inline' }}">
{{- form_label(form, null, { widget: parent() }) -}}
</div>
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%}
<div class="form-check{{ 'radio-inline' in parent_label_class ? ' form-check-inline' }}">
{{- form_label(form, null, { widget: parent() }) -}}
</div>
{%- endif -%}
{%- endblock radio_widget %}
{% block choice_widget_expanded -%}
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
valid: valid,
}) -}}
{% endfor -%}
</div>
{%- endblock choice_widget_expanded %}
{# Labels #}
{% block form_label -%}
{% if label is not same as(false) -%}
{%- if compound is defined and compound -%}
{%- set element = 'legend' -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%}
{%- else -%}
{%- set label_attr = label_attr|merge({for: id}) -%}
{%- endif -%}
{% 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 %}>{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}{% block form_label_errors %}{{- form_errors(form) -}}{% endblock form_label_errors %}</{{ element|default('label') }}>
{%- else -%}
{%- if errors|length > 0 -%}
<div id="{{ id }}_errors" class="mb-2">
{{- form_errors(form) -}}
</div>
{%- endif -%}
{%- endif -%}
{%- endblock form_label %}
{% 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 is_parent_custom = parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class) %}
{% set is_custom = label_attr.class is defined and ('checkbox-custom' in label_attr.class or 'radio-custom' in label_attr.class) %}
{%- if is_parent_custom or is_custom -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' custom-control-label')|trim}) -%}
{%- else %}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%}
{%- endif %}
{%- if not compound -%}
{% set label_attr = label_attr|merge({'for': id}) %}
{%- endif -%}
{%- if required -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%}
{%- endif -%}
{%- 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': '', 'checkbox-custom': '', 'radio-custom': ''})|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 %}>
{{- label is not same as(false) ? (translation_domain is same as(false) ? label : label|trans({}, translation_domain)) -}}
{{- form_errors(form) -}}
</label>
{%- endif -%}
{%- endblock checkbox_radio_label %}
{# Rows #}
{% block form_row -%}
{%- if compound is defined and compound -%}
{%- set element = 'fieldset' -%}
{%- endif -%}
<{{ element|default('div') }} class="form-group">
{{- form_label(form) -}}
{{- form_widget(form) -}}
</{{ element|default('div') }}>
{%- endblock form_row %}
{# Errors #}
{% block form_errors -%}
{%- if errors|length > 0 -%}
<span class="{% if form is not rootform %}invalid-feedback{% else %}alert alert-danger{% endif %} d-block">
{%- for error in errors -%}
<span class="d-block">
<span class="form-error-icon badge badge-danger text-uppercase">{{ 'Error'|trans({}, 'validators') }}</span> <span class="form-error-message">{{ error.message }}</span>
</span>
{%- endfor -%}
</span>
{%- endif %}
{%- endblock form_errors %}

View File

@@ -0,0 +1,207 @@
{% use "form_div_layout.html.twig" %}
{# Widgets #}
{% block textarea_widget -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %}
{{- parent() -}}
{%- endblock textarea_widget %}
{% block money_widget -%}
{% set prepend = not (money_pattern starts with '{{') %}
{% set append = not (money_pattern ends with '}}') %}
{% if prepend or append %}
<div class="input-group{{ group_class|default('') }}">
{% if prepend %}
<span class="input-group-addon">{{ money_pattern|form_encode_currency }}</span>
{% endif %}
{{- block('form_widget_simple') -}}
{% if append %}
<span class="input-group-addon">{{ money_pattern|form_encode_currency }}</span>
{% endif %}
</div>
{% else %}
{{- block('form_widget_simple') -}}
{% endif %}
{%- endblock money_widget %}
{% block percent_widget -%}
<div class="input-group">
{{- block('form_widget_simple') -}}
<span class="input-group-addon">%</span>
</div>
{%- endblock percent_widget %}
{% block datetime_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
<div {{ block('widget_container_attributes') }}>
{{- form_errors(form.date) -}}
{{- form_errors(form.time) -}}
<div class="sr-only">
{%- if form.date.year is defined %}{{ form_label(form.date.year) }}{% endif -%}
{%- if form.date.month is defined %}{{ form_label(form.date.month) }}{% endif -%}
{%- if form.date.day is defined %}{{ form_label(form.date.day) }}{% endif -%}
{%- if form.time.hour is defined %}{{ form_label(form.time.hour) }}{% endif -%}
{%- if form.time.minute is defined %}{{ form_label(form.time.minute) }}{% endif -%}
{%- if form.time.second is defined %}{{ form_label(form.time.second) }}{% endif -%}
</div>
{{- form_widget(form.date, { datetime: true } ) -}}
{{- form_widget(form.time, { datetime: true } ) -}}
</div>
{%- endif -%}
{%- endblock datetime_widget %}
{% block date_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
{%- if datetime is not defined or not datetime -%}
<div {{ block('widget_container_attributes') -}}>
{%- endif %}
<div class="sr-only">
{{ form_label(form.year) }}
{{ form_label(form.month) }}
{{ form_label(form.day) }}
</div>
{{- date_pattern|replace({
'{{ year }}': form_widget(form.year),
'{{ month }}': form_widget(form.month),
'{{ day }}': form_widget(form.day),
})|raw -}}
{%- if datetime is not defined or not datetime -%}
</div>
{%- endif -%}
{%- endif -%}
{%- endblock date_widget %}
{% block time_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
{%- if datetime is not defined or false == datetime -%}
<div {{ block('widget_container_attributes') -}}>
{%- endif -%}
<div class="sr-only">{{ form_label(form.hour) }}</div>
{{- form_widget(form.hour) -}}
{%- if with_minutes -%}:<div class="sr-only">{{ form_label(form.minute) }}</div>{{ form_widget(form.minute) }}{%- endif -%}
{%- if with_seconds -%}:<div class="sr-only">{{ form_label(form.second) }}</div>{{ form_widget(form.second) }}{%- endif -%}
{%- if datetime is not defined or false == datetime -%}
</div>
{%- endif -%}
{%- endif -%}
{%- endblock time_widget %}
{%- block dateinterval_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%}
<div {{ block('widget_container_attributes') }}>
{{- form_errors(form) -}}
<div class="table-responsive">
<table class="table {{ table_class|default('table-bordered table-condensed table-striped') }}" role="presentation">
<thead>
<tr>
{%- if with_years %}<th>{{ form_label(form.years) }}</th>{% endif -%}
{%- if with_months %}<th>{{ form_label(form.months) }}</th>{% endif -%}
{%- if with_weeks %}<th>{{ form_label(form.weeks) }}</th>{% endif -%}
{%- if with_days %}<th>{{ form_label(form.days) }}</th>{% endif -%}
{%- if with_hours %}<th>{{ form_label(form.hours) }}</th>{% endif -%}
{%- if with_minutes %}<th>{{ form_label(form.minutes) }}</th>{% endif -%}
{%- if with_seconds %}<th>{{ form_label(form.seconds) }}</th>{% endif -%}
</tr>
</thead>
<tbody>
<tr>
{%- if with_years %}<td>{{ form_widget(form.years) }}</td>{% endif -%}
{%- if with_months %}<td>{{ form_widget(form.months) }}</td>{% endif -%}
{%- if with_weeks %}<td>{{ form_widget(form.weeks) }}</td>{% endif -%}
{%- if with_days %}<td>{{ form_widget(form.days) }}</td>{% endif -%}
{%- if with_hours %}<td>{{ form_widget(form.hours) }}</td>{% endif -%}
{%- if with_minutes %}<td>{{ form_widget(form.minutes) }}</td>{% endif -%}
{%- if with_seconds %}<td>{{ form_widget(form.seconds) }}</td>{% endif -%}
</tr>
</tbody>
</table>
</div>
{%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%}
</div>
{%- endif -%}
{%- endblock dateinterval_widget -%}
{% block choice_widget_collapsed -%}
{%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%}
{{- parent() -}}
{%- endblock choice_widget_collapsed %}
{% block choice_widget_expanded -%}
{%- if '-inline' in label_attr.class|default('') -%}
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{% endfor -%}
{%- else -%}
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{%- endfor -%}
</div>
{%- endif -%}
{%- endblock choice_widget_expanded %}
{# Labels #}
{% block choice_label -%}
{# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #}
{%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': '', 'checkbox-custom': '', 'radio-custom': ''})|trim}) -%}
{{- block('form_label') -}}
{% endblock choice_label %}
{% block checkbox_label -%}
{{- block('checkbox_radio_label') -}}
{%- endblock checkbox_label %}
{% block radio_label -%}
{{- block('checkbox_radio_label') -}}
{%- endblock radio_label %}
{# Rows #}
{% block button_row -%}
<div class="form-group">
{{- form_widget(form) -}}
</div>
{%- endblock button_row %}
{% block choice_row -%}
{%- set force_error = true -%}
{{- block('form_row') -}}
{%- endblock choice_row %}
{% block date_row -%}
{%- set force_error = true -%}
{{- block('form_row') -}}
{%- endblock date_row %}
{% block time_row -%}
{%- set force_error = true -%}
{{- block('form_row') -}}
{%- endblock time_row %}
{% block datetime_row -%}
{%- set force_error = true -%}
{{- block('form_row') -}}
{%- endblock datetime_row %}

View File

@@ -24,7 +24,7 @@
{%- endblock form_widget_compound -%}
{%- block collection_widget -%}
{% if prototype is defined %}
{% if prototype is defined and not prototype.rendered %}
{%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%}
{% endif %}
{{- block('form_widget') -}}
@@ -79,7 +79,7 @@
{{- block('choice_widget_options') -}}
</optgroup>
{%- else -%}
<option value="{{ choice.value }}"{% if choice.attr %} {% set attr = choice.attr %}{{ block('attributes') }}{% endif %}{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is same as(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}</option>
<option value="{{ choice.value }}"{% if choice.attr %}{% with { attr: choice.attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is same as(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}</option>
{%- endif -%}
{% endfor %}
{%- endblock choice_widget_options -%}
@@ -130,6 +130,41 @@
{%- endif -%}
{%- endblock time_widget -%}
{%- block dateinterval_widget -%}
{%- if widget == 'single_text' -%}
{{- block('form_widget_simple') -}}
{%- else -%}
<div {{ block('widget_container_attributes') }}>
{{- form_errors(form) -}}
<table class="{{ table_class|default('') }}" role="presentation">
<thead>
<tr>
{%- if with_years %}<th>{{ form_label(form.years) }}</th>{% endif -%}
{%- if with_months %}<th>{{ form_label(form.months) }}</th>{% endif -%}
{%- if with_weeks %}<th>{{ form_label(form.weeks) }}</th>{% endif -%}
{%- if with_days %}<th>{{ form_label(form.days) }}</th>{% endif -%}
{%- if with_hours %}<th>{{ form_label(form.hours) }}</th>{% endif -%}
{%- if with_minutes %}<th>{{ form_label(form.minutes) }}</th>{% endif -%}
{%- if with_seconds %}<th>{{ form_label(form.seconds) }}</th>{% endif -%}
</tr>
</thead>
<tbody>
<tr>
{%- if with_years %}<td>{{ form_widget(form.years) }}</td>{% endif -%}
{%- if with_months %}<td>{{ form_widget(form.months) }}</td>{% endif -%}
{%- if with_weeks %}<td>{{ form_widget(form.weeks) }}</td>{% endif -%}
{%- if with_days %}<td>{{ form_widget(form.days) }}</td>{% endif -%}
{%- if with_hours %}<td>{{ form_widget(form.hours) }}</td>{% endif -%}
{%- if with_minutes %}<td>{{ form_widget(form.minutes) }}</td>{% endif -%}
{%- if with_seconds %}<td>{{ form_widget(form.seconds) }}</td>{% endif -%}
</tr>
</tbody>
</table>
{%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%}
</div>
{%- endif -%}
{%- endblock dateinterval_widget -%}
{%- block number_widget -%}
{# type="number" doesn't work with floats #}
{%- set type = type|default('text') -%}
@@ -187,6 +222,8 @@
'%name%': name,
'%id%': id,
}) %}
{%- elseif label is same as(false) -%}
{% set translation_domain = false %}
{%- else -%}
{% set label = name|humanize %}
{%- endif -%}
@@ -204,6 +241,16 @@
{{ block('button_widget') }}
{%- endblock reset_widget -%}
{%- block tel_widget -%}
{%- set type = type|default('tel') -%}
{{ block('form_widget_simple') }}
{%- endblock tel_widget -%}
{%- block color_widget -%}
{%- set type = type|default('color') -%}
{{ block('form_widget_simple') }}
{%- endblock color_widget -%}
{# Labels #}
{%- block form_label -%}
@@ -224,13 +271,13 @@
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
<{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}>
{%- if translation_domain is same as(false) -%}
{{- label -}}
{%- else -%}
{{- label|trans({}, translation_domain) -}}
{%- endif -%}
</label>
</{{ element|default('label') }}>
{%- endif -%}
{%- endblock form_label -%}
@@ -280,7 +327,7 @@
{%- else -%}
{% set form_method = "POST" %}
{%- endif -%}
<form{% if name != '' %} name="{{ name }}"{% endif %} method="{{ form_method|lower }}"{% if action != '' %} action="{{ action }}"{% endif %}{% for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}{% if multipart %} enctype="multipart/form-data"{% 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 }}" />
{%- endif -%}
@@ -293,10 +340,6 @@
</form>
{%- endblock form_end -%}
{%- block form_enctype -%}
{% if multipart %}enctype="multipart/form-data"{% endif %}
{%- endblock form_enctype -%}
{%- block form_errors -%}
{%- if errors|length > 0 -%}
<ul>
@@ -339,47 +382,19 @@
{%- block widget_attributes -%}
id="{{ id }}" name="{{ full_name }}"
{%- if read_only %} readonly="readonly"{% endif -%}
{%- if disabled %} disabled="disabled"{% endif -%}
{%- if required %} required="required"{% endif -%}
{%- for attrname, attrvalue in attr if 'readonly' != attrname or not read_only -%}
{{- " " -}}
{%- if attrname in ['placeholder', 'title'] -%}
{{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}"
{%- elseif attrvalue is same as(true) -%}
{{- attrname }}="{{ attrname }}"
{%- elseif attrvalue is not same as(false) -%}
{{- attrname }}="{{ attrvalue }}"
{%- endif -%}
{%- endfor -%}
{{ block('attributes') }}
{%- endblock widget_attributes -%}
{%- block widget_container_attributes -%}
{%- if id is not empty %}id="{{ id }}"{% endif -%}
{%- for attrname, attrvalue in attr -%}
{{- " " -}}
{%- if attrname in ['placeholder', 'title'] -%}
{{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}"
{%- elseif attrvalue is same as(true) -%}
{{- attrname }}="{{ attrname }}"
{%- elseif attrvalue is not same as(false) -%}
{{- attrname }}="{{ attrvalue }}"
{%- endif -%}
{%- endfor -%}
{{ block('attributes') }}
{%- endblock widget_container_attributes -%}
{%- block button_attributes -%}
id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif -%}
{%- for attrname, attrvalue in attr -%}
{{- " " -}}
{%- if attrname in ['placeholder', 'title'] -%}
{{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}"
{%- elseif attrvalue is same as(true) -%}
{{- attrname }}="{{ attrname }}"
{%- elseif attrvalue is not same as(false) -%}
{{- attrname }}="{{ attrvalue }}"
{%- endif -%}
{%- endfor -%}
{{ block('attributes') }}
{%- endblock button_attributes -%}
{% block attributes -%}

View File

@@ -20,14 +20,14 @@
{% block button_widget -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %}
{{- parent() -}}
{%- endblock %}
{%- endblock button_widget %}
{% block money_widget -%}
<div class="row collapse">
{% set prepend = '{{' == money_pattern[0:2] %}
{% if not prepend %}
<div class="small-3 large-2 columns">
<span class="prefix">{{ money_pattern|replace({ '{{ widget }}':''}) }}</span>
<span class="prefix">{{ money_pattern|form_encode_currency }}</span>
</div>
{% endif %}
<div class="small-9 large-10 columns">
@@ -35,7 +35,7 @@
</div>
{% if prepend %}
<div class="small-3 large-2 columns">
<span class="postfix">{{ money_pattern|replace({ '{{ widget }}':''}) }}</span>
<span class="postfix">{{ money_pattern|form_encode_currency }}</span>
</div>
{% endif %}
</div>
@@ -228,7 +228,7 @@
{# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #}
{% set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) %}
{{- block('form_label') -}}
{%- endblock %}
{%- endblock choice_label %}
{% block checkbox_label -%}
{{- block('checkbox_radio_label') -}}
@@ -258,7 +258,7 @@
{% set label = name|humanize %}
{%- endif -%}
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
<label{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
{{ widget|raw }}
{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}
</label>
@@ -318,11 +318,11 @@
{% block form_errors -%}
{% if errors|length > 0 -%}
{% if form.parent %}<small class="error">{% else %}<div data-alert class="alert-box alert">{% endif %}
{% if form is not rootform %}<small class="error">{% else %}<div data-alert class="alert-box alert">{% endif %}
{%- for error in errors -%}
{{ error.message }}
{% if not loop.last %}, {% endif %}
{%- endfor -%}
{% if form.parent %}</small>{% else %}</div>{% endif %}
{% if form is not rootform %}</small>{% else %}</div>{% endif %}
{%- endif %}
{%- endblock form_errors %}

View File

@@ -35,12 +35,17 @@ class FormThemeTokenParser extends AbstractTokenParser
$stream = $this->parser->getStream();
$form = $this->parser->getExpressionParser()->parseExpression();
$only = false;
if ($this->parser->getStream()->test(Token::NAME_TYPE, 'with')) {
$this->parser->getStream()->next();
$resources = $this->parser->getExpressionParser()->parseExpression();
if ($this->parser->getStream()->nextIf(Token::NAME_TYPE, 'only')) {
$only = true;
}
} else {
$resources = new ArrayExpression(array(), $stream->getCurrent()->getLine());
$resources = new ArrayExpression([], $stream->getCurrent()->getLine());
do {
$resources->addElement($this->parser->getExpressionParser()->parseExpression());
} while (!$stream->test(Token::BLOCK_END_TYPE));
@@ -48,7 +53,7 @@ class FormThemeTokenParser extends AbstractTokenParser
$stream->expect(Token::BLOCK_END_TYPE);
return new FormThemeNode($form, $resources, $lineno, $this->getTag());
return new FormThemeNode($form, $resources, $lineno, $this->getTag(), $only);
}
/**

View File

@@ -41,7 +41,7 @@ class StopwatchTokenParser extends AbstractTokenParser
$stream->expect(Token::BLOCK_END_TYPE);
// {% endstopwatch %}
$body = $this->parser->subparse(array($this, 'decideStopwatchEnd'), true);
$body = $this->parser->subparse([$this, 'decideStopwatchEnd'], true);
$stream->expect(Token::BLOCK_END_TYPE);
if ($this->stopwatchIsAvailable) {

View File

@@ -15,7 +15,6 @@ use Symfony\Bridge\Twig\Node\TransNode;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Node;
use Twig\Node\TextNode;
use Twig\Token;
@@ -27,18 +26,14 @@ use Twig\Token;
class TransChoiceTokenParser extends TransTokenParser
{
/**
* Parses a token and returns a node.
*
* @return Node
*
* @throws SyntaxError
* {@inheritdoc}
*/
public function parse(Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$vars = new ArrayExpression(array(), $lineno);
$vars = new ArrayExpression([], $lineno);
$count = $this->parser->getExpressionParser()->parseExpression();
@@ -65,10 +60,10 @@ class TransChoiceTokenParser extends TransTokenParser
$stream->expect(Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideTransChoiceFork'), true);
$body = $this->parser->subparse([$this, 'decideTransChoiceFork'], true);
if (!$body instanceof TextNode && !$body instanceof AbstractExpression) {
throw new SyntaxError('A message inside a transchoice tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext()->getName());
throw new SyntaxError('A message inside a transchoice tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext());
}
$stream->expect(Token::BLOCK_END_TYPE);
@@ -78,13 +73,11 @@ class TransChoiceTokenParser extends TransTokenParser
public function decideTransChoiceFork($token)
{
return $token->test(array('endtranschoice'));
return $token->test(['endtranschoice']);
}
/**
* Gets the tag name associated with this token parser.
*
* @return string The tag name
* {@inheritdoc}
*/
public function getTag()
{

View File

@@ -12,7 +12,6 @@
namespace Symfony\Bridge\Twig\TokenParser;
use Symfony\Bridge\Twig\Node\TransDefaultDomainNode;
use Twig\Node\Node;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;
@@ -24,9 +23,7 @@ use Twig\TokenParser\AbstractTokenParser;
class TransDefaultDomainTokenParser extends AbstractTokenParser
{
/**
* Parses a token and returns a node.
*
* @return Node
* {@inheritdoc}
*/
public function parse(Token $token)
{
@@ -38,9 +35,7 @@ class TransDefaultDomainTokenParser extends AbstractTokenParser
}
/**
* Gets the tag name associated with this token parser.
*
* @return string The tag name
* {@inheritdoc}
*/
public function getTag()
{

View File

@@ -15,7 +15,6 @@ use Symfony\Bridge\Twig\Node\TransNode;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Node;
use Twig\Node\TextNode;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;
@@ -28,18 +27,14 @@ use Twig\TokenParser\AbstractTokenParser;
class TransTokenParser extends AbstractTokenParser
{
/**
* Parses a token and returns a node.
*
* @return Node
*
* @throws SyntaxError
* {@inheritdoc}
*/
public function parse(Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$vars = new ArrayExpression(array(), $lineno);
$vars = new ArrayExpression([], $lineno);
$domain = null;
$locale = null;
if (!$stream->test(Token::BLOCK_END_TYPE)) {
@@ -60,16 +55,16 @@ class TransTokenParser extends AbstractTokenParser
$stream->next();
$locale = $this->parser->getExpressionParser()->parseExpression();
} elseif (!$stream->test(Token::BLOCK_END_TYPE)) {
throw new SyntaxError('Unexpected token. Twig was looking for the "with", "from", or "into" keyword.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
throw new SyntaxError('Unexpected token. Twig was looking for the "with", "from", or "into" keyword.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
}
// {% trans %}message{% endtrans %}
$stream->expect(Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($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()->getName());
throw new SyntaxError('A message inside a trans tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext());
}
$stream->expect(Token::BLOCK_END_TYPE);
@@ -79,13 +74,11 @@ class TransTokenParser extends AbstractTokenParser
public function decideTransFork($token)
{
return $token->test(array('endtrans'));
return $token->test(['endtrans']);
}
/**
* Gets the tag name associated with this token parser.
*
* @return string The tag name
* {@inheritdoc}
*/
public function getTag()
{

View File

@@ -12,7 +12,6 @@
namespace Symfony\Bridge\Twig\Translation;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Translation\Extractor\AbstractFileExtractor;
use Symfony\Component\Translation\Extractor\ExtractorInterface;
use Symfony\Component\Translation\MessageCatalogue;
@@ -58,17 +57,7 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface
try {
$this->extractTemplate(file_get_contents($file->getPathname()), $catalogue);
} catch (Error $e) {
if ($file instanceof \SplFileInfo) {
$path = $file->getRealPath() ?: $file->getPathname();
$name = $file instanceof SplFileInfo ? $file->getRelativePathname() : $path;
if (method_exists($e, 'setSourceContext')) {
$e->setSourceContext(new Source('', $name, $path));
} else {
$e->setTemplateName($name);
}
}
throw $e;
// ignore errors, these should be fixed by using the linter
}
}
}
@@ -106,9 +95,7 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface
}
/**
* @param string|array $directory
*
* @return array
* {@inheritdoc}
*/
protected function extractFromDirectory($directory)
{

View File

@@ -19,6 +19,7 @@ use Twig\Environment;
use Twig\Error\Error;
use Twig\Error\LoaderError;
use Twig\Loader\ExistsLoaderInterface;
use Twig\Loader\SourceContextLoaderInterface;
use Twig\Template;
/**
@@ -44,7 +45,7 @@ class TwigEngine implements EngineInterface, StreamingEngineInterface
*
* @throws Error if something went wrong like a thrown exception while rendering the template
*/
public function render($name, array $parameters = array())
public function render($name, array $parameters = [])
{
return $this->load($name)->render($parameters);
}
@@ -56,7 +57,7 @@ class TwigEngine implements EngineInterface, StreamingEngineInterface
*
* @throws Error if something went wrong like a thrown exception while rendering the template
*/
public function stream($name, array $parameters = array())
public function stream($name, array $parameters = [])
{
$this->load($name)->display($parameters);
}
@@ -74,19 +75,24 @@ class TwigEngine implements EngineInterface, StreamingEngineInterface
$loader = $this->environment->getLoader();
if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) {
return $loader->exists((string) $name);
}
if (1 === Environment::MAJOR_VERSION && !$loader instanceof ExistsLoaderInterface) {
try {
// cast possible TemplateReferenceInterface to string because the
// EngineInterface supports them but LoaderInterface does not
if ($loader instanceof SourceContextLoaderInterface) {
$loader->getSourceContext((string) $name);
} else {
$loader->getSource((string) $name);
}
return true;
} catch (LoaderError $e) {
}
try {
// cast possible TemplateReferenceInterface to string because the
// EngineInterface supports them but LoaderInterface does not
$loader->getSourceContext((string) $name)->getCode();
} catch (LoaderError $e) {
return false;
}
return true;
return $loader->exists((string) $name);
}
/**

View File

@@ -0,0 +1,97 @@
<?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;
use Symfony\Bundle\FullStack;
use Twig\Error\SyntaxError;
/**
* @internal
*/
class UndefinedCallableHandler
{
private static $filterComponents = [
'humanize' => 'form',
'trans' => 'translation',
'transchoice' => 'translation',
'yaml_encode' => 'yaml',
'yaml_dump' => 'yaml',
];
private static $functionComponents = [
'asset' => 'asset',
'asset_version' => 'asset',
'dump' => 'debug-bundle',
'expression' => 'expression-language',
'form_widget' => 'form',
'form_errors' => 'form',
'form_label' => 'form',
'form_row' => 'form',
'form_rest' => 'form',
'form' => 'form',
'form_start' => 'form',
'form_end' => 'form',
'csrf_token' => 'form',
'logout_url' => 'security-http',
'logout_path' => 'security-http',
'is_granted' => 'security-core',
'link' => 'web-link',
'preload' => 'web-link',
'dns_prefetch' => 'web-link',
'preconnect' => 'web-link',
'prefetch' => 'web-link',
'prerender' => 'web-link',
'workflow_can' => 'workflow',
'workflow_transitions' => 'workflow',
'workflow_has_marked_place' => 'workflow',
'workflow_marked_places' => 'workflow',
];
private static $fullStackEnable = [
'form' => 'enable "framework.form"',
'security-core' => 'add the "SecurityBundle"',
'security-http' => 'add the "SecurityBundle"',
'web-link' => 'enable "framework.web_link"',
'workflow' => 'enable "framework.workflows"',
];
public static function onUndefinedFilter($name)
{
if (!isset(self::$filterComponents[$name])) {
return false;
}
self::onUndefined($name, 'filter', self::$filterComponents[$name]);
return true;
}
public static function onUndefinedFunction($name)
{
if (!isset(self::$functionComponents[$name])) {
return false;
}
self::onUndefined($name, 'function', self::$functionComponents[$name]);
return true;
}
private static function onUndefined($name, $type, $component)
{
if (class_exists(FullStack::class) && isset(self::$fullStackEnable[$component])) {
throw new SyntaxError(sprintf('Did you forget to %s? Unknown %s "%s".', self::$fullStackEnable[$component], $type, $name));
}
throw new SyntaxError(sprintf('Did you forget to run "composer require symfony/%s"? Unknown %s "%s".', $component, $type, $name));
}
}

View File

@@ -16,29 +16,34 @@
}
],
"require": {
"php": ">=5.3.9",
"twig/twig": "~1.34|~2.4"
"php": "^5.5.9|>=7.0.8",
"twig/twig": "^1.41|^2.10"
},
"require-dev": {
"symfony/asset": "~2.7|~3.0.0",
"symfony/finder": "~2.3|~3.0.0",
"symfony/form": "^2.8.23",
"symfony/http-foundation": "^2.8.29|~3.0.0",
"symfony/http-kernel": "~2.8|~3.0.0",
"fig/link-util": "^1.0",
"symfony/asset": "~2.8|~3.0|~4.0",
"symfony/dependency-injection": "~2.8|~3.0|~4.0",
"symfony/finder": "~2.8|~3.0|~4.0",
"symfony/form": "^3.4.31|^4.3.4",
"symfony/http-foundation": "^3.3.11|~4.0",
"symfony/http-kernel": "~3.2|~4.0",
"symfony/polyfill-intl-icu": "~1.0",
"symfony/routing": "~2.2|~3.0.0",
"symfony/templating": "~2.1|~3.0.0",
"symfony/translation": "~2.7|~3.0.0",
"symfony/yaml": "^2.0.5|~3.0.0",
"symfony/security": "^2.8.31|^3.3.13",
"symfony/security-acl": "~2.6|~3.0.0",
"symfony/stopwatch": "~2.2|~3.0.0",
"symfony/console": "~2.8|~3.0.0",
"symfony/var-dumper": "~2.7.16|~2.8.9|~3.0.9",
"symfony/expression-language": "~2.4|~3.0.0"
"symfony/routing": "~2.8|~3.0|~4.0",
"symfony/templating": "~2.8|~3.0|~4.0",
"symfony/translation": "~2.8|~3.0|~4.0",
"symfony/yaml": "~2.8|~3.0|~4.0",
"symfony/security": "^2.8.31|^3.3.13|~4.0",
"symfony/security-acl": "~2.8|~3.0",
"symfony/stopwatch": "~2.8|~3.0|~4.0",
"symfony/console": "~3.4|~4.0",
"symfony/var-dumper": "~2.8.10|~3.1.4|~3.2|~4.0",
"symfony/expression-language": "~2.8|~3.0|~4.0",
"symfony/web-link": "~3.3|~4.0",
"symfony/workflow": "~3.3|~4.0"
},
"conflict": {
"symfony/form": "<2.8.23"
"symfony/form": "<3.4.31|>=4.0,<4.3.4",
"symfony/console": "<3.4"
},
"suggest": {
"symfony/finder": "",
@@ -52,7 +57,8 @@
"symfony/security": "For using the SecurityExtension",
"symfony/stopwatch": "For using the StopwatchExtension",
"symfony/var-dumper": "For using the DumpExtension",
"symfony/expression-language": "For using the ExpressionExtension"
"symfony/expression-language": "For using the ExpressionExtension",
"symfony/web-link": "For using the WebLinkExtension"
},
"autoload": {
"psr-4": { "Symfony\\Bridge\\Twig\\": "" },
@@ -63,7 +69,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.4-dev"
}
}
}