8000 Merge branch '3.4' into 4.2 · symfony/symfony@ecb52bc · GitHub
[go: up one dir, main page]

Skip to content

Commit ecb52bc

Browse files
Merge branch '3.4' into 4.2
* 3.4: fix translating file validation error message [Validator] Add missing Hungarian translations [3.4] [Validator] Add missing french validation translations. [Validator] Only traverse arrays that are cascaded into Handle case where no translations were found [Validator] Translate unique collection message to Hungarian fix tests Run test in separate process Use a class name that does not actually exist fix horizontal spacing of inlined Bootstrap forms [Translator] Warm up the translations cache in dev turn failed file uploads into form errors
2 parents ff3649b + c82e2df commit ecb52bc

File tree

21 files changed

+455
-51
lines changed

21 files changed

+455
-51
lines changed

src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,11 @@
112112
{%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%}
113113
{%- endif -%}
114114
<div class="form-group{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
115-
{{- form_label(form) -}}
116-
{{- form_widget(form, widget_attr) -}}
117-
{{- form_help(form) -}}
118-
{{- form_errors(form) -}}
119-
</div>
115+
{{- form_label(form) }} {# -#}
116+
{{ form_widget(form, widget_attr) }} {# -#}
117+
{{ form_widget(form) }} {# -#}
118+
{{ form_errors(form) }} {# -#}
119+
</div> {# -#}
120120
{%- endblock form_row %}
121121

122122
{% block button_row -%}

src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@
7070
<tag name="form.type" />
7171
<argument type="service" id="form.choice_list_factory"/>
7272
</service>
73+
<service id="form.type.file" class="Symfony\Component\Form\Extension\Core\Type\FileType" public="true">
74+
<argument type="service" id="translator" on-invalid="ignore" />
75+
</service>
7376

7477
<service id="form.type_extension.form.transformation_failure_handling" class="Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension">
7578
<tag name="form.type_extension" extended-type="Symfony\Component\Form\Extension\Core\Type\FormType" />

src/Symfony/Component/BrowserKit/Tests/ClientTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,9 @@ public function testRestart()
778778
$this->assertEquals([], $client->getCookieJar()->all(), '->restart() clears the cookies');
779779
}
780780

781+
/**
782+
* @runInSeparateProcess
783+
*/
781784
public function testInsulatedRequests()
782785
{
783786
$client = new TestClient();

src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ public function testHandleFatalError()
481481

482482
public function testHandleErrorException()
483483
{
484-
$exception = new \Error("Class 'Foo' not found");
484+
$exception = new \Error("Class 'IReallyReallyDoNotExistAnywhereInTheRepositoryISwear' not found");
485485

486486
$handler = new ErrorHandler();
487487
$handler->setExceptionHandler(function () use (&$args) {
@@ -491,7 +491,7 @@ public function testHandleErrorException()
491491
$handler->handleException($exception);
492492

493493
$this->assertInstanceOf('Symfony\Component\Debug\Exception\ClassNotFoundException', $args[0]);
494-
$this->assertStringStartsWith("Attempted to load class \"Foo\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
494+
$this->assertStringStartsWith("Attempted to load class \"IReallyReallyDoNotExistAnywhereInTheRepositoryISwear\" from the global namespace.\nDid you forget a \"use\" statement", $args[0]->getMessage());
495495
}
496496

497497
/**

src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2626
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
2727
use Symfony\Component\DependencyInjection\Reference;
28+
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
2829
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
2930
use Symfony\Component\DependencyInjection\TypedReference;
30-
use Symfony\Component\HttpKernel\HttpKernelInterface;
3131

3232
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
3333

@@ -547,13 +547,17 @@ public function testSetterInjection()
547547
);
548548
}
549549

550+
/**
551+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
552+
* @exceptedExceptionMessage Invalid service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy": method "setLogger()" does not exist.
553+
*/
550554
public function testWithNonExistingSetterAndAutowiring()
551555
{
552556
$container = new ContainerBuilder();
553557

554-
$definition = $container->register(HttpKernelInterface::class, HttpKernelInterface::class)->setAutowired(true);
558+
$definition = $container->register(CaseSensitiveClass::class, CaseSensitiveClass::class)->setAutowired(true);
555559
$definition->addMethodCall('setLogger');
556-
$this->expectException(RuntimeException::class);
560+
557561
(new ResolveClassPass())->process($container);
558562
(new AutowireRequiredMethodsPass())->process($container);
559563
(new AutowirePass())->process($container);

src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@
1717
use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
1919
use Symfony\Component\DependencyInjection\Definition;
20-
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2120
use Symfony\Component\DependencyInjection\Reference;
2221
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
2322
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
2423
use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists;
2524
use Symfony\Component\DependencyInjection\TypedReference;
26-
use Symfony\Component\HttpKernel\HttpKernelInterface;
2725

2826
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
2927

@@ -115,6 +113,10 @@ public function testScalarSetter()
115113
$this->assertEquals([['setDefaultLocale', ['fr']]], $definition->getMethodCalls());
116114
}
117115

116+
/**
117+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
118+
* @exceptedExceptionMessage Invalid service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy": method "setLogger()" does not exist.
119+
*/
118120
public function testWithNonExistingSetterAndBinding()
119121
{
120122
$container = new ContainerBuilder();
@@ -123,36 +125,11 @@ public function testWithNonExistingSetterAndBinding()
123125
'$c' => (new Definition('logger'))->setFactory('logger'),
124126
];
125127

126-
$definition = $container->register(HttpKernelInterface::class, HttpKernelInterface::class);
127-
$definition->addMethodCall('setLogger');
128-
$definition->setBindings($bindings);
129-
$this->expectException(RuntimeException::class);
130-
131-
$pass = new ResolveBindingsPass();
132-
$pass->process($container);
133-
}
134-
135-
public function testTupleBinding()
136-
{
137-
$container = new ContainerBuilder();
138-
139-
$bindings = [
140-
'$c' => new BoundArgument(new Reference('bar')),
141-
CaseSensitiveClass::class.'$c' => new BoundArgument(new Reference('foo')),
142-
];
143-
144128
$definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class);
145-
$definition->addMethodCall('setSensitiveClass');
146-
$definition->addMethodCall('setAnotherC');
129+
$definition->addMethodCall('setLogger');
147130
$definition->setBindings($bindings);
148131

149132
$pass = new ResolveBindingsPass();
150133
$pass->process($container);
151-
152-
$expected = [
153-
['setSensitiveClass', [new Reference('foo')]],
154-
['setAnotherC', [new Reference('bar')]],
155-
];
156-
$this->assertEquals($expected, $definition->getMethodCalls());
157134
}
158135
}

src/Symfony/Component/Form/Extension/Core/Type/FileType.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,33 @@
1313

1414
use Symfony\Component\Form\AbstractType;
1515
use Symfony\Component\Form\FormBuilderInterface;
16+
use Symfony\Component\Form\FormError;
1617
use Symfony\Component\Form\FormEvent;
1718
use Symfony\Component\Form\FormEvents;
1819
use Symfony\Component\Form\FormInterface;
1920
use Symfony\Component\Form\FormView;
2021
use Symfony\Component\OptionsResolver\Options;
2122
use Symfony\Component\OptionsResolver\OptionsResolver;
23+
use Symfony\Component\Translation\TranslatorInterface;
2224

2325
class FileType extends AbstractType
2426
{
27+
const KIB_BYTES = 1024;
28+
const MIB_BYTES = 1048576;
29+
30+
private static $suffixes = [
31+
1 => 'bytes',
32+
self::KIB_BYTES => 'KiB',
33+
self::MIB_BYTES => 'MiB',
34+
];
35+
36+
private $translator;
37+
38+
public function __construct(TranslatorInterface $translator = null)
39+
{
40+
$this->translator = $translator;
41+
}
42+
2543
/**
2644
* {@inheritdoc}
2745
*/
@@ -43,6 +61,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
4361
foreach ($files as $file) {
4462
if ($requestHandler->isFileUpload($file)) {
4563
$data[] = $file;
64+
65+
if (method_exists($requestHandler, 'getUploadFileError') && null !== $errorCode = $requestHandler->getUploadFileError($file)) {
66+
$form->addError($this->getFileUploadError($errorCode));
67+
}
4668
}
4769
}
4870

@@ -54,6 +76,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
5476
}
5577

5678
$event->setData($data);
79+
} elseif ($requestHandler->isFileUpload($event->getData()) && method_exists($requestHandler, 'getUploadFileError') && null !== $errorCode = $requestHandler->getUploadFileError($event->getData())) {
80+
$form->addError($this->getFileUploadError($errorCode));
5781
} elseif (!$requestHandler->isFileUpload($event->getData())) {
5882
$event->setData(null);
5983
}
@@ -116,4 +140,109 @@ public function getBlockPrefix()
116140
{
117141
return 'file';
118142
}
143+
144+
private function getFileUploadError($errorCode)
145+
{
146+
$messageParameters = [];
147+
148+
if (UPLOAD_ERR_INI_SIZE === $errorCode) {
149+
list($limitAsString, $suffix) = $this->factorizeSizes(0, self::getMaxFilesize());
150+
$messageTemplate = 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.';
151+
$messageParameters = [
152+
'{{ limit }}' => $limitAsString,
153+
'{{ suffix }}' => $suffix,
154+
];
155+
} elseif (UPLOAD_ERR_FORM_SIZE === $errorCode) {
156+
$messageTemplate = 'The file is too large.';
157+
} else {
158+
$messageTemplate = 'The file could not be uploaded.';
159+
}
160+
161+
if (null !== $this->translator) {
162+
$message = $this->translator->trans($messageTemplate, $messageParameters);
163+
} else {
164+
$message = strtr($messageTemplate, $messageParameters);
165+
}
166+
167+
return new FormError($message, $messageTemplate, $messageParameters);
168+
}
169+
170+
/**
171+
* Returns the maximum size of an uploaded file as configured in php.ini.
172+
*
173+
* This method should be kept in sync with Symfony\Component\HttpFoundation\File\UploadedFile::getMaxFilesize().
174+
*
175+
* @return int The maximum size of an uploaded file in bytes
176+
*/
177+
private static function getMaxFilesize()
178+
{
179+
$iniMax = strtolower(ini_get('upload_max_filesize'));
180+
181+
if ('' === $iniMax) {
182+
return PHP_INT_MAX;
183+
}
184+
185+
$max = ltrim($iniMax, '+');
186+
if (0 === strpos($max, '0x')) {
187+
$max = \intval($max, 16);
188+
} elseif (0 === strpos($max, '0')) {
189+
$max = \intval($max, 8);
190+
} else {
191+
$max = (int) $max;
192+
}
193+
194+
switch (substr($iniMax, -1)) {
195+
case 't': $max *= 1024;
196+
// no break
197+
case 'g': $max *= 1024;
198+
// no break
199+
case 'm': $max *= 1024;
200+
// no break
201+
case 'k': $max *= 1024;
202+
}
203+
204+
return $max;
205+
}
206+
207+
/**
208+
* Converts the limit to the smallest possible number
209+
* (i.e. try "MB", then "kB", then "bytes").
210+
*
211+
* This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::factorizeSizes().
212+
*/
213+
private function factorizeSizes($size, $limit)
214+
{
215+
$coef = self::MIB_BYTES;
216+
$coefFactor = self::KIB_BYTES;
217+
218+
$limitAsString = (string) ($limit / $coef);
219+
220+
// Restrict the limit to 2 decimals (without rounding! we
221+
// need the precise value)
222+
while (self::moreDecimalsThan($limitAsString, 2)) {
223+
$coef /= $coefFactor;
224+
$limitAsString = (string) ($limit / $coef);
225+
}
226+
227+
// Convert size to the same measure, but round to 2 decimals
228+
$sizeAsString = (string) round($size / $coef, 2);
229+
230+
// If the size and limit produce the same string output
231+
// (due to rounding), reduce the coefficient
232+
while ($sizeAsString === $limitAsString) {
233+
$coef /= $coefFactor;
234+
$limitAsString = (string) ($limit / $coef);
235+
$sizeAsString = (string) round($size / $coef, 2);
236+
}
237+
238+
return [$limitAsString, self::$suffixes[$coef]];
239+
}
240+
241+
/**
242+
* This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::moreDecimalsThan().
243+
*/
244+
private static function moreDecimalsThan($double, $numberOfDecimals)
245+
{
246+
return \strlen((string) $double) > \strlen(round($double, $numberOfDecimals));
247+
}
119248
}

src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Form\RequestHandlerInterface;
1818
use Symfony\Component\Form\Util\ServerParams;
1919
use Symfony\Component\HttpFoundation\File\File;
20+
use Symfony\Component\HttpFoundation\File\UploadedFile;
2021
use Symfony\Component\HttpFoundation\Request;
2122

2223
/**
@@ -115,4 +116,16 @@ public function isFileUpload($data)
115116
{
116117
return $data instanceof File;
117118
}
119+
120+
/**
121+
* @return int|null
122+
*/
123+
public function getUploadFileError($data)
124+
{
125+
if (!$data instanceof UploadedFile || $data->isValid()) {
126+
return null;
127+
}
128+
129+
return $data->getError();
130+
}
118131
}

src/Symfony/Component/Form/NativeRequestHandler.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,30 @@ public function isFileUpload($data)
135135
return \is_array($data) && isset($data['error']) && \is_int($data['error']);
136136
}
137137

138+
/**
139+
* @return int|null
140+
*/
141+
public function getUploadFileError($data)
142+
{
143+
if (!\is_array($data)) {
144+
return null;
145+
}
146+
147+
if (!isset($data['error'])) {
148+
return null;
149+
}
150+
151+
if (!\is_int($data['error'])) {
152+
return null;
153+
}
154+
155+
if (UPLOAD_ERR_OK === $data['error']) {
156+
return null;
157+
}
158+
159+
return $data['error'];
160+
}
161+
138162
/**
139163
* Returns the method used to submit the request to the server.
140164
*

0 commit comments

Comments
 (0)
0