diff --git a/README.md b/README.md index aa1b8aa..4a2afdb 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,34 @@ When the HTML form is submitted, the server-side PHP code can validate and uploa $errors = $file->getErrors(); } +If you need to use translated messages, you can pass a translation object to the objects, like this: + + addValidations(array( + // Ensure file is of type "image/png" + new \Upload\Validation\Mimetype('image/png', $translation), + + // Ensure file has correct extension + new \Upload\Validation\Extension('png', $translation), + + // Ensure file is no larger than 5M (use "B", "K", M", or "G") + new \Upload\Validation\Size('5M', 0, $translation) + )); + + // Try to upload file + try { + // Success! + $file->upload(); + } catch (\Exception $e) { + // Fail! + $errors = $file->getErrors(); + } + ## How to Install Install composer in your project: diff --git a/src/Upload/File.php b/src/Upload/File.php index 4cacd2b..8167a19 100644 --- a/src/Upload/File.php +++ b/src/Upload/File.php @@ -126,6 +126,12 @@ class File extends \SplFileInfo */ protected $errorCode; + /** + * Translation object + * @var \Upload\Translation + */ + protected $translation; + /** * Constructor * @param string $key The file's key in $_FILES superglobal @@ -133,10 +139,13 @@ class File extends \SplFileInfo * @throws \Upload\Exception\UploadException If file uploads are disabled in the php.ini file * @throws \InvalidArgumentException If $_FILES key does not exist */ - public function __construct($key, \Upload\Storage\Base $storage) + public function __construct($key, \Upload\Storage\Base $storage, \Upload\Translation $translation = null) { + $this->translation = $translation; + if (!isset($_FILES[$key])) { - throw new \InvalidArgumentException("Cannot find uploaded file identified by key: $key"); + $message = $this->getTranslation("Cannot find uploaded file identified by key: %s", array($key)); + throw new \InvalidArgumentException($message); } $this->storage = $storage; $this->validations = array(); @@ -247,12 +256,12 @@ public function validate() { // Validate is uploaded OK if ($this->isOk() === false) { - $this->errors[] = self::$errorCodeMessages[$this->errorCode]; + $this->errors[] = $this->getTranslation(self::$errorCodeMessages[$this->errorCode]); } // Validate is uploaded file if ($this->isUploadedFile() === false) { - $this->errors[] = 'The uploaded file was not sent with a POST request'; + $this->errors[] = $this->getTranslation('The uploaded file was not sent with a POST request'); } // User validations @@ -299,7 +308,7 @@ public function addError($error) public function upload($newName = null) { if ($this->validate() === false) { - throw new \Upload\Exception\UploadException('File validation failed'); + throw new \Upload\Exception\UploadException($this->getTranslation('File validation failed')); } // Update the name, leaving out the extension @@ -354,4 +363,19 @@ public static function humanReadableToBytes($input) return $number; } + + /** + * Get the translated message + * @param string $key Message key + * @param array $params List of positional placeholders values + * @return string + */ + protected function getTranslation($key, array $params = array()) + { + if ($this->translation === null) { + return vsprintf($key, $params); + } + + return $this->translation->getMessage($key, $params); + } } diff --git a/src/Upload/Language/en.php b/src/Upload/Language/en.php new file mode 100644 index 0000000..0668140 --- /dev/null +++ b/src/Upload/Language/en.php @@ -0,0 +1,22 @@ + 'The uploaded file exceeds the upload_max_filesize directive in php.ini', + 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form' => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', + 'The uploaded file was only partially uploaded' => 'The uploaded file was only partially uploaded', + 'No file was uploaded' => 'No file was uploaded', + 'Missing a temporary folder' => 'Missing a temporary folder', + 'Failed to write file to disk' => 'Failed to write file to disk', + 'A PHP extension stopped the file upload' => 'A PHP extension stopped the file upload', + 'Cannot find uploaded file identified by key: %s' => 'Cannot find uploaded file identified by key: %s', + 'The uploaded file was not sent with a POST request' => 'The uploaded file was not sent with a POST request', + 'File validation failed' => 'File validation failed', + 'Invalid file extension. Must be one of: %s' => 'Invalid file extension. Must be one of: %s', + 'Invalid mimetype' => 'Invalid mimetype', + 'Invalid file size' => 'Invalid file size', + 'File size is too small' => 'File size is too small', + 'File size is too large' => 'File size is too large', + 'Directory does not exist' => 'Directory does not exist', + 'Directory is not writable' => 'Directory is not writable', + 'File already exists' => 'File already exists' +); diff --git a/src/Upload/Language/pt-BR.php b/src/Upload/Language/pt-BR.php new file mode 100644 index 0000000..eb8ecb9 --- /dev/null +++ b/src/Upload/Language/pt-BR.php @@ -0,0 +1,22 @@ + 'O tamanho do arquivo enviado excede o tamanho permitido pela diretiva upload_max_filesize do php.ini', + 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form' => 'O tamanho do arquivo enviado excede a diretiva MAX_FILE_SIZE especificada no formulário HTML', + 'The uploaded file was only partially uploaded' => 'Arquivo parcialmente enviado', + 'No file was uploaded' => 'Nenhum arquivo enviado', + 'Missing a temporary folder' => 'Nenhum diretório temporário encontrado', + 'Failed to write file to disk' => 'Falha ao gravar o arquivo em disco', + 'A PHP extension stopped the file upload' => 'Uma extensão PHP parou o envio do arquivo', + 'Cannot find uploaded file identified by key: %s' => 'Não foi possível encontrar o arquivo enviado com a chave: %s', + 'The uploaded file was not sent with a POST request' => 'O arquivo não foi enviado utilizando o método POST', + 'File validation failed' => 'Validação do arquivo falhou', + 'Invalid file extension. Must be one of: %s' => 'Extensão de arquivo inválida. Deve ser uma das: %s', + 'Invalid mimetype' => 'Mimetype inválido', + 'Invalid file size' => 'Tamanho de arquivo inválido', + 'File size is too small' => 'Tamanho do arquivo muito pequeno', + 'File size is too large' => 'Tamanho do arquivo muito grande', + 'Directory does not exist' => 'Diretório não existe', + 'Directory is not writable' => 'Diretório sem permissão de escrita', + 'File already exists' => 'Arquivo já existe' +); diff --git a/src/Upload/Storage/Base.php b/src/Upload/Storage/Base.php index 0e50dfc..517d55e 100644 --- a/src/Upload/Storage/Base.php +++ b/src/Upload/Storage/Base.php @@ -41,5 +41,26 @@ */ abstract class Base { + /** + * Translation object + * @var \Upload\Translation + */ + protected $translation; + + /** + * Get the translated message + * @param string $key Message key + * @param array $params List of positional placeholders values + * @return string + */ + protected function getTranslation($key, array $params = array()) + { + if ($this->translation === null) { + return vsprintf($key, $params); + } + + return $this->translation->getMessage($key, $params); + } + abstract public function upload(\Upload\File $file, $newName = null); } diff --git a/src/Upload/Storage/FileSystem.php b/src/Upload/Storage/FileSystem.php index 9c96b9b..6cd8046 100644 --- a/src/Upload/Storage/FileSystem.php +++ b/src/Upload/Storage/FileSystem.php @@ -60,13 +60,15 @@ class FileSystem extends \Upload\Storage\Base * @throws \InvalidArgumentException If directory does not exist * @throws \InvalidArgumentException If directory is not writable */ - public function __construct($directory, $overwrite = false) + public function __construct($directory, $overwrite = false, \Upload\Translation $translation = null) { + $this->translation = $translation; + if (!is_dir($directory)) { - throw new \InvalidArgumentException('Directory does not exist'); + throw new \InvalidArgumentException($this->getTranslation('Directory does not exist')); } if (!is_writable($directory)) { - throw new \InvalidArgumentException('Directory is not writable'); + throw new \InvalidArgumentException($this->getTranslation('Directory is not writable')); } $this->directory = rtrim($directory, '/') . DIRECTORY_SEPARATOR; $this->overwrite = $overwrite; @@ -90,8 +92,8 @@ public function upload(\Upload\File $file, $newName = null) $newFile = $this->directory . $fileName; if ($this->overwrite === false && file_exists($newFile)) { - $file->addError('File already exists'); - throw new \Upload\Exception\UploadException('File already exists'); + $file->addError($this->getTranslation('File already exists')); + throw new \Upload\Exception\UploadException($this->getTranslation('File already exists')); } return $this->moveUploadedFile($file->getPathname(), $newFile); diff --git a/src/Upload/Translation.php b/src/Upload/Translation.php new file mode 100644 index 0000000..d6c2caa --- /dev/null +++ b/src/Upload/Translation.php @@ -0,0 +1,90 @@ + + * @copyright 2012 Josh Lockhart + * @link http://www.joshlockhart.com + * @version 1.0.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Upload; + +/** + * Translation + * + * This class provides the implementation for translated messages. + * + * @author Ramiro Varandas Jr + * @package Upload + */ +class Translation +{ + /** + * Translation messages + * @var array + */ + protected $messages; + + /** + * Constructor + * @param string $language Language prefix (i.e.: en, pt, pt-BR) + * @throws \InvalidArgumentException If translation file does not exist + */ + public function __construct($language) + { + $this->loadTranslationFile($language); + } + + /** + * Load a translation file containing the messages used by the library + * @param string $language + * @throws \InvalidArgumentException If translation file does not exist + */ + protected function loadTranslationFile($language) + { + $filename = __DIR__ . "/Language/${language}.php"; + + if (file_exists($filename) === false) { + throw new \InvalidArgumentException("Cannot find translation file for language: $language"); + } + + $this->messages = require $filename; + } + + /** + * Get a translation message + * @param string $key Message key + * @param array $params Array containing positional placeholders values + * @return string + */ + public function getMessage($key, $params = array()) + { + if (array_key_exists($key, $this->messages) === true) { + return vsprintf($this->messages[$key], $params); + } else { + return $key; + } + } + +} diff --git a/src/Upload/Validation/Base.php b/src/Upload/Validation/Base.php index 17ef794..07fa48a 100644 --- a/src/Upload/Validation/Base.php +++ b/src/Upload/Validation/Base.php @@ -48,6 +48,12 @@ abstract class Base */ protected $message; + /** + * Translation object + * @var \Upload\Translation + */ + protected $translation; + /** * Set error message * @param string $message @@ -66,6 +72,21 @@ public function getMessage() return $this->message; } + /** + * Get the translated message + * @param string $key Message key + * @param array $params List of positional placeholders values + * @return string + */ + protected function getTranslation($key, array $params = array()) + { + if ($this->translation === null) { + return vsprintf($key, $params); + } + + return $this->translation->getMessage($key, $params); + } + /** * Validate file * @param \Upload\File $file diff --git a/src/Upload/Validation/Extension.php b/src/Upload/Validation/Extension.php index 1a9d8fb..4d53afe 100644 --- a/src/Upload/Validation/Extension.php +++ b/src/Upload/Validation/Extension.php @@ -62,8 +62,10 @@ class Extension extends \Upload\Validation\Base * @example new \Upload\Validation\Extension(array('png','jpg','gif')) * @example new \Upload\Validation\Extension('png') */ - public function __construct($allowedExtensions) + public function __construct($allowedExtensions, \Upload\Translation $translation = null) { + $this->translation = $translation; + if (is_string($allowedExtensions)) { $allowedExtensions = array($allowedExtensions); } @@ -86,7 +88,8 @@ public function validate(\Upload\File $file) $isValid = true; if (!in_array($fileExtension, $this->allowedExtensions)) { - $this->setMessage(sprintf($this->message, implode(', ', $this->allowedExtensions))); + $extensions = array(implode(', ', $this->allowedExtensions)); + $this->setMessage($this->getTranslation($this->message, $extensions)); $isValid = false; } diff --git a/src/Upload/Validation/Mimetype.php b/src/Upload/Validation/Mimetype.php index 98b6c31..d24306a 100644 --- a/src/Upload/Validation/Mimetype.php +++ b/src/Upload/Validation/Mimetype.php @@ -57,8 +57,11 @@ class Mimetype extends \Upload\Validation\Base * Constructor * @param array $mimetypes Array of valid mimetypes */ - public function __construct($mimetypes) + public function __construct($mimetypes, \Upload\Translation $translation = null) { + $this->translation = $translation; + $this->message = $this->getTranslation($this->message); + if (!is_array($mimetypes)) { $mimetypes = array($mimetypes); } diff --git a/src/Upload/Validation/Size.php b/src/Upload/Validation/Size.php index 6e4ccbe..d0ebfed 100644 --- a/src/Upload/Validation/Size.php +++ b/src/Upload/Validation/Size.php @@ -66,8 +66,11 @@ class Size extends \Upload\Validation\Base * @param int $maxSize Maximum acceptable file size in bytes (inclusive) * @param int $minSize Minimum acceptable file size in bytes (inclusive) */ - public function __construct($maxSize, $minSize = 0) + public function __construct($maxSize, $minSize = 0, \Upload\Translation $translation = null) { + $this->translation = $translation; + $this->message = $this->getTranslation($this->message); + if (is_string($maxSize)) { $maxSize = \Upload\File::humanReadableToBytes($maxSize); } @@ -90,12 +93,12 @@ public function validate(\Upload\File $file) $isValid = true; if ($fileSize < $this->minSize) { - $this->setMessage('File size is too small'); + $this->setMessage($this->getTranslation('File size is too small')); $isValid = false; } if ($fileSize > $this->maxSize) { - $this->setMessage('File size is too large'); + $this->setMessage($this->getTranslation('File size is too large')); $isValid = false; } diff --git a/tests/FileTest.php b/tests/FileTest.php index 9ee0331..6ea9626 100644 --- a/tests/FileTest.php +++ b/tests/FileTest.php @@ -58,6 +58,11 @@ public function getNewFile() return $file; } + public function getNewTranslation() + { + return new \Upload\Translation('pt-BR'); + } + /******************************************************************************** * Tests *******************************************************************************/ @@ -185,4 +190,33 @@ public function testParsesHumanFriendlyFileSizes() $this->assertEquals(107374182400, \Upload\File::humanReadableToBytes('100G')); $this->assertEquals(100, \Upload\File::humanReadableToBytes('100F')); // <-- Unrecognized. Assume bytes. } + + public function testErrorCodeUsingTranslation() + { + $_FILES['foo']['error'] = 4; + + // Prepare storage + $this->storage = $this->getMock( + '\Upload\Storage\FileSystem', + array('upload'), + array($this->assetsDirectory) + ); + $this->storage->expects($this->any()) + ->method('upload') + ->will($this->returnValue(true)); + + // Prepare file + $file = $this->getMock( + '\Upload\File', + array('isUploadedFile'), + array('foo', $this->storage, $this->getNewTranslation()) + ); + $file->expects($this->any()) + ->method('isUploadedFile') + ->will($this->returnValue(true)); + + $this->assertFalse($file->validate()); + $this->assertCount(1, $file->getErrors()); + $this->assertContains('Nenhum arquivo enviado', $file->getErrors()); + } } diff --git a/tests/Storage/FileSystemTest.php b/tests/Storage/FileSystemTest.php index c7089f0..a9aabdd 100644 --- a/tests/Storage/FileSystemTest.php +++ b/tests/Storage/FileSystemTest.php @@ -9,6 +9,8 @@ public function setUp() // Path to test assets $this->assetsDirectory = dirname(__DIR__) . '/assets'; + $this->translation = new \Upload\Translation('pt-BR'); + // Reset $_FILES superglobal $_FILES['foo'] = array( 'name' => 'foo.txt', @@ -76,4 +78,28 @@ public function testWillOverwriteFile() ->will($this->returnValue(true)); $this->assertTrue($file->upload()); } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInstantiationWithInvalidDirectoryUsingTranslation() + { + $storage = $this->getMock( + '\Upload\Storage\FileSystem', + array('upload'), + array('/foo', false, $this->translation) + ); + } + + /** + * Test won't overwrite existing file + * @expectedException \RuntimeException + * @expectedExceptionMessage Validação do arquivo falhou + */ + public function testWillNotOverwriteFileUsingTranslation() + { + $storage = new \Upload\Storage\FileSystem($this->assetsDirectory, false, $this->translation); + $file = new \Upload\File('foo', $storage, $this->translation); + $file->upload(); + } } diff --git a/tests/TranslationTest.php b/tests/TranslationTest.php new file mode 100644 index 0000000..7371ff2 --- /dev/null +++ b/tests/TranslationTest.php @@ -0,0 +1,42 @@ +translation = new \Upload\Translation('pt-BR'); + } + + /******************************************************************************** + * Tests + *******************************************************************************/ + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructionWithInvalidLanguage() + { + $translation = new \Upload\Translation('xy'); + } + + public function testConstructionWithValidLanguage() + { + $this->assertInstanceOf('Upload\Translation', $this->translation); + } + + public function testTranslatedMessageWithoutPlaceholders() + { + $this->assertEquals('Arquivo já existe', $this->translation->getMessage('File already exists')); + } + + public function testTranslatedMessageWithPlaceholders() + { + $this->assertEquals( + 'Não foi possível encontrar o arquivo enviado com a chave: image', + $this->translation->getMessage('Cannot find uploaded file identified by key: %s', array('image')) + ); + } +} diff --git a/tests/Validation/ExtensionTest.php b/tests/Validation/ExtensionTest.php index 617bb4e..b9f7212 100644 --- a/tests/Validation/ExtensionTest.php +++ b/tests/Validation/ExtensionTest.php @@ -19,6 +19,8 @@ public function setUp() ->method('upload') ->will($this->returnValue(true)); + $this->translation = new \Upload\Translation('pt-BR'); + // Reset $_FILES superglobal $_FILES['foo'] = array( 'name' => 'foo.txt', @@ -40,4 +42,11 @@ public function testInvalidExtension() $validation = new \Upload\Validation\Extension('csv'); $this->assertFalse($validation->validate($file)); } + + public function testValidExtensionUsingTranslation() + { + $file = new \Upload\File('foo', $this->storage); + $validation = new \Upload\Validation\Extension('txt', $this->translation); + $this->assertTrue($validation->validate($file)); + } } diff --git a/tests/Validation/MimetypeTest.php b/tests/Validation/MimetypeTest.php index 675eebb..2e2e745 100644 --- a/tests/Validation/MimetypeTest.php +++ b/tests/Validation/MimetypeTest.php @@ -19,6 +19,8 @@ public function setUp() ->method('upload') ->will($this->returnValue(true)); + $this->translation = new \Upload\Translation('pt-BR'); + // Reset $_FILES superglobal $_FILES['foo'] = array( 'name' => 'foo.txt', @@ -44,4 +46,15 @@ public function testInvalidMimetype() )); $this->assertFalse($validation->validate($file)); } + + public function testInvalidMimetypeUsingTranslation() + { + $file = new \Upload\File('foo', $this->storage, $this->translation); + $validation = new \Upload\Validation\Mimetype(array( + 'image/png' + ), $this->translation); + + $this->assertFalse($validation->validate($file)); + $this->assertEquals('Mimetype inválido', $validation->getMessage()); + } } diff --git a/tests/Validation/SizeTest.php b/tests/Validation/SizeTest.php index d4aa89e..a49d57c 100644 --- a/tests/Validation/SizeTest.php +++ b/tests/Validation/SizeTest.php @@ -19,6 +19,8 @@ public function setUp() ->method('upload') ->will($this->returnValue(true)); + $this->translation = new \Upload\Translation('pt-BR'); + // Reset $_FILES superglobal $_FILES['foo'] = array( 'name' => 'foo.txt', @@ -54,4 +56,12 @@ public function testInvalidFileSizeWithHumanReadableArgument() $validation = new \Upload\Validation\Size('400B'); $this->assertFalse($validation->validate($file)); } + + public function testInvalidFileSizeUsingTranslation() + { + $file = new \Upload\File('foo', $this->storage, $this->translation); + $validation = new \Upload\Validation\Size(400, 0, $this->translation); + $this->assertFalse($validation->validate($file)); + $this->assertEquals('Tamanho do arquivo muito grande', $validation->getMessage()); + } }