Merge branch '2.8' into 3.1

* 2.8:
  [Routing] Add missing options in docblock
  [VarDumper] Fix dumping continuations
  [HttpFoundation] fixed Request::getContent() reusage bug
  [Form] Skip CSRF validation on form when POST max size is exceeded
  Enhance the phpDoc return types so IDEs can handle the configuration tree.
  fixes
  Remove 3.0 from branch suggestions for fixes in PR template
  [Process] Strengthen Windows pipe files opening (again...)
  Fix #19531 [Form] DateType fails parsing when midnight is not a valid time
This commit is contained in:
Fabien Potencier 2016-08-16 07:58:24 -07:00
commit d7f8ca72e8
20 changed files with 191 additions and 59 deletions

View File

@ -1,6 +1,6 @@
| Q | A | Q | A
| ------------- | --- | ------------- | ---
| Branch? | "master" for new features / 2.7, 2.8, 3.0 or 3.1 for fixes | Branch? | "master" for new features / 2.7, 2.8 or 3.1 for fixes
| Bug fix? | yes/no | Bug fix? | yes/no
| New feature? | yes/no | New feature? | yes/no
| BC breaks? | yes/no | BC breaks? | yes/no

View File

@ -12,6 +12,7 @@
<argument>%form.type_extension.csrf.field_name%</argument> <argument>%form.type_extension.csrf.field_name%</argument>
<argument type="service" id="translator.default" /> <argument type="service" id="translator.default" />
<argument>%validator.translation_domain%</argument> <argument>%validator.translation_domain%</argument>
<argument type="service" id="form.server_params" />
</service> </service>
</services> </services>
</container> </container>

View File

@ -138,7 +138,7 @@ class NodeBuilder implements NodeParentInterface
/** /**
* Returns the parent node. * Returns the parent node.
* *
* @return ParentNodeDefinitionInterface The parent node * @return ParentNodeDefinitionInterface|NodeDefinition The parent node
*/ */
public function end() public function end()
{ {

View File

@ -107,7 +107,7 @@ abstract class NodeDefinition implements NodeParentInterface
/** /**
* Returns the parent node. * Returns the parent node.
* *
* @return NodeParentInterface|null The builder of the parent node * @return NodeParentInterface|NodeBuilder|NodeDefinition|null The builder of the parent node
*/ */
public function end() public function end()
{ {

View File

@ -116,20 +116,29 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
return; return;
} }
$timestamp = $this->getIntlDateFormatter()->parse($value); // date-only patterns require parsing to be done in UTC, as midnight might not exist in the local timezone due
// to DST changes
$dateOnly = $this->isPatternDateOnly();
$timestamp = $this->getIntlDateFormatter($dateOnly)->parse($value);
if (intl_get_error_code() != 0) { if (intl_get_error_code() != 0) {
throw new TransformationFailedException(intl_get_error_message()); throw new TransformationFailedException(intl_get_error_message());
} }
try { try {
// read timestamp into DateTime object - the formatter delivers in UTC if ($dateOnly) {
$dateTime = new \DateTime(sprintf('@%s', $timestamp)); // we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight
return new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->inputTimezone));
}
// read timestamp into DateTime object - the formatter delivers a timestamp
$dateTime = new \DateTime(sprintf('@%s', $timestamp), new \DateTimeZone($this->outputTimezone));
} catch (\Exception $e) { } catch (\Exception $e) {
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
} }
if ('UTC' !== $this->inputTimezone) { if ($this->outputTimezone !== $this->inputTimezone) {
$dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone));
} }
@ -139,15 +148,17 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
/** /**
* Returns a preconfigured IntlDateFormatter instance. * Returns a preconfigured IntlDateFormatter instance.
* *
* @param bool $ignoreTimezone Use UTC regardless of the configured timezone.
*
* @return \IntlDateFormatter * @return \IntlDateFormatter
* *
* @throws TransformationFailedException in case the date formatter can not be constructed. * @throws TransformationFailedException in case the date formatter can not be constructed.
*/ */
protected function getIntlDateFormatter() protected function getIntlDateFormatter($ignoreTimezone = false)
{ {
$dateFormat = $this->dateFormat; $dateFormat = $this->dateFormat;
$timeFormat = $this->timeFormat; $timeFormat = $this->timeFormat;
$timezone = $this->outputTimezone; $timezone = $ignoreTimezone ? 'UTC' : $this->outputTimezone;
$calendar = $this->calendar; $calendar = $this->calendar;
$pattern = $this->pattern; $pattern = $this->pattern;
@ -162,4 +173,24 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
return $intlDateFormatter; return $intlDateFormatter;
} }
/**
* Checks if the pattern contains only a date.
*
* @param string $pattern The input pattern
*
* @return bool
*/
protected function isPatternDateOnly()
{
if (null === $this->pattern) {
return false;
}
// strip escaped text
$pattern = preg_replace("#'(.*?)'#", '', $this->pattern);
// check for the absence of time-related placeholders
return 0 === preg_match('#[ahHkKmsSAzZOvVxX]#', $pattern);
}
} }

View File

@ -15,6 +15,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Util\ServerParams;
use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Translation\TranslatorInterface;
@ -65,6 +66,11 @@ class CsrfValidationListener implements EventSubscriberInterface
*/ */
private $translationDomain; private $translationDomain;
/**
* @var ServerParams
*/
private $serverParams;
public static function getSubscribedEvents() public static function getSubscribedEvents()
{ {
return array( return array(
@ -72,7 +78,7 @@ class CsrfValidationListener implements EventSubscriberInterface
); );
} }
public function __construct($fieldName, CsrfTokenManagerInterface $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) public function __construct($fieldName, CsrfTokenManagerInterface $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null, ServerParams $serverParams = null)
{ {
$this->fieldName = $fieldName; $this->fieldName = $fieldName;
$this->tokenManager = $tokenManager; $this->tokenManager = $tokenManager;
@ -80,13 +86,15 @@ class CsrfValidationListener implements EventSubscriberInterface
$this->errorMessage = $errorMessage; $this->errorMessage = $errorMessage;
$this->translator = $translator; $this->translator = $translator;
$this->translationDomain = $translationDomain; $this->translationDomain = $translationDomain;
$this->serverParams = $serverParams ?: new ServerParams();
} }
public function preSubmit(FormEvent $event) public function preSubmit(FormEvent $event)
{ {
$form = $event->getForm(); $form = $event->getForm();
$postRequestSizeExceeded = $form->getConfig()->getMethod() === 'POST' && $this->serverParams->hasPostMaxSizeBeenExceeded();
if ($form->isRoot() && $form->getConfig()->getOption('compound')) { if ($form->isRoot() && $form->getConfig()->getOption('compound') && !$postRequestSizeExceeded) {
$data = $event->getData(); $data = $event->getData();
if (!isset($data[$this->fieldName]) || !$this->tokenManager->isTokenValid(new CsrfToken($this->tokenId, $data[$this->fieldName]))) { if (!isset($data[$this->fieldName]) || !$this->tokenManager->isTokenValid(new CsrfToken($this->tokenId, $data[$this->fieldName]))) {

View File

@ -16,6 +16,7 @@ use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Util\ServerParams;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Translation\TranslatorInterface;
@ -50,13 +51,19 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
*/ */
private $translationDomain; private $translationDomain;
public function __construct(CsrfTokenManagerInterface $defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) /**
* @var ServerParams
*/
private $serverParams;
public function __construct(CsrfTokenManagerInterface $defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null, ServerParams $serverParams = null)
{ {
$this->defaultTokenManager = $defaultTokenManager; $this->defaultTokenManager = $defaultTokenManager;
$this->defaultEnabled = $defaultEnabled; $this->defaultEnabled = $defaultEnabled;
$this->defaultFieldName = $defaultFieldName; $this->defaultFieldName = $defaultFieldName;
$this->translator = $translator; $this->translator = $translator;
$this->translationDomain = $translationDomain; $this->translationDomain = $translationDomain;
$this->serverParams = $serverParams;
} }
/** /**
@ -78,7 +85,8 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
$options['csrf_token_id'] ?: ($builder->getName() ?: get_class($builder->getType()->getInnerType())), $options['csrf_token_id'] ?: ($builder->getName() ?: get_class($builder->getType()->getInnerType())),
$options['csrf_message'], $options['csrf_message'],
$this->translator, $this->translator,
$this->translationDomain $this->translationDomain,
$this->serverParams
)) ))
; ;
} }

View File

@ -73,10 +73,7 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface
// Mark the form with an error if the uploaded size was too large // Mark the form with an error if the uploaded size was too large
// This is done here and not in FormValidator because $_POST is // This is done here and not in FormValidator because $_POST is
// empty when that error occurs. Hence the form is never submitted. // empty when that error occurs. Hence the form is never submitted.
$contentLength = $this->serverParams->getContentLength(); if ($this->serverParams->hasPostMaxSizeBeenExceeded()) {
$maxContentLength = $this->serverParams->getPostMaxSize();
if (!empty($maxContentLength) && $contentLength > $maxContentLength) {
// Submit the form, but don't clear the default values // Submit the form, but don't clear the default values
$form->submit(null, false); $form->submit(null, false);

View File

@ -81,10 +81,7 @@ class NativeRequestHandler implements RequestHandlerInterface
// Mark the form with an error if the uploaded size was too large // Mark the form with an error if the uploaded size was too large
// This is done here and not in FormValidator because $_POST is // This is done here and not in FormValidator because $_POST is
// empty when that error occurs. Hence the form is never submitted. // empty when that error occurs. Hence the form is never submitted.
$contentLength = $this->serverParams->getContentLength(); if ($this->serverParams->hasPostMaxSizeBeenExceeded()) {
$maxContentLength = $this->serverParams->getPostMaxSize();
if (!empty($maxContentLength) && $contentLength > $maxContentLength) {
// Submit the form, but don't clear the default values // Submit the form, but don't clear the default values
$form->submit(null, false); $form->submit(null, false);

View File

@ -227,6 +227,26 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
$this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('02*2010*03 04|05|06')); $this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('02*2010*03 04|05|06'));
} }
public function testReverseTransformDateOnlyWithDstIssue()
{
$transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'dd/MM/yyyy');
$this->assertDateTimeEquals(
new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')),
$transformer->reverseTransform('28/05/1978')
);
}
public function testReverseTransformDateOnlyWithDstIssueAndEscapedText()
{
$transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, "'day': dd 'month': MM 'year': yyyy");
$this->assertDateTimeEquals(
new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')),
$transformer->reverseTransform('day: 28 month: 05 year: 1978')
);
}
public function testReverseTransformEmpty() public function testReverseTransformEmpty()
{ {
$transformer = new DateTimeToLocalizedStringTransformer(); $transformer = new DateTimeToLocalizedStringTransformer();

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Form\Tests\Extension\Csrf\EventListener; namespace Symfony\Component\Form\Tests\Extension\Csrf\EventListener;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener;
@ -72,4 +73,25 @@ class CsrfValidationListenerTest extends \PHPUnit_Framework_TestCase
// Validate accordingly // Validate accordingly
$this->assertSame($data, $event->getData()); $this->assertSame($data, $event->getData());
} }
public function testMaxPostSizeExceeded()
{
$serverParams = $this
->getMockBuilder('\Symfony\Component\Form\Util\ServerParams')
->disableOriginalConstructor()
->getMock()
;
$serverParams
->expects($this->once())
->method('hasPostMaxSizeBeenExceeded')
->willReturn(true)
;
$event = new FormEvent($this->form, array('csrf' => 'token'));
$validation = new CsrfValidationListener('csrf', $this->tokenManager, 'unknown', 'Error message', null, null, $serverParams);
$validation->preSubmit($event);
$this->assertEmpty($this->form->getErrors());
}
} }

View File

@ -25,6 +25,19 @@ class ServerParams
$this->requestStack = $requestStack; $this->requestStack = $requestStack;
} }
/**
* Returns true if the POST max size has been exceeded in the request.
*
* @return bool
*/
public function hasPostMaxSizeBeenExceeded()
{
$contentLength = $this->getContentLength();
$maxContentLength = $this->getPostMaxSize();
return $maxContentLength && $contentLength > $maxContentLength;
}
/** /**
* Returns maximum post size in bytes. * Returns maximum post size in bytes.
* *

View File

@ -1525,7 +1525,7 @@ class Request
return stream_get_contents($this->content); return stream_get_contents($this->content);
} }
if (null === $this->content) { if (null === $this->content || false === $this->content) {
$this->content = file_get_contents('php://input'); $this->content = file_get_contents('php://input');
} }

View File

@ -1059,8 +1059,16 @@ class RequestTest extends \PHPUnit_Framework_TestCase
$req->getContent($second); $req->getContent($second);
} }
public function getContentCantBeCalledTwiceWithResourcesProvider()
{
return array(
'Resource then fetch' => array(true, false),
'Resource then resource' => array(true, true),
);
}
/** /**
* @dataProvider getContentCantBeCalledTwiceWithResourcesProvider * @dataProvider getContentCanBeCalledTwiceWithResourcesProvider
* @requires PHP 5.6 * @requires PHP 5.6
*/ */
public function testGetContentCanBeCalledTwiceWithResources($first, $second) public function testGetContentCanBeCalledTwiceWithResources($first, $second)
@ -1077,12 +1085,14 @@ class RequestTest extends \PHPUnit_Framework_TestCase
$b = stream_get_contents($b); $b = stream_get_contents($b);
} }
$this->assertEquals($a, $b); $this->assertSame($a, $b);
} }
public function getContentCantBeCalledTwiceWithResourcesProvider() public function getContentCanBeCalledTwiceWithResourcesProvider()
{ {
return array( return array(
'Fetch then fetch' => array(false, false),
'Fetch then resource' => array(false, true),
'Resource then fetch' => array(true, false), 'Resource then fetch' => array(true, false),
'Resource then resource' => array(true, true), 'Resource then resource' => array(true, true),
); );

View File

@ -201,9 +201,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface
foreach ($this->data as $dump) { foreach ($this->data as $dump) {
$dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth)); $dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth));
$dump['data'] = stream_get_contents($data, -1, 0);
rewind($data);
$dump['data'] = stream_get_contents($data);
ftruncate($data, 0); ftruncate($data, 0);
rewind($data); rewind($data);
$dumps[] = $dump; $dumps[] = $dump;

View File

@ -51,9 +51,10 @@ class WindowsPipes extends AbstractPipes
Process::STDOUT => Process::OUT, Process::STDOUT => Process::OUT,
Process::STDERR => Process::ERR, Process::STDERR => Process::ERR,
); );
$tmpCheck = false;
$tmpDir = sys_get_temp_dir(); $tmpDir = sys_get_temp_dir();
$error = 'unknown reason'; $lastError = 'unknown reason';
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
for ($i = 0;; ++$i) { for ($i = 0;; ++$i) {
foreach ($pipes as $pipe => $name) { foreach ($pipes as $pipe => $name) {
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
@ -61,7 +62,11 @@ class WindowsPipes extends AbstractPipes
continue 2; continue 2;
} }
$h = fopen($file, 'xb'); $h = fopen($file, 'xb');
if (!$h && false === strpos($error, 'File exists')) { if (!$h) {
$error = $lastError;
if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) {
continue;
}
restore_error_handler(); restore_error_handler();
throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error)); throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error));
} }

View File

@ -106,9 +106,19 @@ class Router implements RouterInterface, RequestMatcherInterface
* *
* Available options: * Available options:
* *
* * cache_dir: The cache directory (or null to disable caching) * * cache_dir: The cache directory (or null to disable caching)
* * debug: Whether to enable debugging or not (false by default) * * debug: Whether to enable debugging or not (false by default)
* * resource_type: Type hint for the main resource (optional) * * generator_class: The name of a UrlGeneratorInterface implementation
* * generator_base_class: The base class for the dumped generator class
* * generator_cache_class: The class name for the dumped generator class
* * generator_dumper_class: The name of a GeneratorDumperInterface implementation
* * matcher_class: The name of a UrlMatcherInterface implementation
* * matcher_base_class: The base class for the dumped matcher class
* * matcher_dumper_class: The class name for the dumped matcher class
* * matcher_cache_class: The name of a MatcherDumperInterface implementation
* * resource_type: Type hint for the main resource (optional)
* * strict_requirements: Configure strict requirement checking for generators
* implementing ConfigurableRequirementsInterface (default is true)
* *
* @param array $options An array of options * @param array $options An array of options
* *

View File

@ -4,7 +4,7 @@
<body> <body>
<trans-unit id="1"> <trans-unit id="1">
<source>This value should be false.</source> <source>This value should be false.</source>
<target>Deze waarde mag niet waar zijn.</target> <target>Deze waarde moet onwaar zijn.</target>
</trans-unit> </trans-unit>
<trans-unit id="2"> <trans-unit id="2">
<source>This value should be true.</source> <source>This value should be true.</source>
@ -36,7 +36,7 @@
</trans-unit> </trans-unit>
<trans-unit id="9"> <trans-unit id="9">
<source>This field was not expected.</source> <source>This field was not expected.</source>
<target>Dit veld was niet verwacht.</target> <target>Dit veld werd niet verwacht.</target>
</trans-unit> </trans-unit>
<trans-unit id="10"> <trans-unit id="10">
<source>This field is missing.</source> <source>This field is missing.</source>
@ -56,7 +56,7 @@
</trans-unit> </trans-unit>
<trans-unit id="14"> <trans-unit id="14">
<source>The file could not be found.</source> <source>The file could not be found.</source>
<target>Het bestand is niet gevonden.</target> <target>Het bestand kon niet gevonden worden.</target>
</trans-unit> </trans-unit>
<trans-unit id="15"> <trans-unit id="15">
<source>The file is not readable.</source> <source>The file is not readable.</source>
@ -100,7 +100,7 @@
</trans-unit> </trans-unit>
<trans-unit id="25"> <trans-unit id="25">
<source>This value is not valid.</source> <source>This value is not valid.</source>
<target>Deze waarde is ongeldig.</target> <target>Deze waarde is niet geldig.</target>
</trans-unit> </trans-unit>
<trans-unit id="26"> <trans-unit id="26">
<source>This value is not a valid time.</source> <source>This value is not a valid time.</source>
@ -124,7 +124,7 @@
</trans-unit> </trans-unit>
<trans-unit id="34"> <trans-unit id="34">
<source>The file could not be uploaded.</source> <source>The file could not be uploaded.</source>
<target>Het bestand kon niet geüpload worden.</target> <target>Het bestand kon niet worden geüpload.</target>
</trans-unit> </trans-unit>
<trans-unit id="35"> <trans-unit id="35">
<source>This value should be a valid number.</source> <source>This value should be a valid number.</source>
@ -140,15 +140,15 @@
</trans-unit> </trans-unit>
<trans-unit id="38"> <trans-unit id="38">
<source>This value is not a valid language.</source> <source>This value is not a valid language.</source>
<target>Deze waarde representeert geen geldige taal.</target> <target>Deze waarde is geen geldige taal.</target>
</trans-unit> </trans-unit>
<trans-unit id="39"> <trans-unit id="39">
<source>This value is not a valid locale.</source> <source>This value is not a valid locale.</source>
<target>Deze waarde representeert geen geldige lokalisering.</target> <target>Deze waarde is geen geldige locale.</target>
</trans-unit> </trans-unit>
<trans-unit id="40"> <trans-unit id="40">
<source>This value is not a valid country.</source> <source>This value is not a valid country.</source>
<target>Deze waarde representeert geen geldig land.</target> <target>Deze waarde is geen geldig land.</target>
</trans-unit> </trans-unit>
<trans-unit id="41"> <trans-unit id="41">
<source>This value is already used.</source> <source>This value is already used.</source>
@ -184,7 +184,7 @@
</trans-unit> </trans-unit>
<trans-unit id="49"> <trans-unit id="49">
<source>The file was only partially uploaded.</source> <source>The file was only partially uploaded.</source>
<target>Het bestand is niet geheel geüpload.</target> <target>Het bestand is slechts gedeeltelijk geüpload.</target>
</trans-unit> </trans-unit>
<trans-unit id="50"> <trans-unit id="50">
<source>No file was uploaded.</source> <source>No file was uploaded.</source>

View File

@ -54,18 +54,6 @@ class HtmlDumper extends CliDumper
$this->dumpId = 'sf-dump-'.mt_rand(); $this->dumpId = 'sf-dump-'.mt_rand();
} }
/**
* {@inheritdoc}
*/
public function setOutput($output)
{
if ($output !== $prev = parent::setOutput($output)) {
$this->headerIsDumped = false;
}
return $prev;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -111,7 +99,7 @@ class HtmlDumper extends CliDumper
*/ */
protected function getDumpHeader() protected function getDumpHeader()
{ {
$this->headerIsDumped = true; $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;
if (null !== $this->dumpHeader) { if (null !== $this->dumpHeader) {
return $this->dumpHeader; return $this->dumpHeader;
@ -433,7 +421,7 @@ EOHTML;
if (-1 === $this->lastDepth) { if (-1 === $this->lastDepth) {
$this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line;
} }
if (!$this->headerIsDumped) { if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) {
$this->line = $this->getDumpHeader().$this->line; $this->line = $this->getDumpHeader().$this->line;
} }

View File

@ -122,8 +122,7 @@ EOTXT
$data = $cloner->cloneVar($var); $data = $cloner->cloneVar($var);
$out = fopen('php://memory', 'r+b'); $out = fopen('php://memory', 'r+b');
$dumper->dump($data, $out); $dumper->dump($data, $out);
rewind($out); $out = stream_get_contents($out, -1, 0);
$out = stream_get_contents($out);
$this->assertStringMatchesFormat( $this->assertStringMatchesFormat(
<<<EOTXT <<<EOTXT
@ -132,7 +131,32 @@ EOTXT
EOTXT EOTXT
, ,
$out
);
}
public function testAppend()
{
$out = fopen('php://memory', 'r+b');
$dumper = new HtmlDumper();
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$cloner = new VarCloner();
$dumper->dump($cloner->cloneVar(123), $out);
$dumper->dump($cloner->cloneVar(456), $out);
$out = stream_get_contents($out, -1, 0);
$this->assertSame(<<<'EOTXT'
<foo></foo><bar><span class=sf-dump-num>123</span>
</bar>
<bar><span class=sf-dump-num>456</span>
</bar>
EOTXT
,
$out $out
); );
} }