diff --git a/src/Symfony/Component/Form/DataProcessor/FileUploader.php b/src/Symfony/Component/Form/DataProcessor/FileUploader.php new file mode 100644 index 0000000000..1ca9fd93c8 --- /dev/null +++ b/src/Symfony/Component/Form/DataProcessor/FileUploader.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\DataProcessor; + +use Symfony\Component\Form\FieldInterface; +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\File\TemporaryStorage; + +/** + * Moves uploaded files to a temporary location + * + * @author Bernhard Schussek + */ +class FileUploader implements DataProcessorInterface +{ + private $field; + + private $storage; + + public function __construct(FieldInterface $field, TemporaryStorage $storage) + { + $this->field = $field; + $this->storage = $storage; + } + + public function processData($data) + { + // Newly uploaded file + if ($data['file'] instanceof UploadedFile && $data['file']->isValid()) { + $data['token'] = (string)rand(100000, 999999); + $directory = $this->storage->getTempDir($data['token']); + + if (!file_exists($directory)) { + // Recursively create directories + mkdir($directory, 0777, true); + } + + $data['file']->move($directory); + $data['name'] = $data['file']->getName(); + } + + // Existing uploaded file + if (!$data['file'] && $data['token'] && $data['name']) { + $path = $this->storage->getTempDir($data['token']) . DIRECTORY_SEPARATOR . $data ['name']; + + if (file_exists($path)) { + $data['file'] = new File($path); + } + } + + // Clear other fields if we still don't have a file, but keep + // possible existing files of the field + if (!$data['file']) { + $currentData = $this->field->getNormalizedData(); + $data['file'] = $currentData['file']; + $data['token'] = ''; + $data['name'] = ''; + } + + return $data; + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Form/FileField.php b/src/Symfony/Component/Form/FileField.php deleted file mode 100644 index 5169d99a16..0000000000 --- a/src/Symfony/Component/Form/FileField.php +++ /dev/null @@ -1,205 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form; - -use Symfony\Component\HttpFoundation\File\File; -use Symfony\Component\Form\Exception\FormException; - -/** - * A file field to upload files. - */ -class FileField extends Form -{ - /** - * Whether the size of the uploaded file exceeds the upload_max_filesize - * directive in php.ini - * @var Boolean - */ - protected $iniSizeExceeded = false; - - /** - * Whether the size of the uploaded file exceeds the MAX_FILE_SIZE - * directive specified in the HTML form - * @var Boolean - */ - protected $formSizeExceeded = false; - - /** - * Whether the file was completely uploaded - * @var Boolean - */ - protected $uploadComplete = true; - - /** - * {@inheritDoc} - */ - protected function configure() - { - $this->addRequiredOption('secret'); - $this->addOption('tmp_dir', sys_get_temp_dir()); - - parent::configure(); - - $this->add(new Field('file')); - $this->add(new HiddenField('token')); - $this->add(new HiddenField('original_name')); - } - - /** - * Moves the file to a temporary location to prevent its deletion when - * the PHP process dies - * - * This way the file can survive if the form does not validate and is - * resubmitted. - * - * @see Symfony\Component\Form\Form::preprocessData() - */ - protected function preprocessData($data) - { - if ($data['file']) { - switch ($data['file']->getError()) { - case UPLOAD_ERR_INI_SIZE: - $this->iniSizeExceeded = true; - break; - case UPLOAD_ERR_FORM_SIZE: - $this->formSizeExceeded = true; - break; - case UPLOAD_ERR_PARTIAL: - $this->uploadComplete = false; - break; - case UPLOAD_ERR_NO_TMP_DIR: - throw new FormException('Could not upload a file because a temporary directory is missing (UPLOAD_ERR_NO_TMP_DIR)'); - case UPLOAD_ERR_CANT_WRITE: - throw new FormException('Could not write file to disk (UPLOAD_ERR_CANT_WRITE)'); - case UPLOAD_ERR_EXTENSION: - throw new FormException('A PHP extension stopped the file upload (UPLOAD_ERR_EXTENSION)'); - case UPLOAD_ERR_OK: - default: - $data['file']->move($this->getTmpDir()); - $data['file']->rename($this->getTmpName($data['token'])); - $data['original_name'] = $data['file']->getOriginalName(); - $data['file'] = ''; - break; - } - } - - return $data; - } - - /** - * Turns a file path into an array of field values - * - * @see Symfony\Component\Form\Field::normalize() - */ - protected function normalize($path) - { - return array( - 'file' => '', - 'token' => rand(100000, 999999), - 'original_name' => '', - ); - } - - /** - * Turns an array of field values into a file path - * - * @see Symfony\Component\Form\Field::denormalize() - */ - protected function denormalize($data) - { - $path = $this->getTmpPath($data['token']); - - return file_exists($path) ? $path : $this->getData(); - } - - /** - * Returns the absolute temporary path to the uploaded file - * - * @param string $token - */ - protected function getTmpPath($token) - { - return $this->getTmpDir() . DIRECTORY_SEPARATOR . $this->getTmpName($token); - } - - /** - * Returns the temporary directory where files are stored - * - * @param string $token - */ - protected function getTmpDir() - { - return realpath($this->getOption('tmp_dir')); - } - - /** - * Returns the temporary file name for the given token - * - * @param string $token - */ - protected function getTmpName($token) - { - return md5(session_id() . $this->getOption('secret') . $token); - } - - /** - * Returns the original name of the uploaded file - * - * @return string - */ - public function getOriginalName() - { - $data = $this->getNormalizedData(); - - return $data['original_name']; - } - - /** - * {@inheritDoc} - */ - public function isMultipart() - { - return true; - } - - /** - * Returns true if the size of the uploaded file exceeds the - * upload_max_filesize directive in php.ini - * - * @return Boolean - */ - public function isIniSizeExceeded() - { - return $this->iniSizeExceeded; - } - - /** - * Returns true if the size of the uploaded file exceeds the - * MAX_FILE_SIZE directive specified in the HTML form - * - * @return Boolean - */ - public function isFormSizeExceeded() - { - return $this->formSizeExceeded; - } - - /** - * Returns true if the file was completely uploaded - * - * @return Boolean - */ - public function isUploadComplete() - { - return $this->uploadComplete; - } -} diff --git a/src/Symfony/Component/Form/FormFactory.php b/src/Symfony/Component/Form/FormFactory.php index 0ae6438db3..6dcb82ecda 100644 --- a/src/Symfony/Component/Form/FormFactory.php +++ b/src/Symfony/Component/Form/FormFactory.php @@ -21,6 +21,7 @@ use Symfony\Component\Form\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\DataProcessor\RadioToArrayConverter; use Symfony\Component\Form\DataProcessor\UrlProtocolFixer; use Symfony\Component\Form\DataProcessor\CollectionMerger; +use Symfony\Component\Form\DataProcessor\FileUploader; use Symfony\Component\Form\FieldFactory\FieldFactoryInterface; use Symfony\Component\Form\Renderer\DefaultRenderer; use Symfony\Component\Form\Renderer\Theme\ThemeInterface; @@ -51,8 +52,11 @@ use Symfony\Component\Form\ValueTransformer\ValueTransformerChain; use Symfony\Component\Form\ValueTransformer\ArrayToChoicesTransformer; use Symfony\Component\Form\ValueTransformer\ArrayToPartsTransformer; use Symfony\Component\Form\ValueTransformer\ValueToDuplicatesTransformer; +use Symfony\Component\Form\ValueTransformer\FileToArrayTransformer; +use Symfony\Component\Form\ValueTransformer\FileToStringTransformer; use Symfony\Component\Validator\ValidatorInterface; use Symfony\Component\Locale\Locale; +use Symfony\Component\HttpFoundation\File\TemporaryStorage; class FormFactory { @@ -64,12 +68,19 @@ class FormFactory private $fieldFactory; - public function __construct(ThemeInterface $theme, CsrfProviderInterface $csrfProvider, ValidatorInterface $validator, FieldFactoryInterface $fieldFactory) + private $storage; + + public function __construct(ThemeInterface $theme, + CsrfProviderInterface $csrfProvider, + ValidatorInterface $validator, + FieldFactoryInterface $fieldFactory, + TemporaryStorage $storage) { $this->theme = $theme; $this->csrfProvider = $csrfProvider; $this->validator = $validator; $this->fieldFactory = $fieldFactory; + $this->storage = $storage; } protected function getTheme() @@ -866,4 +877,30 @@ class FormFactory ->add($firstChild) ->add($secondChild); } + + public function getFileField($key, array $options = array()) + { + $options = array_merge(array( + 'template' => 'file', + 'type' => 'string', + ), $options); + + $field = $this->getForm($key, $options); + + if ($options['type'] === 'string') { + $field->setNormalizationTransformer(new ValueTransformerChain(array( + new ReversedTransformer(new FileToStringTransformer()), + new FileToArrayTransformer(), + ))); + } else { + $field->setNormalizationTransformer(new FileToArrayTransformer()); + } + + return $field + ->setDataPreprocessor(new FileUploader($field, $this->storage)) + ->setData(null) // FIXME + ->add($this->getField('file', array('type' => 'file'))) + ->add($this->getHiddenField('token')) + ->add($this->getHiddenField('name')); + } } \ No newline at end of file diff --git a/src/Symfony/Component/Form/ValueTransformer/FileToArrayTransformer.php b/src/Symfony/Component/Form/ValueTransformer/FileToArrayTransformer.php new file mode 100644 index 0000000000..c3a765c500 --- /dev/null +++ b/src/Symfony/Component/Form/ValueTransformer/FileToArrayTransformer.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ValueTransformer; + +use Symfony\Component\Form\ValueTransformer\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\HttpFoundation\File\File; + +/** + * @author Bernhard Schussek + */ +class FileToArrayTransformer implements ValueTransformerInterface +{ + public function transform($file) + { + if (null === $file || '' === $file) { + return array( + 'file' => '', + 'token' => '', + 'name' => '', + ); + } + + if (!$file instanceof File) { + throw new UnexpectedTypeException($file, 'Symfony\Component\HttpFoundation\File\File'); + } + + return array( + 'file' => $file, + 'token' => '', + 'name' => '', + ); + } + + public function reverseTransform($array) + { + if (null === $array || '' === $array || array() === $array) { + return null; + } + + if (!is_array($array)) { + throw new UnexpectedTypeException($array, 'array'); + } + + if (!array_key_exists('file', $array)) { + throw new TransformationFailedException('The key "file" is missing'); + } + + if (!empty($array['file']) && !$array['file'] instanceof File) { + throw new TransformationFailedException('The key "file" should be empty or instance of File'); + } + + return $array['file']; + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Form/ValueTransformer/FileToStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/FileToStringTransformer.php new file mode 100644 index 0000000000..65d8be86b0 --- /dev/null +++ b/src/Symfony/Component/Form/ValueTransformer/FileToStringTransformer.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ValueTransformer; + +use Symfony\Component\Form\ValueTransformer\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\HttpFoundation\File\File; + +/** + * @author Bernhard Schussek + */ +class FileToStringTransformer implements ValueTransformerInterface +{ + public function transform($file) + { + if (null === $file || '' === $file) { + return ''; + } + + if (!$file instanceof File) { + throw new UnexpectedTypeException($file, 'Symfony\Component\HttpFoundation\File\File'); + } + + return $file->getPath(); + } + + public function reverseTransform($path) + { + if (null === $path || '' === $path) { + return null; + } + + if (!is_string($path)) { + throw new UnexpectedTypeException($path, 'string'); + } + + return new File($path); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php b/src/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000000..3e2f8267a6 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +class UnexpectedTypeException extends FileException +{ + public function __construct($value, $expectedType) + { + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value))); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/File/Exception/UploadException.php b/src/Symfony/Component/HttpFoundation/File/Exception/UploadException.php new file mode 100644 index 0000000000..351c3603e8 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred during file upload + * + * @author Bernhard Schussek + */ +class UploadException extends FileException +{ +} \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/File/SessionBasedTemporaryStorage.php b/src/Symfony/Component/HttpFoundation/File/SessionBasedTemporaryStorage.php new file mode 100644 index 0000000000..1f270b7a1f --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/File/SessionBasedTemporaryStorage.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\Session; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class SessionBasedTemporaryStorage extends TemporaryStorage +{ + public function __construct(Session $session, $directory, $secret, $nestingLevels = 3) + { + parent::__construct($directory, $secret, $nestingLevels); + + $this->session = $session; + } + + protected function generateHashInfo($token) + { + $this->session->start(); + + return $this->session->getId() . parent::generateHashInfo($token); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/File/TemporaryStorage.php b/src/Symfony/Component/HttpFoundation/File/TemporaryStorage.php new file mode 100644 index 0000000000..a4362fbfff --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/File/TemporaryStorage.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + */ +class TemporaryStorage +{ + private $directory; + + private $secret; + + private $nestingLevels; + + public function __construct($directory, $secret, $nestingLevels = 3) + { + $this->directory = realpath($directory); + $this->secret = $secret; + $this->nestingLevels = $nestingLevels; + } + + protected function generateHashInfo($token) + { + return $this->secret . $token; + } + + protected function generateHash($token) + { + return md5($this->generateHashInfo($token)); + } + + public function getTempDir($token) + { + if (!is_string($token)) { + throw new UnexpectedTypeException($token, 'string'); + } + + $hash = $this->generateHash($token); + + if (strlen($hash) < $this->nestingLevels) { + throw new FileException(sprintf( + 'For %s nesting levels the hash must have at least %s characters', $this->nestingLevels, $this->nestingLevels)); + } + + $directory = $this->directory; + + for ($i = 0; $i < ($this->nestingLevels - 1); ++$i) { + $directory .= DIRECTORY_SEPARATOR . $hash{$i}; + } + + return $directory . DIRECTORY_SEPARATOR . substr($hash, $i); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php index ceaf1205d2..1348e48a01 100644 --- a/src/Symfony/Component/HttpFoundation/File/UploadedFile.php +++ b/src/Symfony/Component/HttpFoundation/File/UploadedFile.php @@ -2,7 +2,7 @@ /* * This file is part of the Symfony package. - * + * * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpFoundation\File; use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\UploadException; /** * A file uploaded through a form. @@ -68,14 +69,24 @@ class UploadedFile extends File throw new FileException(sprintf('Unable to create UploadedFile because "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path'))); } - if (is_file($path)) { - $this->path = realpath($path); - } - if (null === $error) { $error = UPLOAD_ERR_OK; } + switch ($error) { + // TESTME + case UPLOAD_ERR_NO_TMP_DIR: + throw new UploadException('Could not upload a file because a temporary directory is missing (UPLOAD_ERR_NO_TMP_DIR)'); + case UPLOAD_ERR_CANT_WRITE: + throw new UploadException('Could not write file to disk (UPLOAD_ERR_CANT_WRITE)'); + case UPLOAD_ERR_EXTENSION: + throw new UploadException('A PHP extension stopped the file upload (UPLOAD_ERR_EXTENSION)'); + } + + if (is_file($path)) { + $this->path = realpath($path); + } + if (null === $mimeType) { $mimeType = 'application/octet-stream'; } @@ -123,6 +134,48 @@ class UploadedFile extends File return $this->error; } + /** + * Returns whether the file was uploaded succesfully. + * + * @return Boolean True if no error occurred during uploading + */ + public function isValid() + { + return $this->error === UPLOAD_ERR_OK; + } + + /** + * Returns true if the size of the uploaded file exceeds the + * upload_max_filesize directive in php.ini + * + * @return Boolean + */ + protected function isIniSizeExceeded() + { + return $this->error === UPLOAD_ERR_INI_SIZE; + } + + /** + * Returns true if the size of the uploaded file exceeds the + * MAX_FILE_SIZE directive specified in the HTML form + * + * @return Boolean + */ + protected function isFormSizeExceeded() + { + return $this->error === UPLOAD_ERR_FORM_SIZE; + } + + /** + * Returns true if the file was completely uploaded + * + * @return Boolean + */ + protected function isUploadComplete() + { + return $this->error !== UPLOAD_ERR_PARTIAL; + } + /** * @inheritDoc */ diff --git a/tests/Symfony/Tests/Component/Form/FileFieldTest.php b/tests/Symfony/Tests/Component/Form/FileFieldTest.php index 61b0900294..695743d335 100644 --- a/tests/Symfony/Tests/Component/Form/FileFieldTest.php +++ b/tests/Symfony/Tests/Component/Form/FileFieldTest.php @@ -11,10 +11,12 @@ namespace Symfony\Tests\Component\Form; +require_once __DIR__.'/TestCase.php'; + use Symfony\Component\Form\FileField; use Symfony\Component\HttpFoundation\File\File; -class FileFieldTest extends \PHPUnit_Framework_TestCase +class FileFieldTest extends TestCase { public static $tmpFiles = array(); @@ -24,18 +26,14 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase public static function setUpBeforeClass() { - self::$tmpDir = sys_get_temp_dir(); - - // we need a session ID - @session_start(); + self::$tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'symfony-test'; } protected function setUp() { - $this->field = new FileField('file', array( - 'secret' => '$secret$', - 'tmp_dir' => self::$tmpDir, - )); + parent::setUp(); + + $this->field = $this->factory->getFileField('file'); } protected function tearDown() @@ -54,180 +52,119 @@ class FileFieldTest extends \PHPUnit_Framework_TestCase public function testSubmitUploadsNewFiles() { - $tmpDir = realpath(self::$tmpDir); - $tmpName = md5(session_id() . '$secret$' . '12345'); - $tmpPath = $tmpDir . DIRECTORY_SEPARATOR . $tmpName; - $that = $this; + $tmpDir = self::$tmpDir; + $generatedToken = ''; - $file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false); + $this->storage->expects($this->atLeastOnce()) + ->method('getTempDir') + ->will($this->returnCallback(function ($token) use ($tmpDir, &$generatedToken) { + // A 6-digit token is generated by FileUploader and passed + // to getTempDir() + $generatedToken = $token; + + return $tmpDir; + })); + + $file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') + ->disableOriginalConstructor() + ->getMock(); $file->expects($this->once()) ->method('move') ->with($this->equalTo($tmpDir)); - $file->expects($this->once()) - ->method('rename') - ->with($this->equalTo($tmpName)) - ->will($this->returnCallback(function ($directory) use ($that, $tmpPath) { - $that->createTmpFile($tmpPath); - })); $file->expects($this->any()) - ->method('getOriginalName') + ->method('isValid') + ->will($this->returnValue(true)); + $file->expects($this->any()) + ->method('getName') ->will($this->returnValue('original_name.jpg')); + $file->expects($this->any()) + ->method('getPath') + ->will($this->returnValue($tmpDir.'/original_name.jpg')); $this->field->submit(array( 'file' => $file, - 'token' => '12345', - 'original_name' => '', + 'token' => '', + 'name' => '', )); - $this->assertTrue(file_exists($tmpPath)); + $this->assertRegExp('/^\d{6}$/', $generatedToken); $this->assertEquals(array( - 'file' => '', - 'token' => '12345', - 'original_name' => 'original_name.jpg', + 'file' => $file, + 'token' => $generatedToken, + 'name' => 'original_name.jpg', ), $this->field->getDisplayedData()); - $this->assertEquals($tmpPath, $this->field->getData()); - $this->assertFalse($this->field->isIniSizeExceeded()); - $this->assertFalse($this->field->isFormSizeExceeded()); - $this->assertTrue($this->field->isUploadComplete()); + $this->assertEquals($tmpDir.'/original_name.jpg', $this->field->getData()); } public function testSubmitKeepsUploadedFilesOnErrors() { - $tmpPath = self::$tmpDir . '/' . md5(session_id() . '$secret$' . '12345'); + $tmpDir = self::$tmpDir; + $tmpPath = $tmpDir . DIRECTORY_SEPARATOR . 'original_name.jpg'; $this->createTmpFile($tmpPath); + $this->storage->expects($this->atLeastOnce()) + ->method('getTempDir') + ->with($this->equalTo('123456')) + ->will($this->returnValue($tmpDir)); + $this->field->submit(array( - 'file' => '', - 'token' => '12345', - 'original_name' => 'original_name.jpg', + 'file' => null, + 'token' => '123456', + 'name' => 'original_name.jpg', )); $this->assertTrue(file_exists($tmpPath)); + + $file = new File($tmpPath); + + $this->assertEquals(array( + 'file' => $file, + 'token' => '123456', + 'name' => 'original_name.jpg', + ), $this->field->getDisplayedData()); + $this->assertEquals($tmpPath, $this->field->getData()); + } + + public function testSubmitEmpty() + { + $this->storage->expects($this->never()) + ->method('getTempDir'); + + $this->field->submit(array( + 'file' => '', + 'token' => '', + 'name' => '', + )); + $this->assertEquals(array( 'file' => '', - 'token' => '12345', - 'original_name' => 'original_name.jpg', + 'token' => '', + 'name' => '', ), $this->field->getDisplayedData()); - $this->assertEquals(realpath($tmpPath), realpath($this->field->getData())); + $this->assertEquals(null, $this->field->getData()); } - public function testSubmitKeepsOldFileIfNotOverwritten() + public function testSubmitEmptyKeepsExistingFiles() { - $oldPath = tempnam(sys_get_temp_dir(), 'FileFieldTest'); - $this->createTmpFile($oldPath); + $tmpPath = self::$tmpDir . DIRECTORY_SEPARATOR . 'original_name.jpg'; + $this->createTmpFile($tmpPath); + $file = new File($tmpPath); - $this->field->setData($oldPath); - - $this->assertEquals($oldPath, $this->field->getData()); + $this->storage->expects($this->never()) + ->method('getTempDir'); + $this->field->setData($tmpPath); $this->field->submit(array( 'file' => '', - 'token' => '12345', - 'original_name' => '', + 'token' => '', + 'name' => '', )); - $this->assertTrue(file_exists($oldPath)); $this->assertEquals(array( - 'file' => '', - 'token' => '12345', - 'original_name' => '', + 'file' => $file, + 'token' => '', + 'name' => '', ), $this->field->getDisplayedData()); - $this->assertEquals($oldPath, $this->field->getData()); - } - - public function testSubmitHandlesUploadErrIniSize() - { - $file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false); - $file->expects($this->any()) - ->method('getError') - ->will($this->returnValue(UPLOAD_ERR_INI_SIZE)); - - $this->field->submit(array( - 'file' => $file, - 'token' => '12345', - 'original_name' => '' - )); - - $this->assertTrue($this->field->isIniSizeExceeded()); - } - - public function testSubmitHandlesUploadErrFormSize() - { - $file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false); - $file->expects($this->any()) - ->method('getError') - ->will($this->returnValue(UPLOAD_ERR_FORM_SIZE)); - - $this->field->submit(array( - 'file' => $file, - 'token' => '12345', - 'original_name' => '' - )); - - $this->assertTrue($this->field->isFormSizeExceeded()); - } - - public function testSubmitHandlesUploadErrPartial() - { - $file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false); - $file->expects($this->any()) - ->method('getError') - ->will($this->returnValue(UPLOAD_ERR_PARTIAL)); - - $this->field->submit(array( - 'file' => $file, - 'token' => '12345', - 'original_name' => '' - )); - - $this->assertFalse($this->field->isUploadComplete()); - } - - public function testSubmitThrowsExceptionOnUploadErrNoTmpDir() - { - $file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false); - $file->expects($this->any()) - ->method('getError') - ->will($this->returnValue(UPLOAD_ERR_NO_TMP_DIR)); - - $this->setExpectedException('Symfony\Component\Form\Exception\FormException'); - - $this->field->submit(array( - 'file' => $file, - 'token' => '12345', - 'original_name' => '' - )); - } - - public function testSubmitThrowsExceptionOnUploadErrCantWrite() - { - $file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false); - $file->expects($this->any()) - ->method('getError') - ->will($this->returnValue(UPLOAD_ERR_CANT_WRITE)); - - $this->setExpectedException('Symfony\Component\Form\Exception\FormException'); - - $this->field->submit(array( - 'file' => $file, - 'token' => '12345', - 'original_name' => '' - )); - } - - public function testSubmitThrowsExceptionOnUploadErrExtension() - { - $file = $this->getMock('Symfony\Component\HttpFoundation\File\UploadedFile', array(), array(), '', false); - $file->expects($this->any()) - ->method('getError') - ->will($this->returnValue(UPLOAD_ERR_EXTENSION)); - - $this->setExpectedException('Symfony\Component\Form\Exception\FormException'); - - $this->field->submit(array( - 'file' => $file, - 'token' => '12345', - 'original_name' => '' - )); + $this->assertEquals($tmpPath, $this->field->getData()); } } \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Form/TestCase.php b/tests/Symfony/Tests/Component/Form/TestCase.php index 8c09ad5a48..70ccf6842a 100644 --- a/tests/Symfony/Tests/Component/Form/TestCase.php +++ b/tests/Symfony/Tests/Component/Form/TestCase.php @@ -23,6 +23,8 @@ class TestCase extends \PHPUnit_Framework_TestCase protected $fieldFactory; + protected $storage; + protected $factory; protected function setUp() @@ -31,6 +33,10 @@ class TestCase extends \PHPUnit_Framework_TestCase $this->csrfProvider = $this->getMock('Symfony\Component\Form\CsrfProvider\CsrfProviderInterface'); $this->validator = $this->getMock('Symfony\Component\Validator\ValidatorInterface'); $this->fieldFactory = $this->getMock('Symfony\Component\Form\FieldFactory\FieldFactoryInterface'); - $this->factory = new FormFactory($this->theme, $this->csrfProvider, $this->validator, $this->fieldFactory); + $this->storage = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\TemporaryStorage') + ->disableOriginalConstructor() + ->getMock(); + $this->factory = new FormFactory($this->theme, $this->csrfProvider, + $this->validator, $this->fieldFactory, $this->storage); } } \ No newline at end of file