diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index fb402c9aaf2ca..c93959532d398 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,15 @@ in 2.7 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.7.0...v2.7.1 +* 2.7.29 (2017-06-07) + + * bug #23069 [SecurityBundle] Show unique Inherited roles in profile panel (yceruto) + * bug #23073 [TwigBridge] Fix namespaced classes (ogizanagi) + * bug #22936 [Form] Mix attr option between guessed options and user options (yceruto) + * bug #23024 [EventDispatcher] Fix ContainerAwareEventDispatcher::hasListeners(null) (nicolas-grekas) + * bug #22996 [Form] Fix \IntlDateFormatter timezone parameter usage to bypass PHP bug #66323 (romainneutron) + * bug #22994 Harden the debugging of Twig filters and functions (stof) + * 2.7.28 (2017-05-29) * bug #22847 [Console] ChoiceQuestion must have choices (ro0NL) diff --git a/CHANGELOG-2.8.md b/CHANGELOG-2.8.md index da6f2aeebb9fb..ce55fb16317b8 100644 --- a/CHANGELOG-2.8.md +++ b/CHANGELOG-2.8.md @@ -7,6 +7,40 @@ in 2.8 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.8.0...v2.8.1 +* 2.8.23 (2017-07-04) + + * bug #23341 [DoctrineBridge][Security][Validator] do not validate empty values (xabbuh) + * bug #23274 Display a better error design when the toolbar cannot be displayed (yceruto) + * bug #23333 [PropertyAccess] Fix TypeError discard (dunglas) + * bug #23345 [Console] fix description of INF default values (xabbuh) + * bug #23279 Don't call count on non countable object (pierredup) + * bug #23283 [TwigBundle] add back exception check (xabbuh) + * bug #23268 Show exception is checked twice in ExceptionController of twig (gmponos) + * bug #23266 Display a better error message when the toolbar cannot be displayed (javiereguiluz) + * bug #23271 [FrameworkBundle] allow SSI fragments configuration in XML files (xabbuh) + * bug #23254 [Form][TwigBridge] render hidden _method field in form_rest() (xabbuh) + * bug #23250 [Translation] return fallback locales whenever possible (xabbuh) + * bug #23240 [Console] Fix catching exception type in QuestionHelper (voronkovich) + * bug #23229 [WebProfilerBundle] Eliminate line wrap on count column (routing) (e-moe) + * bug #22732 [Security] fix switch user _exit without having current token (dmaicher) + * bug #22730 [FrameworkBundle] Sessions: configurable "use_strict_mode" option for NativeSessionStorage (MacDada) + * bug #23195 [FrameworkBundle] [Command] Clean bundle directory, fixes #23177 (NicolasPion) + * bug #23052 [TwigBundle] Add Content-Type header for exception response (rchoquet) + * bug #23199 Reset redirectCount when throwing exception (hvanoch) + * bug #23186 [TwigBundle] Move template.xml loading to a compiler pass (ogizanagi) + * bug #23130 Keep s-maxage when expiry and validation are used in combination (mpdude) + * bug #23129 Fix two edge cases in ResponseCacheStrategy (mpdude) + * feature #22636 [Routing] Expose request in route conditions, if needed and possible (ro0NL) + * bug #22636 [Routing] Expose request in route conditions, if needed and possible (ro0NL) + * bug #23057 [Translation][FrameworkBundle] Fix resource loading order inconsistency reported in #23034 (mpdude) + * bug #23092 [Filesystem] added workaround in Filesystem::rename for PHP bug (VolCh) + * bug #23128 [HttpFoundation] fix for Support for new 7.1 session options (vincentaubert) + * bug #23176 [VarDumper] fixes (nicolas-grekas) + * bug #22953 #22839 - changed debug toolbar dump section to relative and use full window width (mkurzeja) + * bug #23086 [FrameworkBundle] Fix perf issue in CacheClearCommand::warmup() (nicolas-grekas) + * bug #23098 Cache ipCheck (2.7) (gonzalovilaseca) + * bug #23069 [SecurityBundle] Show unique Inherited roles in profile panel (yceruto) + * 2.8.22 (2017-06-07) * bug #23073 [TwigBridge] Fix namespaced classes (ogizanagi) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 66212cf3bf4c4..532b8a261c4f2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -20,13 +20,13 @@ Symfony is the result of the work of many people who made the code better - Javier Eguiluz (javier.eguiluz) - Hugo Hamon (hhamon) - Abdellatif Ait boudad (aitboudad) + - Romain Neutron (romain) - Pascal Borreli (pborreli) - Wouter De Jong (wouterj) - - Romain Neutron (romain) - - Grégoire Pineau (lyrixx) - Robin Chalas (chalas_r) - - Joseph Bielawski (stloyd) - Maxime Steinhausser (ogizanagi) + - Grégoire Pineau (lyrixx) + - Joseph Bielawski (stloyd) - Karma Dordrak (drak) - Lukas Kahwe Smith (lsmith) - Martin Hasoň (hason) @@ -72,8 +72,8 @@ Symfony is the result of the work of many people who made the code better - Dariusz Górecki (canni) - Titouan Galopin (tgalopin) - Douglas Greenshields (shieldo) - - Konstantin Myakshin (koc) - Jáchym Toušek (enumag) + - Konstantin Myakshin (koc) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) @@ -112,10 +112,10 @@ Symfony is the result of the work of many people who made the code better - Jacob Dreesen (jdreesen) - Tobias Nyholm (tobias) - Tomáš Votruba (tomas_votruba) + - Yonel Ceruto González (yonelceruto) - Fabien Pennequin (fabienpennequin) - Gordon Franke (gimler) - Eric GELOEN (gelo) - - Yonel Ceruto González (yonelceruto) - Daniel Wehner (dawehner) - Tugdual Saunier (tucksaun) - Théo FIDRY (theofidry) @@ -130,6 +130,7 @@ Symfony is the result of the work of many people who made the code better - Daniel Gomes (danielcsgomes) - Hidenori Goto (hidenorigoto) - Guilherme Blanco (guilhermeblanco) + - Vincent AUBERT (vincent) - Pablo Godel (pgodel) - Jérémie Augustin (jaugustin) - Andréia Bohner (andreia) @@ -140,10 +141,10 @@ Symfony is the result of the work of many people who made the code better - Joel Wurtz (brouznouf) - Philipp Wahala (hifi) - Vyacheslav Pavlov + - Richard van Laak (rvanlaak) - Javier Spagnoletti (phansys) - Richard Shank (iampersistent) - Thomas Rabaix (rande) - - Vincent AUBERT (vincent) - Rouven Weßling (realityking) - Teoh Han Hui (teohhanhui) - Jérôme Vasseur (jvasseur) @@ -151,7 +152,6 @@ Symfony is the result of the work of many people who made the code better - Helmer Aaviksoo - Grégoire Paris (greg0ire) - Hiromi Hishida (77web) - - Richard van Laak (rvanlaak) - Matthieu Ouellette-Vachon (maoueh) - Michał Pipa (michal.pipa) - Amal Raghav (kertz) @@ -242,6 +242,7 @@ Symfony is the result of the work of many people who made the code better - Uwe Jäger (uwej711) - Eugene Leonovich (rybakit) - Filippo Tessarotto + - Oleg Voronkovich - Joseph Rouff (rouffj) - Félix Labrecque (woodspire) - GordonsLondon @@ -277,7 +278,6 @@ Symfony is the result of the work of many people who made the code better - Jordan Samouh (jordansamouh) - Chris Smith (cs278) - Florian Klein (docteurklein) - - Oleg Voronkovich - Manuel Kiessling (manuelkiessling) - Atsuhiro KUBO (iteman) - Andrew Moore (finewolf) @@ -387,6 +387,7 @@ Symfony is the result of the work of many people who made the code better - Emanuele Gaspari (inmarelibero) - Sébastien Santoro (dereckson) - Brian King + - Frank de Jonge (frenkynet) - Michel Salib (michelsalib) - geoffrey - Steffen Roßkamp @@ -523,7 +524,6 @@ Symfony is the result of the work of many people who made the code better - Romain Pierre (romain-pierre) - Jan Behrens - Mantas Var (mvar) - - Frank de Jonge (frenkynet) - Sebastian Krebs - Jean-Christophe Cuvelier [Artack] - Christopher Davis (chrisguitarguy) @@ -1278,6 +1278,7 @@ Symfony is the result of the work of many people who made the code better - ged15 - Daan van Renterghem - Nicole Cordes + - Martin Kirilov - Bram Van der Sype (brammm) - Christopher Hertel (chertel) - Guile (guile) diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 9d5f03a282b5a..b24553b4e7d5b 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -62,6 +62,10 @@ public function validate($entity, Constraint $constraint) throw new ConstraintDefinitionException('At least one field has to be specified.'); } + if (null === $entity) { + return; + } + if ($constraint->em) { $em = $this->registry->getManager($constraint->em); diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 09d11388089f7..d7383ea9bb9f4 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -267,6 +267,7 @@ {%- endblock form -%} {%- block form_start -%} + {%- do form.setMethodRendered() -%} {% set method = method|upper %} {%- if method in ["GET", "POST"] -%} {% set form_method = method %} @@ -306,6 +307,20 @@ {{- form_row(child) -}} {% endif %} {%- endfor %} + + {% if not form.methodRendered %} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in ["GET", "POST"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = "POST" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} + {% endif %} {% endblock form_rest %} {# Support #} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index a711b77d7987c..820e1b12e2327 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -22,7 +22,7 @@ "require-dev": { "symfony/asset": "~2.7|~3.0.0", "symfony/finder": "~2.3|~3.0.0", - "symfony/form": "^2.8.19", + "symfony/form": "^2.8.23", "symfony/http-kernel": "~2.8|~3.0.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/routing": "~2.2|~3.0.0", @@ -36,6 +36,9 @@ "symfony/var-dumper": "~2.7.16|~2.8.9|~3.0.9", "symfony/expression-language": "~2.4|~3.0.0" }, + "conflict": { + "symfony/form": "<2.8.23" + }, "suggest": { "symfony/finder": "", "symfony/asset": "For using the AssetExtension", diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index 9f36c8d714d72..395d3e6d335c1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -110,6 +110,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $rows = array(); $copyUsed = false; $exitCode = 0; + $validAssetDirs = array(); /** @var BundleInterface $bundle */ foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) { if (!is_dir($originDir = $bundle->getPath().'/Resources/public')) { @@ -148,6 +149,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $exitCode = 1; $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */), $message, $e->getMessage()); } + $validAssetDirs[] = $targetDir; + } + // remove the assets of the bundles that no longer exist + foreach (new \FilesystemIterator($bundlesDir) as $dir) { + if (!in_array($dir, $validAssetDirs)) { + $this->filesystem->remove($dir); + } } $io->table(array('', 'Bundle', 'Method / Error'), $rows); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index f926b4153bcd5..de1cfc8e2024f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -147,7 +147,7 @@ protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = tr $safeTempKernel = str_replace('\\', '\\\\', get_class($tempKernel)); $realKernelFQN = get_class($realKernel); - foreach (Finder::create()->files()->name('*.meta')->in($warmupDir) as $file) { + foreach (Finder::create()->files()->depth('<3')->name('*.meta')->in($warmupDir) as $file) { file_put_contents($file, preg_replace( '/(C\:\d+\:)"'.$safeTempKernel.'"/', sprintf('$1"%s"', $realKernelFQN), @@ -159,14 +159,16 @@ protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = tr $search = array($warmupDir, str_replace('\\', '\\\\', $warmupDir)); $replace = str_replace('\\', '/', $realCacheDir); foreach (Finder::create()->files()->in($warmupDir) as $file) { - $content = str_replace($search, $replace, file_get_contents($file)); - file_put_contents($file, $content); + $content = str_replace($search, $replace, file_get_contents($file), $count); + if ($count) { + file_put_contents($file, $content); + } } // fix references to container's class $tempContainerClass = get_class($tempKernel->getContainer()); $realContainerClass = get_class($realKernel->getContainer()); - foreach (Finder::create()->files()->name($tempContainerClass.'*')->in($warmupDir) as $file) { + foreach (Finder::create()->files()->depth('<2')->name($tempContainerClass.'*')->in($warmupDir) as $file) { $content = str_replace($tempContainerClass, $realContainerClass, file_get_contents($file)); file_put_contents($file, $content); rename($file, str_replace(DIRECTORY_SEPARATOR.$tempContainerClass, DIRECTORY_SEPARATOR.$realContainerClass, $file)); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 05900e2db4bdd..8449474d0d170 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -390,6 +390,7 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->scalarNode('gc_divisor')->end() ->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_maxlifetime')->end() + ->booleanNode('use_strict_mode')->end() ->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end() ->integerNode('metadata_update_threshold') ->defaultValue('0') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 15f3e3c0e5672..afb20db752dac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -406,7 +406,7 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c // session storage $container->setAlias('session.storage', $config['storage_id']); $options = array(); - foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor') as $key) { + foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'use_strict_mode') as $key) { if (isset($config[$key])) { $options[$key] = $config[$key]; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 4e382f677f71b..d0aba42703fef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -22,6 +22,7 @@ + @@ -65,6 +66,10 @@ + + + + @@ -113,6 +118,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/AssetsHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/AssetsHelper.php index 6acc04539393d..172417924c921 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/AssetsHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/AssetsHelper.php @@ -78,7 +78,7 @@ public function getVersion($path = null, $packageName = null) // packageName is null and path not, so path is a path or a packageName try { - $package = $this->packages->getPackage($path); + $this->packages->getPackage($path); } catch (\InvalidArgumentException $e) { // path is not a package, so it should be a path return $this->packages->getVersion($path); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index b3e32a5eb599c..13decebe16236 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -16,6 +16,9 @@ 'esi' => array( 'enabled' => true, ), + 'ssi' => array( + 'enabled' => true, + ), 'profiler' => array( 'only_exceptions' => true, 'enabled' => false, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index c0ebb67ea15a5..4147ec917f37e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -12,6 +12,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 1e9de9c842548..46860412582b7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -10,6 +10,8 @@ framework: enabled: true esi: enabled: true + ssi: + enabled: true profiler: only_exceptions: true enabled: false diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 61908517f478e..cb5c70c1740f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -99,6 +99,13 @@ public function testEsi() $this->assertTrue($container->hasDefinition('esi'), '->registerEsiConfiguration() loads esi.xml'); } + public function testSsi() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('ssi'), '->registerSsiConfiguration() loads ssi.xml'); + } + public function testEnabledProfiler() { $container = $this->createContainerFromFile('profiler'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index 361bcf001b560..75f4281952227 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -135,6 +135,51 @@ public function testGetDefaultLocale() $this->assertSame('en', $translator->getLocale()); } + /** @dataProvider getDebugModeAndCacheDirCombinations */ + public function testResourceFilesOptionLoadsBeforeOtherAddedResources($debug, $enableCache) + { + $someCatalogue = $this->getCatalogue('some_locale', array()); + + $loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock(); + + $loader->expects($this->at(0)) + ->method('load') + /* The "messages.some_locale.loader" is passed via the resource_file option and shall be loaded first */ + ->with('messages.some_locale.loader', 'some_locale', 'messages') + ->willReturn($someCatalogue); + + $loader->expects($this->at(1)) + ->method('load') + /* This resource is added by an addResource() call and shall be loaded after the resource_files */ + ->with('second_resource.some_locale.loader', 'some_locale', 'messages') + ->willReturn($someCatalogue); + + $options = array( + 'resource_files' => array('some_locale' => array('messages.some_locale.loader')), + 'debug' => $debug, + ); + + if ($enableCache) { + $options['cache_dir'] = $this->tmpDir; + } + + /** @var Translator $translator */ + $translator = $this->createTranslator($loader, $options); + $translator->addResource('loader', 'second_resource.some_locale.loader', 'some_locale', 'messages'); + + $translator->trans('some_message', array(), null, 'some_locale'); + } + + public function getDebugModeAndCacheDirCombinations() + { + return array( + array(false, false), + array(true, false), + array(false, true), + array(true, true), + ); + } + protected function getCatalogue($locale, $messages, $resources = array()) { $catalogue = new MessageCatalogue($locale); diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php index fba6d70d6978b..9fdfb645d372a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -37,6 +37,14 @@ class Translator extends BaseTranslator implements WarmableInterface */ private $resourceLocales; + /** + * Holds parameters from addResource() calls so we can defer the actual + * parent::addResource() calls until initialize() is executed. + * + * @var array + */ + private $resources = array(); + /** * Constructor. * @@ -65,9 +73,7 @@ public function __construct(ContainerInterface $container, MessageSelector $sele $this->options = array_merge($this->options, $options); $this->resourceLocales = array_keys($this->options['resource_files']); - if (null !== $this->options['cache_dir'] && $this->options['debug']) { - $this->loadResources(); - } + $this->addResourceFiles($this->options['resource_files']); parent::__construct($container->getParameter('kernel.default_locale'), $selector, $this->options['cache_dir'], $this->options['debug']); } @@ -93,6 +99,11 @@ public function warmUp($cacheDir) } } + public function addResource($format, $resource, $locale, $domain = null) + { + $this->resources[] = array($format, $resource, $locale, $domain); + } + /** * {@inheritdoc} */ @@ -104,7 +115,12 @@ protected function initializeCatalogue($locale) protected function initialize() { - $this->loadResources(); + foreach ($this->resources as $key => $params) { + list($format, $resource, $locale, $domain) = $params; + parent::addResource($format, $resource, $locale, $domain); + } + $this->resources = array(); + foreach ($this->loaderIds as $id => $aliases) { foreach ($aliases as $alias) { $this->addLoader($alias, $this->container->get($id)); @@ -112,14 +128,13 @@ protected function initialize() } } - private function loadResources() + private function addResourceFiles($filesByLocale) { - foreach ($this->options['resource_files'] as $locale => $files) { + foreach ($filesByLocale as $locale => $files) { foreach ($files as $key => $file) { // filename is domain.locale.format list($domain, $locale, $format) = explode('.', basename($file), 3); $this->addResource($format, $file, $locale, $domain); - unset($this->options['resource_files'][$locale][$key]); } } } diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index e99b4242082de..3854221318e6f 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -100,7 +100,7 @@ public function collect(Request $request, Response $response, \Exception $except 'logout_url' => $logoutUrl, 'user' => $token->getUsername(), 'roles' => array_map(function (RoleInterface $role) { return $role->getRole(); }, $assignedRoles), - 'inherited_roles' => array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles), + 'inherited_roles' => array_unique(array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles)), 'supports_role_hierarchy' => null !== $this->roleHierarchy, ); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index 0357b612060c4..e8f808f98ee94 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -111,6 +111,11 @@ public function provideRoles() array('ROLE_ADMIN'), array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'), ), + array( + array('ROLE_ADMIN', 'ROLE_OPERATOR'), + array('ROLE_ADMIN', 'ROLE_OPERATOR'), + array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'), + ), ); } @@ -118,6 +123,7 @@ private function getRoleHierarchy() { return new RoleHierarchy(array( 'ROLE_ADMIN' => array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'), + 'ROLE_OPERATOR' => array('ROLE_USER'), )); } diff --git a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php index fafe49d5ff585..03998326e7866 100644 --- a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php @@ -72,7 +72,7 @@ public function showAction(Request $request, FlattenException $exception, DebugL 'logger' => $logger, 'currentContent' => $currentContent, ) - )); + ), 200, array('Content-Type' => $request->getMimeType($request->getRequestFormat()) ?: 'text/html')); } /** diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index ab9479e46166a..c829cfa4377fb 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -96,6 +96,7 @@ public function process(ContainerBuilder $container) $twigLoader->clearTag('twig.loader'); } else { $container->setAlias('twig.loader.filesystem', new Alias('twig.loader.native_filesystem', false)); + $container->removeDefinition('templating.engine.twig'); } if ($container->has('assets.packages')) { diff --git a/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php b/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php index 68a7b4560c95d..b92764d371bf4 100644 --- a/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php +++ b/src/Symfony/Bundle/TwigBundle/Loader/FilesystemLoader.php @@ -75,7 +75,6 @@ protected function findTemplate($template, $throw = true) } $file = null; - $previous = null; try { $file = parent::findTemplate($logicalName); } catch (LoaderError $e) { diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Controller/ExceptionControllerTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Controller/ExceptionControllerTest.php index cf5722727beb7..5d779c68c2a5e 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Controller/ExceptionControllerTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Controller/ExceptionControllerTest.php @@ -22,14 +22,9 @@ class ExceptionControllerTest extends TestCase { public function testShowActionCanBeForcedToShowErrorPage() { - $twig = new Environment( - new ArrayLoader(array( - '@Twig/Exception/error404.html.twig' => 'ok', - )) - ); + $twig = $this->createTwigEnv(array('@Twig/Exception/error404.html.twig' => 'not found')); - $request = Request::create('whatever', 'GET'); - $request->headers->set('X-Php-Ob-Level', 1); + $request = $this->createRequest('html'); $request->attributes->set('showException', false); $exception = FlattenException::create(new \Exception(), 404); $controller = new ExceptionController($twig, /* "showException" defaults to --> */ true); @@ -37,25 +32,61 @@ public function testShowActionCanBeForcedToShowErrorPage() $response = $controller->showAction($request, $exception, null); $this->assertEquals(200, $response->getStatusCode()); // successful request - $this->assertEquals('ok', $response->getContent()); // content of the error404.html template + $this->assertEquals('not found', $response->getContent()); } public function testFallbackToHtmlIfNoTemplateForRequestedFormat() { - $twig = new Environment( - new ArrayLoader(array( - '@Twig/Exception/error.html.twig' => 'html', - )) - ); + $twig = $this->createTwigEnv(array('@Twig/Exception/error.html.twig' => '')); - $request = Request::create('whatever'); - $request->headers->set('X-Php-Ob-Level', 1); - $request->setRequestFormat('txt'); + $request = $this->createRequest('txt'); $exception = FlattenException::create(new \Exception()); $controller = new ExceptionController($twig, false); - $response = $controller->showAction($request, $exception); + $controller->showAction($request, $exception); + + $this->assertEquals('html', $request->getRequestFormat()); + } + + public function testFallbackToHtmlWithFullExceptionIfNoTemplateForRequestedFormatAndExceptionsShouldBeShown() + { + $twig = $this->createTwigEnv(array('@Twig/Exception/exception_full.html.twig' => '')); + + $request = $this->createRequest('txt'); + $request->attributes->set('showException', true); + $exception = FlattenException::create(new \Exception()); + $controller = new ExceptionController($twig, false); + + $controller->showAction($request, $exception); $this->assertEquals('html', $request->getRequestFormat()); } + + public function testResponseHasRequestedMimeType() + { + $twig = $this->createTwigEnv(array('@Twig/Exception/error.json.twig' => '{}')); + + $request = $this->createRequest('json'); + $exception = FlattenException::create(new \Exception()); + $controller = new ExceptionController($twig, false); + + $response = $controller->showAction($request, $exception); + + $this->assertEquals('json', $request->getRequestFormat()); + $this->assertEquals($request->getMimeType('json'), $response->headers->get('Content-Type')); + } + + private function createRequest($requestFormat) + { + $request = Request::create('whatever'); + $request->headers->set('X-Php-Ob-Level', 1); + $request->setRequestFormat($requestFormat); + + return $request; + } + + private function createTwigEnv(array $templates) + { + return new Environment(new ArrayLoader($templates)); + } } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index fb82ee8266dbf..f5809cc045c42 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -170,9 +170,10 @@ public function testGlobalsWithDifferentTypesAndValues() $calls = $container->getDefinition('twig')->getMethodCalls(); foreach (array_slice($calls, 1) as $call) { - list($name, $value) = each($globals); - $this->assertEquals($name, $call[1][0]); - $this->assertSame($value, $call[1][1]); + $this->assertEquals(key($globals), $call[1][0]); + $this->assertSame(current($globals), $call[1][1]); + + next($globals); } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig index 22a11b03a4361..c148954f5a9c8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -350,6 +350,15 @@ 100% { background: #222; } } +.sf-toolbar-block.sf-toolbar-block-dump { + position: static; +} + +.sf-toolbar-block.sf-toolbar-block-dump .sf-toolbar-info { + max-width: none; + right: 0; +} + .sf-toolbar-block-dump pre.sf-dump { background-color: #222; border-color: #777; @@ -488,3 +497,33 @@ display: none; } } + +/***** Error Toolbar *****/ +.sf-error-toolbar .sf-toolbarreset { + background: #222; + color: #f5f5f5; + font: 13px/36px Arial, sans-serif; + height: 36px; + padding: 0 15px; + text-align: left; +} + +.sf-error-toolbar .sf-toolbarreset svg { + height: auto; +} + +.sf-error-toolbar .sf-toolbarreset a { + color: #99cdd8; + margin-left: 5px; + text-decoration: underline; +} + +.sf-error-toolbar .sf-toolbarreset a:hover { + text-decoration: none; +} + +.sf-error-toolbar .sf-toolbarreset .sf-toolbar-icon { + float: left; + padding: 5px 0; + margin-right: 10px; +} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig index a82a59ecca54f..2d3ab92e23d76 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar_js.html.twig @@ -61,7 +61,14 @@ }, function(xhr) { if (xhr.status !== 0) { - confirm('An error occurred while loading the web debug toolbar (' + xhr.status + ': ' + xhr.statusText + ').\n\nDo you want to open the profiler?') && (window.location = '{{ path("_profiler", { "token": token }) }}'); + var sfwdt = document.getElementById('sfwdt{{ token }}'); + sfwdt.innerHTML = '\ +
\ +
\ + An error occurred while loading the web debug toolbar. Open the web profiler.\ +
\ + '; + sfwdt.setAttribute('class', 'sf-toolbar sf-error-toolbar'); } }, {'maxTries': 5} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Router/panel.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Router/panel.html.twig index 16487d3fcbd36..1cfa085089685 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Router/panel.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Router/panel.html.twig @@ -54,7 +54,7 @@ {% for trace in traces %} - {{ loop.index }} + {{ loop.index }} {{ trace.name }} {{ trace.path }} diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index 278a767bf0216..9b9e31829694e 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -468,6 +468,7 @@ public function followRedirect() if (-1 !== $this->maxRedirects) { if ($this->redirectCount > $this->maxRedirects) { + $this->redirectCount = 0; throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects)); } } diff --git a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php index 87e38fdb89071..942fdc96226ac 100644 --- a/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/JsonDescriptor.php @@ -103,7 +103,7 @@ private function getInputArgumentData(InputArgument $argument) 'is_required' => $argument->isRequired(), 'is_array' => $argument->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), - 'default' => $argument->getDefault(), + 'default' => INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), ); } @@ -121,7 +121,7 @@ private function getInputOptionData(InputOption $option) 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), - 'default' => $option->getDefault(), + 'default' => INF === $option->getDefault() ? 'INF' : $option->getDefault(), ); } diff --git a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php index c0dd4830c3d2d..5b82b3deefa8e 100644 --- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php @@ -237,6 +237,10 @@ private function writeText($content, array $options = array()) */ private function formatDefaultValue($default) { + if (INF === $default) { + return 'INF'; + } + if (is_string($default)) { $default = OutputFormatter::escape($default); } elseif (is_array($default)) { diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index 8998f7b0f3c21..a1e770ab2b2a3 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -111,8 +111,7 @@ public function getName() * * @return bool|mixed|null|string * - * @throws \Exception - * @throws \RuntimeException + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden */ public function doAsk(OutputInterface $output, Question $question) { @@ -126,7 +125,7 @@ public function doAsk(OutputInterface $output, Question $question) if ($question->isHidden()) { try { $ret = trim($this->getHiddenResponse($output, $inputStream)); - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { if (!$question->isHiddenFallback()) { throw $e; } diff --git a/src/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php b/src/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php index 8f825ecb68395..b4f34ada19ec6 100644 --- a/src/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php @@ -32,6 +32,7 @@ public static function getInputArguments() 'input_argument_3' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', 'default_value'), 'input_argument_4' => new InputArgument('argument_name', InputArgument::REQUIRED, "multiline\nargument description"), 'input_argument_with_style' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', 'style'), + 'input_argument_with_default_inf_value' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', INF), ); } @@ -46,6 +47,7 @@ public static function getInputOptions() 'input_option_6' => new InputOption('option_name', array('o', 'O'), InputOption::VALUE_REQUIRED, 'option with multiple shortcuts'), 'input_option_with_style' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description', 'style'), 'input_option_with_style_array' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'option description', array('Hello', 'world')), + 'input_option_with_default_inf_value' => new InputOption('option_name', 'o', InputOption::VALUE_OPTIONAL, 'option description', INF), ); } diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.json b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.json new file mode 100644 index 0000000000000..b61ecf7b8c23c --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.json @@ -0,0 +1,7 @@ +{ + "name": "argument_name", + "is_required": false, + "is_array": false, + "description": "argument description", + "default": "INF" +} diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.md b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.md new file mode 100644 index 0000000000000..293d816201271 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.md @@ -0,0 +1,7 @@ +**argument_name:** + +* Name: argument_name +* Is required: no +* Is array: no +* Description: argument description +* Default: `INF` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.txt b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.txt new file mode 100644 index 0000000000000..c32d768c6f328 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.txt @@ -0,0 +1 @@ + argument_name argument description [default: INF] diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.xml b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.xml new file mode 100644 index 0000000000000..d457260070be0 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_with_default_inf_value.xml @@ -0,0 +1,7 @@ + + + argument description + + INF + + diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.json b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.json new file mode 100644 index 0000000000000..7c96ad30405c6 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.json @@ -0,0 +1,9 @@ +{ + "name": "--option_name", + "shortcut": "-o", + "accept_value": true, + "is_value_required": false, + "is_multiple": false, + "description": "option description", + "default": "INF" +} diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.md b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.md new file mode 100644 index 0000000000000..b57bd04bf9993 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: no +* Is multiple: no +* Description: option description +* Default: `INF` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.txt b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.txt new file mode 100644 index 0000000000000..d467dcf42327b --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.txt @@ -0,0 +1 @@ + -o, --option_name[=OPTION_NAME] option description [default: INF] diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.xml b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.xml new file mode 100644 index 0000000000000..5d1d21753e9dc --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_with_default_inf_value.xml @@ -0,0 +1,7 @@ + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 21977487a364f..732ce97a17dcc 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -120,7 +120,7 @@ public function testGet() $this->assertEquals('Circular reference detected for service "baz", path: "baz".', $e->getMessage(), '->get() throws a LogicException if the service has a circular reference to itself'); } - $this->assertTrue($builder->get('bar') === $builder->get('bar'), '->get() always returns the same instance if the service is shared'); + $this->assertSame($builder->get('bar'), $builder->get('bar'), '->get() always returns the same instance if the service is shared'); } public function testNonSharedServicesReturnsDifferentInstances() diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index ddf10468564b5..cfa6540399dbe 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -276,6 +276,13 @@ public function rename($origin, $target, $overwrite = false) } if (true !== @rename($origin, $target)) { + if (is_dir($origin)) { + // See https://bugs.php.net/bug.php?id=54097 & http://php.net/manual/en/function.rename.php#113943 + $this->mirror($origin, $target, null, array('override' => $overwrite, 'delete' => $overwrite)); + $this->remove($origin); + + return; + } throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target); } } diff --git a/src/Symfony/Component/Filesystem/Tests/LockHandlerTest.php b/src/Symfony/Component/Filesystem/Tests/LockHandlerTest.php index 0791cebc694b8..9ed871ea5a2a3 100644 --- a/src/Symfony/Component/Filesystem/Tests/LockHandlerTest.php +++ b/src/Symfony/Component/Filesystem/Tests/LockHandlerTest.php @@ -49,7 +49,7 @@ public function testErrorHandlingInLockIfLockPathBecomesUnwritable() $this->markTestSkipped('This test cannot run on Windows.'); } - $lockPath = sys_get_temp_dir().'/'.uniqid(); + $lockPath = sys_get_temp_dir().'/'.uniqid('', true); $e = null; $wrongMessage = null; diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index e08c9e51276be..fd24ccbeb11da 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -89,7 +89,7 @@ public function createView(ChoiceListInterface $list, $preferredChoices = null, if (!is_callable($preferredChoices) && !empty($preferredChoices)) { $preferredChoices = function ($choice) use ($preferredChoices) { - return false !== array_search($choice, $preferredChoices, true); + return in_array($choice, $preferredChoices, true); }; } diff --git a/src/Symfony/Component/Form/FormBuilderInterface.php b/src/Symfony/Component/Form/FormBuilderInterface.php index 1145ab26420c0..c19cc903beeba 100644 --- a/src/Symfony/Component/Form/FormBuilderInterface.php +++ b/src/Symfony/Component/Form/FormBuilderInterface.php @@ -27,7 +27,7 @@ interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuild * @param string|FormTypeInterface $type * @param array $options * - * @return $this + * @return self */ public function add($child, $type = null, array $options = array()); @@ -58,7 +58,7 @@ public function get($name); * * @param string $name * - * @return $this + * @return self */ public function remove($name); diff --git a/src/Symfony/Component/Form/FormRenderer.php b/src/Symfony/Component/Form/FormRenderer.php index 15a8d08430cfe..9d1c075bb5fa7 100644 --- a/src/Symfony/Component/Form/FormRenderer.php +++ b/src/Symfony/Component/Form/FormRenderer.php @@ -316,6 +316,6 @@ public function searchAndRenderBlock(FormView $view, $blockNameSuffix, array $va */ public function humanize($text) { - return ucfirst(trim(strtolower(preg_replace(array('/([A-Z])/', '/[_\s]+/'), array('_$1', ' '), $text)))); + return ucfirst(strtolower(trim(preg_replace(array('/([A-Z])/', '/[_\s]+/'), array('_$1', ' '), $text)))); } } diff --git a/src/Symfony/Component/Form/FormView.php b/src/Symfony/Component/Form/FormView.php index c1da5f8fc9bb1..8655bedf6e135 100644 --- a/src/Symfony/Component/Form/FormView.php +++ b/src/Symfony/Component/Form/FormView.php @@ -53,6 +53,8 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable */ private $rendered = false; + private $methodRendered = false; + public function __construct(FormView $parent = null) { $this->parent = $parent; @@ -90,6 +92,19 @@ public function setRendered() return $this; } + /** + * @return bool + */ + public function isMethodRendered() + { + return $this->methodRendered; + } + + public function setMethodRendered() + { + $this->methodRendered = true; + } + /** * Returns a child by name (implements \ArrayAccess). * diff --git a/src/Symfony/Component/HttpFoundation/IpUtils.php b/src/Symfony/Component/HttpFoundation/IpUtils.php index 28093be43403f..eba603b15df01 100644 --- a/src/Symfony/Component/HttpFoundation/IpUtils.php +++ b/src/Symfony/Component/HttpFoundation/IpUtils.php @@ -18,6 +18,8 @@ */ class IpUtils { + private static $checkedIps = array(); + /** * This class should not be instantiated. */ @@ -61,26 +63,31 @@ public static function checkIp($requestIp, $ips) */ public static function checkIp4($requestIp, $ip) { + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { - return false; + return self::$checkedIps[$cacheKey] = false; } if (false !== strpos($ip, '/')) { list($address, $netmask) = explode('/', $ip, 2); if ($netmask === '0') { - return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + return self::$checkedIps[$cacheKey] = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); } if ($netmask < 0 || $netmask > 32) { - return false; + return self::$checkedIps[$cacheKey] = false; } } else { $address = $ip; $netmask = 32; } - return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); } /** @@ -100,6 +107,11 @@ public static function checkIp4($requestIp, $ip) */ public static function checkIp6($requestIp, $ip) { + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) { throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); } @@ -108,7 +120,7 @@ public static function checkIp6($requestIp, $ip) list($address, $netmask) = explode('/', $ip, 2); if ($netmask < 1 || $netmask > 128) { - return false; + return self::$checkedIps[$cacheKey] = false; } } else { $address = $ip; @@ -119,7 +131,7 @@ public static function checkIp6($requestIp, $ip) $bytesTest = unpack('n*', @inet_pton($requestIp)); if (!$bytesAddr || !$bytesTest) { - return false; + return self::$checkedIps[$cacheKey] = false; } for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { @@ -127,10 +139,10 @@ public static function checkIp6($requestIp, $ip) $left = ($left <= 16) ? $left : 16; $mask = ~(0xffff >> $left) & 0xffff; if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { - return false; + return self::$checkedIps[$cacheKey] = false; } } - return true; + return self::$checkedIps[$cacheKey] = true; } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index b8acebe6d44c6..481d02f27cf41 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -92,6 +92,10 @@ class NativeSessionStorage implements SessionStorageInterface * upload_progress.freq, "1%" * upload_progress.min-freq, "1" * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset=" + * sid_length, "32" + * sid_bits_per_character, "5" + * trans_sid_hosts, $_SERVER['HTTP_HOST'] + * trans_sid_tags, "a=href,area=href,frame=src,form=" * * @param array $options Session configuration options * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler @@ -336,6 +340,7 @@ public function setOptions(array $options) 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', 'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags', + 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', )); foreach ($options as $key => $value) { diff --git a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php index 39a99e6966c22..027b2b1761334 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/ResponseCacheStrategy.php @@ -39,7 +39,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface */ public function add(Response $response) { - if ($response->isValidateable()) { + if (!$response->isFresh() || !$response->isCacheable()) { $this->cacheable = false; } else { $maxAge = $response->getMaxAge(); @@ -70,6 +70,9 @@ public function update(Response $response) if ($response->isValidateable()) { $response->setEtag(null); $response->setLastModified(null); + } + + if (!$response->isFresh()) { $this->cacheable = false; } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 97e83f8d59c80..48cb62a602cbc 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -59,11 +59,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.8.22'; - const VERSION_ID = 20822; + const VERSION = '2.8.23'; + const VERSION_ID = 20823; const MAJOR_VERSION = 2; const MINOR_VERSION = 8; - const RELEASE_VERSION = 22; + const RELEASE_VERSION = 23; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '11/2018'; diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php index f30a3b6ad94ab..5e4c322223eb3 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/ResponseCacheStrategyTest.php @@ -75,4 +75,148 @@ public function testSharedMaxAgeNotSetIfNotSetInMasterRequest() $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage')); } + + public function testMasterResponseNotCacheableWhenEmbeddedResponseRequiresValidation() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $embeddedResponse = new Response(); + $embeddedResponse->setLastModified(new \DateTime()); + $cacheStrategy->add($embeddedResponse); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($masterResponse->isFresh()); + } + + public function testValidationOnMasterResponseIsNotPossibleWhenItContainsEmbeddedResponses() + { + $cacheStrategy = new ResponseCacheStrategy(); + + // This master response uses the "validation" model + $masterResponse = new Response(); + $masterResponse->setLastModified(new \DateTime()); + $masterResponse->setEtag('foo'); + + // Embedded response uses "expiry" model + $embeddedResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $cacheStrategy->add($embeddedResponse); + + $cacheStrategy->update($masterResponse); + + $this->assertFalse($masterResponse->isValidateable()); + $this->assertFalse($masterResponse->headers->has('Last-Modified')); + $this->assertFalse($masterResponse->headers->has('ETag')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); + } + + public function testMasterResponseWithValidationIsUnchangedWhenThereIsNoEmbeddedResponse() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setLastModified(new \DateTime()); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->isValidateable()); + } + + public function testMasterResponseWithExpirationIsUnchangedWhenThereIsNoEmbeddedResponse() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->isFresh()); + } + + public function testMasterResponseIsNotCacheableWhenEmbeddedResponseIsNotCacheable() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); // Public, cacheable + + /* This response has no validation or expiration information. + That makes it uncacheable, it is always stale. + (It does *not* make this private, though.) */ + $embeddedResponse = new Response(); + $this->assertFalse($embeddedResponse->isFresh()); // not fresh, as no lifetime is provided + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($masterResponse->isFresh()); + } + + public function testEmbeddingPrivateResponseMakesMainResponsePrivate() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); // public, cacheable + + // The embedded response might for example contain per-user data that remains valid for 60 seconds + $embeddedResponse = new Response(); + $embeddedResponse->setPrivate(); + $embeddedResponse->setMaxAge(60); // this would implicitly set "private" as well, but let's be explicit + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertTrue($masterResponse->headers->hasCacheControlDirective('private')); + // Not sure if we should pass "max-age: 60" in this case, as long as the response is private and + // that's the more conservative of both the master and embedded response...? + } + + public function testResponseIsExiprableWhenEmbeddedResponseCombinesExpiryAndValidation() + { + /* When "expiration wins over validation" (https://symfony.com/doc/current/http_cache/validation.html) + * and both the main and embedded response provide s-maxage, then the more restricting value of both + * should be fine, regardless of whether the embedded response can be validated later on or must be + * completely regenerated. + */ + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + + $embeddedResponse = new Response(); + $embeddedResponse->setSharedMaxAge(60); + $embeddedResponse->setEtag('foo'); + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage')); + } + + public function testResponseIsExpirableButNotValidateableWhenMasterResponseCombinesExpirationAndValidation() + { + $cacheStrategy = new ResponseCacheStrategy(); + + $masterResponse = new Response(); + $masterResponse->setSharedMaxAge(3600); + $masterResponse->setEtag('foo'); + $masterResponse->setLastModified(new \DateTime()); + + $embeddedResponse = new Response(); + $embeddedResponse->setSharedMaxAge(60); + + $cacheStrategy->add($embeddedResponse); + $cacheStrategy->update($masterResponse); + + $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage')); + $this->assertFalse($masterResponse->isValidateable()); + } } diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php index a1112b92a7d05..4cb3b03f898a5 100644 --- a/src/Symfony/Component/Process/Tests/ProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -439,6 +439,9 @@ public function testExitCodeCommandFailed() $this->assertGreaterThan(0, $process->getExitCode()); } + /** + * @group tty + */ public function testTTYCommand() { if ('\\' === DIRECTORY_SEPARATOR) { @@ -454,6 +457,9 @@ public function testTTYCommand() $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); } + /** + * @group tty + */ public function testTTYCommandExitCode() { if ('\\' === DIRECTORY_SEPARATOR) { diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index cdb075c84621f..a6615f11e7a5e 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -245,7 +245,7 @@ public static function handleError($type, $message, $file, $line, $context) private static function throwInvalidArgumentException($message, $trace, $i) { - if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file']) { + if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file'] && isset($trace[$i]['args'][0])) { $pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface ')); $pos += strlen($delim); $type = $trace[$i]['args'][0]; diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ReturnTyped.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ReturnTyped.php new file mode 100644 index 0000000000000..b6a9852715d79 --- /dev/null +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/ReturnTyped.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\PropertyAccess\Tests\Fixtures; + +/** + * @author Kévin Dunglas + */ +class ReturnTyped +{ + public function getFoos(): array + { + return 'It doesn\'t respect the return type on purpose'; + } + + public function addFoo(\DateTime $dateTime) + { + } + + public function removeFoo(\DateTime $dateTime) + { + } +} diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index fc434adb45a0e..bff2c728f1ca2 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall; use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet; @@ -566,4 +567,16 @@ public function testThrowTypeErrorWithInterface() $this->propertyAccessor->setValue($object, 'countable', 'This is a string, \Countable expected.'); } + + /** + * @requires PHP 7 + * + * @expectedException \TypeError + */ + public function testDoNotDiscardReturnTypeError() + { + $object = new ReturnTyped(); + + $this->propertyAccessor->setValue($object, 'foos', array(new \DateTime())); + } } diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index 537a81e9a44b1..cdf1973df4158 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -250,7 +250,7 @@ private function parseConfigs(\DOMElement $node, $path) $condition = trim($n->textContent); break; default: - throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement" or "option".', $n->localName, $path)); + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement", "option" or "condition".', $n->localName, $path)); } } diff --git a/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php b/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php index 463bc0d056809..900c59fa37392 100644 --- a/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php @@ -49,7 +49,7 @@ public function match($pathinfo) protected function handleRouteRequirements($pathinfo, $name, Route $route) { // expression condition - if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) { + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { return array(self::REQUIREMENT_MISMATCH, null); } diff --git a/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php b/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php index cb1a35f4d3023..9085be0424886 100644 --- a/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php @@ -105,7 +105,7 @@ protected function matchCollection($pathinfo, RouteCollection $routes) // check condition if ($condition = $route->getCondition()) { - if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request))) { + if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route); continue; diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php index 9786a9b42cc60..c646723b3ab08 100644 --- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php @@ -207,7 +207,7 @@ protected function getAttributes(Route $route, $name, array $attributes) protected function handleRouteRequirements($pathinfo, $name, Route $route) { // expression condition - if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request))) { + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { return array(self::REQUIREMENT_MISMATCH, null); } @@ -248,4 +248,19 @@ protected function getExpressionLanguage() return $this->expressionLanguage; } + + /** + * @internal + */ + protected function createRequest($pathinfo) + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + return null; + } + + return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), array(), array(), array( + 'SCRIPT_FILENAME' => $this->context->getBaseUrl(), + 'SCRIPT_NAME' => $this->context->getBaseUrl(), + )); + } } diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 6d59855d2d8f3..9fd53e9814496 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -337,6 +337,16 @@ public function testCondition() $matcher->match('/foo'); } + public function testRequestCondition() + { + $coll = new RouteCollection(); + $route = new Route('/foo/{bar}'); + $route->setCondition('request.getBaseUrl() == "/sub/front.php" and request.getPathInfo() == "/foo/bar"'); + $coll->add('foo', $route); + $matcher = new UrlMatcher($coll, new RequestContext('/sub/front.php')); + $this->assertEquals(array('bar' => 'bar', '_route' => 'foo'), $matcher->match('/foo/bar')); + } + public function testDecodeOnce() { $coll = new RouteCollection(); diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php index 71d19adc14f2b..7bfc556bdbf25 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php @@ -27,7 +27,7 @@ class UsernamePasswordToken extends AbstractToken * Constructor. * * @param string|object $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method - * @param string $credentials This usually is the password of the user + * @param mixed $credentials This usually is the password of the user * @param string $providerKey The provider key * @param (RoleInterface|string)[] $roles An array of roles * diff --git a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php index 2dc7fee49992e..5f4c146cab469 100644 --- a/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php +++ b/src/Symfony/Component/Security/Core/Validator/Constraints/UserPasswordValidator.php @@ -39,6 +39,10 @@ public function validate($password, Constraint $constraint) throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UserPassword'); } + if (null === $password || '' === $password) { + return; + } + $user = $this->tokenStorage->getToken()->getUser(); if (!$user instanceof UserInterface) { diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 7de83d2513369..606392de8a16c 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -158,7 +158,7 @@ private function attemptSwitchUser(Request $request) */ private function attemptExitUser(Request $request) { - if (false === $original = $this->getOriginalToken($this->tokenStorage->getToken())) { + if (null === ($currentToken = $this->tokenStorage->getToken()) || false === $original = $this->getOriginalToken($currentToken)) { throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php index 43013520c36ba..6b6cb246c985f 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php @@ -65,6 +65,17 @@ public function testEventIsIgnoredIfUsernameIsNotPassedWithTheRequest() $this->assertNull($this->tokenStorage->getToken()); } + /** + * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException + */ + public function testExitUserThrowsAuthenticationExceptionIfNoCurrentToken() + { + $this->tokenStorage->setToken(null); + $this->request->query->set('_switch_user', '_exit'); + $listener = new SwitchUserListener($this->tokenStorage, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager); + $listener->handle($this->event); + } + /** * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException */ diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 63bfb871e819d..5149d0dbc7d97 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -228,7 +228,7 @@ protected function handleCircularReference($object) return call_user_func($this->circularReferenceHandler, $object); } - throw new CircularReferenceException(sprintf('A circular reference has been detected (configured limit: %d).', $this->circularReferenceLimit)); + throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', get_class($object), $this->circularReferenceLimit)); } /** diff --git a/src/Symfony/Component/Translation/DataCollectorTranslator.php b/src/Symfony/Component/Translation/DataCollectorTranslator.php index 50e4a2ff93805..0429595a57847 100644 --- a/src/Symfony/Component/Translation/DataCollectorTranslator.php +++ b/src/Symfony/Component/Translation/DataCollectorTranslator.php @@ -95,7 +95,7 @@ public function getCatalogue($locale = null) */ public function getFallbackLocales() { - if ($this->translator instanceof Translator) { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { return $this->translator->getFallbackLocales(); } diff --git a/src/Symfony/Component/Translation/Dumper/IcuResFileDumper.php b/src/Symfony/Component/Translation/Dumper/IcuResFileDumper.php index 049b98c7a73d3..0535fda201046 100644 --- a/src/Symfony/Component/Translation/Dumper/IcuResFileDumper.php +++ b/src/Symfony/Component/Translation/Dumper/IcuResFileDumper.php @@ -62,7 +62,7 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti $resOffset = $this->getPosition($data); - $data .= pack('v', count($messages)) + $data .= pack('v', count($messages->all($domain))) .$indexes .$this->writePadding($data) .$resources @@ -73,11 +73,11 @@ public function formatCatalogue(MessageCatalogue $messages, $domain, array $opti $root = pack('V7', $resOffset + (2 << 28), // Resource Offset + Resource Type 6, // Index length - $keyTop, // Index keys top - $bundleTop, // Index resources top - $bundleTop, // Index bundle top - count($messages), // Index max table length - 0 // Index attributes + $keyTop, // Index keys top + $bundleTop, // Index resources top + $bundleTop, // Index bundle top + count($messages->all($domain)), // Index max table length + 0 // Index attributes ); $header = pack('vC2v4C12@32', diff --git a/src/Symfony/Component/Translation/Loader/FileLoader.php b/src/Symfony/Component/Translation/Loader/FileLoader.php index a7f24f41a6535..1885963a99550 100644 --- a/src/Symfony/Component/Translation/Loader/FileLoader.php +++ b/src/Symfony/Component/Translation/Loader/FileLoader.php @@ -54,7 +54,7 @@ public function load($resource, $locale, $domain = 'messages') return $catalogue; } - /* + /** * @param string $resource * * @return array diff --git a/src/Symfony/Component/Translation/LoggingTranslator.php b/src/Symfony/Component/Translation/LoggingTranslator.php index b259df5e0aa49..85ab3c47f55c8 100644 --- a/src/Symfony/Component/Translation/LoggingTranslator.php +++ b/src/Symfony/Component/Translation/LoggingTranslator.php @@ -95,7 +95,7 @@ public function getCatalogue($locale = null) */ public function getFallbackLocales() { - if ($this->translator instanceof Translator) { + if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) { return $this->translator->getFallbackLocales(); } diff --git a/src/Symfony/Component/Validator/Constraints/AbstractComparison.php b/src/Symfony/Component/Validator/Constraints/AbstractComparison.php index 78db943f74f14..e20a8f3fb7b05 100644 --- a/src/Symfony/Component/Validator/Constraints/AbstractComparison.php +++ b/src/Symfony/Component/Validator/Constraints/AbstractComparison.php @@ -30,6 +30,10 @@ abstract class AbstractComparison extends Constraint */ public function __construct($options = null) { + if (null === $options) { + $options = array(); + } + if (is_array($options) && !isset($options['value'])) { throw new ConstraintDefinitionException(sprintf( 'The %s constraint requires the "value" option to be set.', diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php index c84f46c449f06..ded371b04aa1a 100644 --- a/src/Symfony/Component/Validator/Constraints/UrlValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UrlValidator.php @@ -48,7 +48,7 @@ public function validate($value, Constraint $constraint) throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Url'); } - if (null === $value) { + if (null === $value || '' === $value) { return; } diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf index 72d4d92973fdc..30a883bbf239d 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf @@ -76,7 +76,7 @@ This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less. - Cette chaine est trop longue. Elle doit avoir au maximum {{ limit }} caractère.|Cette chaine est trop longue. Elle doit avoir au maximum {{ limit }} caractères. + Cette chaîne est trop longue. Elle doit avoir au maximum {{ limit }} caractère.|Cette chaîne est trop longue. Elle doit avoir au maximum {{ limit }} caractères. This value should be {{ limit }} or more. @@ -84,7 +84,7 @@ This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more. - Cette chaine est trop courte. Elle doit avoir au minimum {{ limit }} caractère.|Cette chaine est trop courte. Elle doit avoir au minimum {{ limit }} caractères. + Cette chaîne est trop courte. Elle doit avoir au minimum {{ limit }} caractère.|Cette chaîne est trop courte. Elle doit avoir au minimum {{ limit }} caractères. This value should not be blank. @@ -180,7 +180,7 @@ This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. - Cette chaine doit avoir exactement {{ limit }} caractère.|Cette chaine doit avoir exactement {{ limit }} caractères. + Cette chaîne doit avoir exactement {{ limit }} caractère.|Cette chaîne doit avoir exactement {{ limit }} caractères. The file was only partially uploaded. diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php index fe0e25e6e3aea..2c1bb5bbcc1fd 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php @@ -68,14 +68,21 @@ protected static function addPhp5Dot5Comparisons(array $comparisons) return $result; } + public function provideInvalidConstraintOptions() + { + return array( + array(null), + array(array()), + ); + } + /** + * @dataProvider provideInvalidConstraintOptions * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException */ - public function testThrowsConstraintExceptionIfNoValueOrProperty() + public function testThrowsConstraintExceptionIfNoValueOrProperty($options) { - $comparison = $this->createConstraint(array()); - - $this->validator->validate('some value', $comparison); + $this->createConstraint($options); } /** @@ -170,11 +177,11 @@ public function provideAllInvalidComparisons() abstract public function provideInvalidComparisons(); /** - * @param array $options Options for the constraint + * @param array|null $options Options for the constraint * * @return Constraint */ - abstract protected function createConstraint(array $options); + abstract protected function createConstraint(array $options = null); /** * @return string|null diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php index 47f1e483fb4b2..32c81c3ae78d5 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php @@ -30,7 +30,7 @@ protected function createValidator() return new EqualToValidator(); } - protected function createConstraint(array $options) + protected function createConstraint(array $options = null) { return new EqualTo($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php index 41899b2ef10da..2a8bcb3b5f13a 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php @@ -30,7 +30,7 @@ protected function createValidator() return new GreaterThanOrEqualValidator(); } - protected function createConstraint(array $options) + protected function createConstraint(array $options = null) { return new GreaterThanOrEqual($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php index 080928c2e6d27..8e763fbe30911 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php @@ -30,7 +30,7 @@ protected function createValidator() return new GreaterThanValidator(); } - protected function createConstraint(array $options) + protected function createConstraint(array $options = null) { return new GreaterThan($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php index a83af3d5c6def..4999919a31a5a 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php @@ -30,7 +30,7 @@ protected function createValidator() return new IdenticalToValidator(); } - protected function createConstraint(array $options) + protected function createConstraint(array $options = null) { return new IdenticalTo($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php index 8b1adef1806c1..84f150bf133e9 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php @@ -30,7 +30,7 @@ protected function createValidator() return new LessThanOrEqualValidator(); } - protected function createConstraint(array $options) + protected function createConstraint(array $options = null) { return new LessThanOrEqual($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php index c3dea687be826..da2885fba3a5c 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LessThanValidatorTest.php @@ -30,7 +30,7 @@ protected function createValidator() return new LessThanValidator(); } - protected function createConstraint(array $options) + protected function createConstraint(array $options = null) { return new LessThan($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php index 1ee8a54ac35fe..a797762bd3230 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotEqualToValidatorTest.php @@ -30,7 +30,7 @@ protected function createValidator() return new NotEqualToValidator(); } - protected function createConstraint(array $options) + protected function createConstraint(array $options = null) { return new NotEqualTo($options); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php index 3810a847f4f01..16f518f179149 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/NotIdenticalToValidatorTest.php @@ -30,7 +30,7 @@ protected function createValidator() return new NotIdenticalToValidator(); } - protected function createConstraint(array $options) + protected function createConstraint(array $options = null) { return new NotIdenticalTo($options); } diff --git a/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php index 903641f69c636..d70c09ac4da62 100644 --- a/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php @@ -45,7 +45,7 @@ public static function castStream($stream, array $a, Stub $stub, $isNested) public static function castStreamContext($stream, array $a, Stub $stub, $isNested) { - return stream_context_get_params($stream); + return @stream_context_get_params($stream) ?: $a; } public static function castGd($gd, array $a, Stub $stub, $isNested) diff --git a/src/Symfony/Component/VarDumper/Caster/SplCaster.php b/src/Symfony/Component/VarDumper/Caster/SplCaster.php index e0409788e1ee1..75f01ca7cfa12 100644 --- a/src/Symfony/Component/VarDumper/Caster/SplCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/SplCaster.php @@ -180,7 +180,7 @@ public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $s $storage = array(); unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 - foreach ($c as $obj) { + foreach (clone $c as $obj) { $storage[spl_object_hash($obj)] = array( 'object' => $obj, 'info' => $c->getInfo(), diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php index f444741580af8..1e29573f3d37e 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -123,9 +123,9 @@ public function dumpScalar(Cursor $cursor, $type, $value) $style = 'num'; switch (true) { - case INF === $value: $value = 'INF'; break; + case INF === $value: $value = 'INF'; break; case -INF === $value: $value = '-INF'; break; - case is_nan($value): $value = 'NAN'; break; + case is_nan($value): $value = 'NAN'; break; default: $value = (string) $value; if (false === strpos($value, $this->decimalPoint)) {