diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index c93959532d398..cf90bc2e50f34 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,36 @@ 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.30 (2017-07-03) + + * 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 #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 #23086 [FrameworkBundle] Fix perf issue in CacheClearCommand::warmup() (nicolas-grekas) + * bug #23098 Cache ipCheck (2.7) (gonzalovilaseca) + * 2.7.29 (2017-06-07) * bug #23069 [SecurityBundle] Show unique Inherited roles in profile panel (yceruto) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 532b8a261c4f2..0b695308c528f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -20,11 +20,11 @@ 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) + - Maxime Steinhausser (ogizanagi) + - Robin Chalas (chalas_r) - Romain Neutron (romain) - Pascal Borreli (pborreli) - Wouter De Jong (wouterj) - - Robin Chalas (chalas_r) - - Maxime Steinhausser (ogizanagi) - Grégoire Pineau (lyrixx) - Joseph Bielawski (stloyd) - Karma Dordrak (drak) @@ -38,8 +38,8 @@ Symfony is the result of the work of many people who made the code better - Jules Pietri (heah) - Roland Franssen (ro0) - Sarah Khalil (saro0h) - - Jonathan Wage (jwage) - Guilhem Niot (energetick) + - Jonathan Wage (jwage) - Diego Saint Esteben (dosten) - Alexandre Salomé (alexandresalome) - William Durand (couac) @@ -48,24 +48,24 @@ Symfony is the result of the work of many people who made the code better - stealth35 ‏ (stealth35) - Alexander Mols (asm89) - Bulat Shakirzyanov (avalanche123) - - Peter Rehm (rpet) - Iltar van der Berg (kjarli) + - Peter Rehm (rpet) - Saša Stamenković (umpirsky) - Henrik Bjørnskov (henrikbjorn) - Miha Vrhovnik - Diego Saint Esteben (dii3g0) - Konstantin Kudryashov (everzet) + - Matthias Pigulla (mpdude) - Bilal Amarni (bamarni) - Florin Patan (florinpatan) - - Matthias Pigulla (mpdude) + - Gábor Egyed (1ed) - Kevin Bond (kbond) - Andrej Hudec (pulzarraider) - - Gábor Egyed (1ed) + - Pierre du Plessis (pierredup) - Michel Weimerskirch (mweimerskirch) - Eric Clemmons (ericclemmons) - Charles Sarrazin (csarrazi) - Christian Raue - - Pierre du Plessis (pierredup) - Arnout Boks (aboks) - Deni - Henrik Westphal (snc) @@ -83,25 +83,27 @@ Symfony is the result of the work of many people who made the code better - Toni Uebernickel (havvg) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) + - Jérôme Tamarelle (gromnan) - John Wards (johnwards) - Dariusz Ruminski - Fran Moreno (franmomu) - Antoine Hérault (herzult) - - Jérôme Tamarelle (gromnan) - Paráda József (paradajozsef) - Arnaud Le Blanc (arnaud-lb) - Maxime STEINHAUSSER + - Alexander M. Turek (derrabus) - Michal Piotrowski (eventhorizon) - Issei Murasawa (issei_m) - Tim Nagel (merk) - Brice BERNARD (brikou) - - Alexander M. Turek (derrabus) - Baptiste Clavié (talus) + - Vladimir Reznichenko (kalessil) - marc.weistroff - lenar - Włodzimierz Gajda (gajdaw) - - Vladimir Reznichenko (kalessil) + - Yonel Ceruto González (yonelceruto) - Alexander Schwenn (xelaris) + - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) - Colin Frei - Adrien Brault (adrienbrault) @@ -109,10 +111,8 @@ Symfony is the result of the work of many people who made the code better - Peter Kokot (maastermedia) - David Buchmann (dbu) - excelwebzone - - Jacob Dreesen (jdreesen) - Tobias Nyholm (tobias) - Tomáš Votruba (tomas_votruba) - - Yonel Ceruto González (yonelceruto) - Fabien Pennequin (fabienpennequin) - Gordon Franke (gimler) - Eric GELOEN (gelo) @@ -124,13 +124,13 @@ Symfony is the result of the work of many people who made the code better - Sebastiaan Stok (sstok) - Stefano Sala (stefano.sala) - Evgeniy (ewgraf) + - Vincent AUBERT (vincent) - Juti Noppornpitak (shiroyuki) - Tigran Azatyan (tigranazatyan) - Sebastian Hörl (blogsh) - Daniel Gomes (danielcsgomes) - Hidenori Goto (hidenorigoto) - Guilherme Blanco (guilhermeblanco) - - Vincent AUBERT (vincent) - Pablo Godel (pgodel) - Jérémie Augustin (jaugustin) - Andréia Bohner (andreia) @@ -147,6 +147,7 @@ Symfony is the result of the work of many people who made the code better - Thomas Rabaix (rande) - Rouven Weßling (realityking) - Teoh Han Hui (teohhanhui) + - David Maicher (dmaicher) - Jérôme Vasseur (jvasseur) - Clemens Tolboom - Helmer Aaviksoo @@ -154,16 +155,16 @@ Symfony is the result of the work of many people who made the code better - Hiromi Hishida (77web) - Matthieu Ouellette-Vachon (maoueh) - Michał Pipa (michal.pipa) + - Dawid Nowak - Amal Raghav (kertz) - Jonathan Ingram (jonathaningram) - Artur Kotyrba - - David Maicher (dmaicher) - jeremyFreeAgent (Jérémy Romey) (jeremyfreeagent) - James Halsall (jaitsu) - Warnar Boekkooi (boekkooi) - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) - - Dawid Nowak + - Lars Strojny (lstrojny) - Possum - Dorian Villet (gnutix) - Richard Miller (mr_r_miller) @@ -175,12 +176,12 @@ Symfony is the result of the work of many people who made the code better - Chris Wilkinson (thewilkybarkid) - Andreas Hucks (meandmymonkey) - Noel Guilbert (noel) - - Lars Strojny (lstrojny) - Stepan Anchugov (kix) - bronze1man - Daniel Espendiller - sun (sun) - Larry Garfield (crell) + - Oleg Voronkovich - Martin Schuhfuß (usefulthink) - apetitpa - Matthieu Bontemps (mbontemps) @@ -242,7 +243,6 @@ 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 @@ -294,6 +294,7 @@ Symfony is the result of the work of many people who made the code better - Victor Bocharsky (bocharsky_bw) - Jan Decavele (jandc) - Gustavo Piltcher + - Nikolay Labinskiy (e-moe) - Stepan Tanasiychuk (stfalcon) - Tiago Ribeiro (fixe) - Hidde Boomsma (hboomsma) @@ -359,7 +360,6 @@ Symfony is the result of the work of many people who made the code better - Endre Fejes - Tobias Naumann (tna) - Daniel Beyer - - Nikolay Labinskiy (e-moe) - Shein Alexey - Romain Gautier (mykiwi) - Joe Lencioni @@ -609,6 +609,7 @@ Symfony is the result of the work of many people who made the code better - Kevin (oxfouzer) - Paweł Wacławczyk (pwc) - Oleg Zinchenko (cystbear) + - Baptiste Meyer (meyerbaptiste) - Johannes Klauss (cloppy) - Evan Villemez - fzerorubigd @@ -671,6 +672,7 @@ Symfony is the result of the work of many people who made the code better - Benoit Lévêque (benoit_leveque) - Jeroen Fiege (fieg) - Krzysiek Łabuś + - George Mponos (gmponos) - Xavier Lacot (xavier) - possum - Denis Zunke (donalberto) @@ -734,6 +736,7 @@ Symfony is the result of the work of many people who made the code better - Omar Yepez (oyepez003) - mwsaz - Jelle Kapitein + - Ben Scott - Benoît Bourgeois - mantulo - corphi @@ -815,6 +818,7 @@ Symfony is the result of the work of many people who made the code better - ttomor - Mei Gwilym (meigwilym) - Michael H. Arieli (excelwebzone) + - Tom Panier (neemzy) - Fred Cox - Luciano Mammino (loige) - fabios @@ -850,6 +854,7 @@ Symfony is the result of the work of many people who made the code better - Máximo Cuadros (mcuadros) - tamirvs - julien.galenski + - Israel J. Carberry - Bob van de Vijver - Christian Neff - Per Sandström (per) @@ -888,7 +893,6 @@ Symfony is the result of the work of many people who made the code better - Eddie Jaoude - Antanas Arvasevicius - Haritz Iturbe (hizai) - - Baptiste Meyer (meyerbaptiste) - Nerijus Arlauskas (nercury) - SPolischook - Diego Sapriza @@ -910,8 +914,10 @@ Symfony is the result of the work of many people who made the code better - Marcin Chwedziak - hjkl - Tony Cosentino (tony-co) + - Dan Wilga - Alexander Cheprasov - Rodrigo Díez Villamuera (rodrigodiez) + - Malte Blättermann - e-ivanov - Jochen Bayer (jocl) - Jeremy Bush @@ -923,12 +929,14 @@ Symfony is the result of the work of many people who made the code better - Péter Buri (burci) - Davide Borsatto (davide.borsatto) - kaiwa + - RJ Garcia - Charles Sanquer (csanquer) - Albert Ganiev (helios-ag) - Neil Katin - David Otton - Will Donohoe - peter + - Jaroslav Kuba - flip111 - Jérémy Jourdin (jjk801) - BRAMILLE Sébastien (oktapodia) @@ -936,6 +944,7 @@ Symfony is the result of the work of many people who made the code better - Gustavo Adrian - Yannick - spdionis + - rchoquet - Taras Girnyk - Eduardo García Sanz (coma) - James Gilliland @@ -965,6 +974,7 @@ Symfony is the result of the work of many people who made the code better - Paul Matthews - Juan Traverso - Tarjei Huse (tarjei) + - tsufeki - Philipp Strube - Christian Sciberras - Clement Herreman (clemherreman) @@ -1055,6 +1065,7 @@ Symfony is the result of the work of many people who made the code better - m.chwedziak - Philip Frank - Lance McNearney + - Gonzalo Vilaseca (gonzalovilaseca) - Giorgio Premi - Ian Carroll - caponica @@ -1065,7 +1076,6 @@ Symfony is the result of the work of many people who made the code better - adev - Luis Galeas - Martin Pärtel - - George Mponos (gmponos) - Patrick Daley (padrig) - Xavier Briand (xavierbriand) - Max Summe @@ -1146,6 +1156,7 @@ Symfony is the result of the work of many people who made the code better - Hoffmann András - Olivier - pscheit + - Wybren Koelmans - Zdeněk Drahoš - Dan Harper - moldcraft @@ -1233,6 +1244,7 @@ Symfony is the result of the work of many people who made the code better - Fabien LUCAS (flucas2) - Indra Gunawan (indragunawan) - Karim Cassam Chenaï (ka) + - Michal Kurzeja (mkurzeja) - Nicolas Bastien (nicolas_bastien) - Denis (yethee) - Andrew Zhilin (zhil) @@ -1251,9 +1263,11 @@ Symfony is the result of the work of many people who made the code better - Warwick - VJ - Chris + - Florent Olivaud - JakeFr - Simon Sargeant - efeen + - Nicolas Pion - Muhammed Akbulut - Michał Dąbrowski (defrag) - Simone Fumagalli (hpatoio) @@ -1270,6 +1284,7 @@ Symfony is the result of the work of many people who made the code better - Grinbergs Reinis (shima5) - Artem Lopata (bumz) - Nicole Cordes + - VolCh - Alexey Popkov - Gijs Kunze - Artyom Protaskin @@ -1375,6 +1390,7 @@ Symfony is the result of the work of many people who made the code better - Dane Powell - Gerrit Drost - Linnaea Von Lavia + - Javan Eskander - Lenar Lõhmus - Cristian Gonzalez - AlberT @@ -1423,7 +1439,6 @@ Symfony is the result of the work of many people who made the code better - Yanick Witschi - Ondrej Mirtes - akimsko - - Ben Scott - Youpie - srsbiz - Taylan Kasap @@ -1556,6 +1571,7 @@ Symfony is the result of the work of many people who made the code better - Dawid Nowak - Lesnykh Ilia - Karolis Daužickas + - Nicolas - Sergio Santoro - tirnanog06 - phc @@ -1689,6 +1705,7 @@ Symfony is the result of the work of many people who made the code better - Adam Elsodaney (archfizz) - Daniel Kolvik (dkvk) - Marc Lemay (flug) + - Henne Van Och (hennevo) - Jeroen De Dauw (jeroendedauw) - Maxime COLIN (maximecolin) - Muharrem Demirci (mdemirci) diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 51181b0714e47..b0d9534a0e98d 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 04988d1539b24..0a52cc5110c82 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 @@ -262,6 +262,7 @@ {%- endblock form -%} {%- block form_start -%} + {%- do form.setMethodRendered() -%} {% set method = method|upper %} {%- if method in ["GET", "POST"] -%} {% set form_method = method %} @@ -301,6 +302,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 3b6c4356e14ba..9610edc8085b5 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", "symfony/finder": "~2.3", - "symfony/form": "~2.7.26|^2.8.19", + "symfony/form": "~2.7.30|^2.8.23", "symfony/http-kernel": "~2.3", "symfony/intl": "~2.3", "symfony/routing": "~2.2", @@ -36,6 +36,9 @@ "symfony/var-dumper": "~2.7.16|^2.8.9", "symfony/expression-language": "~2.4" }, + "conflict": { + "symfony/form": "<2.7.30|~2.8,<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 94daa82cc9a26..0a3ea2871deb3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -89,6 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln('Installing assets as hard copies.'); } + $validAssetDirs = array(); foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) { if (is_dir($originDir = $bundle->getPath().'/Resources/public')) { $targetDir = $bundlesDir.preg_replace('/bundle$/', '', strtolower($bundle->getName())); @@ -131,6 +132,13 @@ protected function execute(InputInterface $input, OutputInterface $output) } else { $this->hardCopy($originDir, $targetDir); } + $validAssetDirs[] = $targetDir; + } + } + // remove the assets of the bundles that no longer exist + foreach (new \FilesystemIterator($bundlesDir) as $dir) { + if (!in_array($dir, $validAssetDirs)) { + $filesystem->remove($dir); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php index 84c2fae7d4db8..5e9014bfc5ddd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php @@ -141,7 +141,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), @@ -153,14 +153,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 b21b3ee8df769..404bd60ced98d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -348,6 +348,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 3d125d616d393..85a1a9ba208f3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -402,7 +402,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', 'gc_maxlifetime', 'gc_probability', 'gc_divisor') as $key) { + foreach (array('name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', '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 fa7aa2b2bd808..2d866ff1b5cac 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 @@ + @@ -64,6 +65,10 @@ + + + + @@ -111,6 +116,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 5022aeaf9f207..6861844b49233 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 5b16a59796091..c0d8a85a1c5d7 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 47dcb575317e3..3e000ca9cc59c 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 a782a3f0748bb..5601e6a88b236 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -98,6 +98,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/TwigBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php index 0eab87de26e2b..1878204003b62 100644 --- a/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php @@ -74,7 +74,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 17f3ac17c87f2..82f10c8296fd2 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( - 'TwigBundle:Exception:error404.html.twig' => 'ok', - )) - ); + $twig = $this->createTwigEnv(array('TwigBundle: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( - 'TwigBundle:Exception:error.html.twig' => 'html', - )) - ); + $twig = $this->createTwigEnv(array('TwigBundle: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('TwigBundle: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('TwigBundle: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 a01a9017f4a49..2e05fb89abe71 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/toolbar.css.twig @@ -440,3 +440,33 @@ visibility: hidden; } } + +/***** 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 91ad9ad10ecf1..3bdd8ef1e843e 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 @@ -58,7 +58,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/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index c69599083802d..b5b2dab50d4cc 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -448,6 +448,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/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 cefe0a02aa829..f4274b80a4443 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -108,7 +108,7 @@ public function testGet() } $builder->register('foobar', 'stdClass')->setScope('container'); - $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'); } /** diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index edfc1b9d46a23..e60d4690738ad 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/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php index fc0dee140b047..47f9e43294ba0 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Form\Tests\Extension\Csrf\EventListener; use PHPUnit\Framework\TestCase; -use Symfony\Component\Form\Form; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; 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 36a20550b07cf..812f5f2e4fcc0 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 @@ -340,6 +344,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 0ffd46d11f7b1..93be7003c1b37 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -58,11 +58,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.7.29'; - const VERSION_ID = 20729; + const VERSION = '2.7.30'; + const VERSION_ID = 20730; const MAJOR_VERSION = 2; const MINOR_VERSION = 7; - const RELEASE_VERSION = 29; + const RELEASE_VERSION = 30; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '05/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 e034aeb28a743..90689c88cb8e6 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/Acl/Dbal/Schema.php b/src/Symfony/Component/Security/Acl/Dbal/Schema.php index ed9327ce7b139..864d0091af3ef 100644 --- a/src/Symfony/Component/Security/Acl/Dbal/Schema.php +++ b/src/Symfony/Component/Security/Acl/Dbal/Schema.php @@ -21,7 +21,7 @@ */ final class Schema extends BaseSchema { - protected $options; + private $options; /** * Constructor. 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/Authorization/AccessDecisionManager.php b/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php index b8b6a776e9b8e..c18c2cbf09f0f 100644 --- a/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php +++ b/src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php @@ -144,7 +144,6 @@ private function decideConsensus(TokenInterface $token, array $attributes, $obje { $grant = 0; $deny = 0; - $abstain = 0; foreach ($this->voters as $voter) { $result = $voter->vote($token, $object, $attributes); @@ -160,7 +159,6 @@ private function decideConsensus(TokenInterface $token, array $attributes, $obje break; default: - ++$abstain; break; } 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 b2049d47511ce..0243d8aa21596 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 0fd5b35ae333b..7d4db851fe23b 100644 --- a/src/Symfony/Component/Translation/Dumper/IcuResFileDumper.php +++ b/src/Symfony/Component/Translation/Dumper/IcuResFileDumper.php @@ -52,7 +52,7 @@ public function format(MessageCatalogue $messages, $domain = 'messages') $resOffset = $this->getPosition($data); - $data .= pack('v', count($messages)) + $data .= pack('v', count($messages->all($domain))) .$indexes .$this->writePadding($data) .$resources @@ -63,11 +63,11 @@ public function format(MessageCatalogue $messages, $domain = 'messages') $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/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 fb1f1f3ef7c75..34466c7cb3251 100644 --- a/src/Symfony/Component/Validator/Constraints/AbstractComparison.php +++ b/src/Symfony/Component/Validator/Constraints/AbstractComparison.php @@ -29,6 +29,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 5cb126d8fe2fe..26246fe6d2aad 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 b50ecbc63a1b5..e28ea54b4512c 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 9c8ce50fc3dd1..8843e6077f6d0 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); } /** @@ -169,9 +176,9 @@ 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); } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php index c20db1550ba27..b396601396429 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 41708f65c966e..923c7e61a08aa 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 85a2b1dad18d5..094949d97dfb1 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 0b6a4e411af9f..d7db609e17499 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 75181355109ff..8f1666ea81572 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 d555870c120b1..6d216e31644b9 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 bc2c348efade0..c969e2a2af099 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 43149847b9c5a..3aed5b7bf60b8 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 b0b57e3cda598..d50419f624394 100644 --- a/src/Symfony/Component/VarDumper/Caster/SplCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/SplCaster.php @@ -86,7 +86,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 59ba119e42914..822736abd5561 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -125,9 +125,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)) {