Merge branch '2.8' into 3.2

* 2.8: (40 commits)
  Show exception is checked twice in ExceptionController of twig
  allow SSI fragments configuration in XML files
  Display a better error message when the toolbar cannot be displayed
  render hidden _method field in form_rest()
  return fallback locales whenever possible
  [Console] Fix catching exception type in QuestionHelper
  [WebProfilerBundle] Eliminate line wrap on count columnt (routing)
  [Routing] Fix XmlFileLoader exception message
  [Translation] Fix FileLoader::loadResource() php doc
  Sessions: configurable "use_strict_mode" option for NativeSessionStorage
  [FrameworkBundle] [Command] Clean bundle directory, fixes #23177
  Reset redirectCount when throwing exception
  [TwigBundle] Remove template.xml services when templating is disabled
  add content-type header on exception response
  Embedding a response that combines expiration and validation, that should not defeat expiration on the combined response
  Fix two edge cases in ResponseCacheStrategy
  [Routing] Expose request in route conditions, if needed and possible
  [Routing] Expose request in route conditions, if needed and possible
  [Translation][FrameworkBundle] Fix resource loading order inconsistency reported in #23034
  [Filesystem] added workaround in Filesystem::rename for PHP bug
  ...
This commit is contained in:
Christian Flothmann 2017-06-23 08:35:45 +02:00
commit 80b114e66b
48 changed files with 449 additions and 76 deletions

View File

@ -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)
@ -244,6 +244,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
@ -279,7 +280,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)
@ -389,6 +389,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
@ -525,7 +526,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)
@ -1280,6 +1280,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)

View File

@ -285,6 +285,7 @@
{%- endblock form -%}
{%- block form_start -%}
{%- do form.setMethodRendered() -%}
{% set method = method|upper %}
{%- if method in ["GET", "POST"] -%}
{% set form_method = method %}
@ -320,6 +321,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 -%}
<input type="hidden" name="_method" value="{{ method }}" />
{%- endif -%}
{% endif %}
{% endblock form_rest %}
{# Support #}

View File

@ -22,7 +22,7 @@
"require-dev": {
"symfony/asset": "~2.8|~3.0",
"symfony/finder": "~2.8|~3.0",
"symfony/form": "^3.2.7",
"symfony/form": "^3.2.10|^3.3.3",
"symfony/http-kernel": "~3.2",
"symfony/polyfill-intl-icu": "~1.0",
"symfony/routing": "~2.8|~3.0",
@ -36,6 +36,9 @@
"symfony/var-dumper": "~2.8.10|~3.1.4|~3.2",
"symfony/expression-language": "~2.8|~3.0"
},
"conflict": {
"symfony/form": "<3.2.10|~3.3,<3.3.3"
},
"suggest": {
"symfony/finder": "",
"symfony/asset": "For using the AssetExtension",

View File

@ -110,6 +110,7 @@ EOT
$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 @@ EOT
$exitCode = 1;
$rows[] = array(sprintf('<fg=red;options=bold>%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)) {
$filesystem->remove($dir);
}
}
$io->table(array('', 'Bundle', 'Method / Error'), $rows);

View File

@ -147,7 +147,7 @@ EOF
$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 @@ EOF
$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));

View File

@ -400,6 +400,7 @@ class Configuration implements ConfigurationInterface
->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')

View File

@ -555,7 +555,7 @@ class FrameworkExtension extends Extension
// 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];
}

View File

@ -13,6 +13,7 @@
<xsd:element name="form" type="form" minOccurs="0" maxOccurs="1" />
<xsd:element name="csrf-protection" type="csrf_protection" minOccurs="0" maxOccurs="1" />
<xsd:element name="esi" type="esi" minOccurs="0" maxOccurs="1" />
<xsd:element name="ssi" type="ssi" minOccurs="0" maxOccurs="1" />
<xsd:element name="fragments" type="fragments" minOccurs="0" maxOccurs="1" />
<xsd:element name="profiler" type="profiler" minOccurs="0" maxOccurs="1" />
<xsd:element name="router" type="router" minOccurs="0" maxOccurs="1" />
@ -57,6 +58,10 @@
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="ssi">
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="fragments">
<xsd:attribute name="enabled" type="xsd:boolean" />
<xsd:attribute name="path" type="xsd:string" />
@ -105,6 +110,7 @@
<xsd:attribute name="gc-maxlifetime" type="xsd:string" />
<xsd:attribute name="gc-divisor" type="xsd:string" />
<xsd:attribute name="gc-probability" type="xsd:string" />
<xsd:attribute name="use-strict-mode" type="xsd:boolean" />
<xsd:attribute name="save-path" type="xsd:string" />
</xsd:complexType>

View File

@ -14,6 +14,9 @@ $container->loadFromExtension('framework', array(
'esi' => array(
'enabled' => true,
),
'ssi' => array(
'enabled' => true,
),
'profiler' => array(
'only_exceptions' => true,
'enabled' => false,

View File

@ -12,6 +12,7 @@
<framework:csrf-protection field-name="_csrf"/>
</framework:form>
<framework:esi enabled="true" />
<framework:ssi enabled="true" />
<framework:profiler only-exceptions="true" enabled="false" />
<framework:router resource="%kernel.root_dir%/config/routing.xml" type="xml" />
<framework:session gc-maxlifetime="90000" gc-probability="1" gc-divisor="108" storage-id="session.storage.native" handler-id="session.handler.native_file" name="_SYMFONY" cookie-lifetime="86400" cookie-path="/" cookie-domain="example.com" cookie-secure="true" cookie-httponly="false" use-cookies="true" save-path="/path/to/sessions" />

View File

@ -9,6 +9,8 @@ framework:
trusted_proxies: ['127.0.0.1', '10.0.0.1']
esi:
enabled: true
ssi:
enabled: true
profiler:
only_exceptions: true
enabled: false

View File

@ -133,6 +133,13 @@ abstract class FrameworkExtensionTest extends TestCase
$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');

View File

@ -144,7 +144,52 @@ class TranslatorTest extends TestCase
{
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
(new Translator($container, new MessageSelector(), array(), array('foo' => 'bar')));
(new Translator($container, new MessageSelector(), [], ['foo' => 'bar']));
}
/** @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())

View File

@ -38,6 +38,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.
*
@ -66,9 +74,7 @@ class Translator extends BaseTranslator implements WarmableInterface
$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']);
}
@ -94,6 +100,11 @@ class Translator extends BaseTranslator implements WarmableInterface
}
}
public function addResource($format, $resource, $locale, $domain = null)
{
$this->resources[] = array($format, $resource, $locale, $domain);
}
/**
* {@inheritdoc}
*/
@ -105,7 +116,12 @@ class Translator extends BaseTranslator implements WarmableInterface
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));
@ -113,14 +129,13 @@ class Translator extends BaseTranslator implements WarmableInterface
}
}
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]);
}
}
}

View File

@ -114,7 +114,7 @@ class SecurityDataCollector extends DataCollector
'logout_url' => $logoutUrl,
'user' => $token->getUsername(),
'roles' => $this->cloneVar(array_map(function (RoleInterface $role) { return $role->getRole(); }, $assignedRoles)),
'inherited_roles' => $this->cloneVar(array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles)),
'inherited_roles' => $this->cloneVar(array_unique(array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles))),
'supports_role_hierarchy' => null !== $this->roleHierarchy,
);
}

View File

@ -166,6 +166,11 @@ class SecurityDataCollectorTest extends TestCase
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'),
),
);
}
@ -173,6 +178,7 @@ class SecurityDataCollectorTest extends TestCase
{
return new RoleHierarchy(array(
'ROLE_ADMIN' => array('ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'),
'ROLE_OPERATOR' => array('ROLE_USER'),
));
}

View File

@ -72,7 +72,7 @@ class ExceptionController
'logger' => $logger,
'currentContent' => $currentContent,
)
));
), 200, array('Content-Type' => $request->getMimeType($request->getRequestFormat()) ?: 'text/html'));
}
/**
@ -123,7 +123,7 @@ class ExceptionController
// default to a generic HTML exception
$request->setRequestFormat('html');
return sprintf('@Twig/Exception/%s.html.twig', $showException ? 'exception_full' : $name);
return sprintf('@Twig/Exception/%s.html.twig', $name);
}
// to be removed when the minimum required version of Twig is >= 3.0

View File

@ -96,6 +96,7 @@ class ExtensionPass implements CompilerPassInterface
} else {
$twigLoader->replaceArgument(1, $composerRootDir);
$container->setAlias('twig.loader.filesystem', new Alias('twig.loader.native_filesystem', false));
$container->removeDefinition('templating.engine.twig');
}
if ($container->has('assets.packages')) {

View File

@ -76,7 +76,6 @@ class FilesystemLoader extends BaseFilesystemLoader
}
$file = null;
$previous = null;
try {
$file = parent::findTemplate($logicalName);
} catch (LoaderError $e) {

View File

@ -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' => '<html>not found</html>'));
$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,47 @@ class ExceptionControllerTest extends TestCase
$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('<html>not found</html>', $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' => '<html></html>'));
$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);
$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('html', $request->getRequestFormat());
$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));
}
}

View File

@ -136,9 +136,10 @@ class TwigExtensionTest extends TestCase
$calls = $container->getDefinition('twig')->getMethodCalls();
foreach (array_slice($calls, 2) 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);
}
}

View File

@ -369,6 +369,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;

View File

@ -96,8 +96,17 @@
})
},
function(xhr) {
var errorToolbarHtml = '
<style>
.sfErrorToolbar { background: #222; bottom: 0; color: #f5f5f5; font: 13px/36px Arial, sans-serif; height: 36px; padding: 0 15px; position: fixed; width: 100%; }
.sfErrorToolbar a { color: #99cdd8; margin-left: 5px; text-decoration: underline; }
.sfErrorToolbar a:hover { text-decoration: none; }
</style>
<div class="sfErrorToolbar">An error occurred while loading the web debug toolbar. <a href="{{ path("_profiler", { "token": token }) }}">Open the web profiler.</a></div>
';
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 }) }}');
window.document.body.insertAdjacentHTML('beforeend', errorToolbarHtml);
}
},
{ maxTries: 5 }

View File

@ -54,7 +54,7 @@
<tbody>
{% for trace in traces %}
<tr class="{{ trace.level == 1 ? 'status-warning' : trace.level == 2 ? 'status-success' }}">
<td class="font-normal text-muted">{{ loop.index }}</td>
<td class="font-normal text-muted nowrap">{{ loop.index }}</td>
<td>{{ trace.name }}</td>
<td>{{ trace.path }}</td>
<td class="font-normal">

View File

@ -468,6 +468,7 @@ abstract class Client
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));
}
}

View File

@ -124,8 +124,7 @@ class QuestionHelper extends Helper
*
* @return bool|mixed|null|string
*
* @throws \Exception
* @throws \RuntimeException
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function doAsk(OutputInterface $output, Question $question)
{
@ -139,7 +138,7 @@ class QuestionHelper extends Helper
if ($question->isHidden()) {
try {
$ret = trim($this->getHiddenResponse($output, $inputStream));
} catch (\RuntimeException $e) {
} catch (RuntimeException $e) {
if (!$question->isHiddenFallback()) {
throw $e;
}

View File

@ -276,6 +276,13 @@ class Filesystem
}
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);
}
}

View File

@ -49,7 +49,7 @@ class LockHandlerTest extends TestCase
$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;

View File

@ -54,7 +54,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
if (!is_callable($preferredChoices) && !empty($preferredChoices)) {
$preferredChoices = function ($choice) use ($preferredChoices) {
return false !== array_search($choice, $preferredChoices, true);
return in_array($choice, $preferredChoices, true);
};
}

View File

@ -27,7 +27,7 @@ interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuild
* @param string|null $type
* @param array $options
*
* @return $this
* @return self
*/
public function add($child, $type = null, array $options = array());
@ -58,7 +58,7 @@ interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuild
*
* @param string $name
*
* @return $this
* @return self
*/
public function remove($name);

View File

@ -305,6 +305,6 @@ class FormRenderer implements FormRendererInterface
*/
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))));
}
}

View File

@ -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 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
return $this;
}
/**
* @return bool
*/
public function isMethodRendered()
{
return $this->methodRendered;
}
public function setMethodRendered()
{
$this->methodRendered = true;
}
/**
* Returns a child by name (implements \ArrayAccess).
*

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\HttpFoundation;
*/
class IpUtils
{
private static $checkedIps = array();
/**
* This class should not be instantiated.
*/
@ -61,26 +63,31 @@ class IpUtils
*/
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 @@ class IpUtils
*/
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 @@ class IpUtils
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 @@ class IpUtils
$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 @@ class IpUtils
$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;
}
}

View File

@ -91,6 +91,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
@ -316,6 +320,7 @@ class NativeSessionStorage implements SessionStorageInterface
'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) {

View File

@ -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 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
if ($response->isValidateable()) {
$response->setEtag(null);
$response->setLastModified(null);
}
if (!$response->isFresh()) {
$this->cacheable = false;
}

View File

@ -75,4 +75,148 @@ class ResponseCacheStrategyTest extends TestCase
$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());
}
}

View File

@ -225,7 +225,7 @@ class XmlFileLoader extends FileLoader
$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));
}
}

View File

@ -49,7 +49,7 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable
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);
}

View File

@ -105,7 +105,7 @@ class TraceableUrlMatcher extends UrlMatcher
// 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;

View File

@ -207,7 +207,7 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
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 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface
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(),
));
}
}

View File

@ -337,6 +337,16 @@ class UrlMatcherTest extends TestCase
$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();

View File

@ -161,7 +161,7 @@ class SwitchUserListener implements ListenerInterface
*/
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.');
}

View File

@ -65,6 +65,17 @@ class SwitchUserListenerTest extends TestCase
$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
*/

View File

@ -97,7 +97,7 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
*/
public function getFallbackLocales()
{
if ($this->translator instanceof Translator) {
if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
return $this->translator->getFallbackLocales();
}

View File

@ -54,7 +54,7 @@ abstract class FileLoader extends ArrayLoader
return $catalogue;
}
/*
/**
* @param string $resource
*
* @return array

View File

@ -96,7 +96,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface
*/
public function getFallbackLocales()
{
if ($this->translator instanceof Translator) {
if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
return $this->translator->getFallbackLocales();
}

View File

@ -50,7 +50,7 @@ class ResourceCaster
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)

View File

@ -184,7 +184,7 @@ class SplCaster
$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(),