Created
June 8, 2010 09:26
-
-
Save romac/429809 to your computer and use it in GitHub Desktop.
RRFileUploader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* Copyright (c) 2010 Romain Ruetschi <[email protected]> | |
* | |
* 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. | |
*/ | |
/** | |
* Source file containing class RRFileUploader. | |
* | |
* @package RRFileUploader | |
* @license http://opensource.org/licenses/mit-license.php MIT License | |
* @author Romain Ruetschi <[email protected]> | |
* @version 0.1 | |
* @see RRFileUploader | |
*/ | |
/** | |
* Class RRFileUploader. | |
* | |
* @todo Description for class RRFileUploader. | |
* | |
* @package RRFileUploader | |
* @license http://opensource.org/licenses/mit-license.php MIT license | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
class RRFileUploader | |
{ | |
/** | |
* Valid Mime Types | |
* | |
* @var string | |
*/ | |
protected $_validMimeTypes = array(); | |
/** | |
* Valid Extensions | |
* | |
* @var string | |
*/ | |
protected $_validExtensions = array(); | |
/** | |
* The path to the directory where the files will be moved. | |
* | |
* @var string | |
*/ | |
protected $_destinationPath = ''; | |
/** | |
* Non Moved Files | |
* | |
* @var array | |
*/ | |
protected $_nonMovedFiles = array(); | |
/** | |
* Error Messages | |
* | |
* @var array | |
*/ | |
protected $_errorMessages = array( | |
UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the "upload_max_filesize" directive in php.ini', | |
UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the "MAX_FILE_SIZE" directive that was specified in the HTML form', | |
UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded', | |
UPLOAD_ERR_NO_FILE => 'No file was uploaded.', | |
UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder.', | |
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.', | |
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.' | |
); | |
/** | |
* Create a new RRFileUploader instance. | |
* | |
* @param string $destinationPath The path to the directory where the files will be moved. | |
* @param array $validMimeTypes The valid MIME types. | |
* @param array $validExtensions The valid extensions. | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function __construct | |
( | |
$destinationPath, | |
array $validMimeTypes = array(), | |
array $validExtensions = array() | |
) | |
{ | |
if( is_array( $destinationPath ) ) | |
{ | |
$this->setOptions( $destinationPath ); | |
} | |
else | |
{ | |
$this->setDestinationPath( $destinationPath ); | |
if( $validMimeTypes ) | |
{ | |
$this->setValidMimeTypes( $validMimeTypes ); | |
} | |
if( $validExtensions ) | |
{ | |
$this->setValidExtensions( $validExtensions ); | |
} | |
} | |
} | |
/** | |
* Set all options at once. | |
* You must supply an array to this function in which | |
* each key represents a property, associated | |
* with the value to set to this property. | |
* Then the setter corresponding to this property will be called. | |
* | |
* @example | |
* <code> | |
* $uploader->setOptions( array( | |
* 'destinationPath' => __DIR__, | |
* 'validMimeTypes' => array( 'image/jpeg' ) | |
* ) ); | |
* </code> | |
* | |
* Will call: $uploader->setDestinationPath( __DIR__ ); | |
* and: $uploader->setValidMimeTypes( array( 'image/jpeg' ) ); | |
* | |
* @param array $options The options to set. | |
* @return RRFileUploader | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function setOptions( array $options ) | |
{ | |
foreach( $options as $property => $value ) | |
{ | |
$methodName = 'set' . ucfirst( $property ); | |
if( method_exists( $this, $methodName ) ) | |
{ | |
$this->$methodName( $value ); | |
} | |
} | |
return $this; | |
} | |
/** | |
* Move all the uploaded files present in the superglobal $_FILES array. | |
* | |
* @see moveUploadedFile() | |
* @return array An array of all the uploaded files new filenames. | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function moveAllUploadedFiles() | |
{ | |
$normalizedFilesArray = $this->_normalizeFilesArray( $_FILES ); | |
$uploadedFiles = $this->_nonMovedFiles = array(); | |
foreach( $normalizedFilesArray as $file ) | |
{ | |
try | |
{ | |
$uploadedFiles[] = $this->moveUploadedFile( $file ); | |
} | |
catch( Exception $e ) | |
{ | |
$this->_nonMovedFiles[] = array( | |
'error' => $e, | |
'file' => $file | |
); | |
} | |
} | |
return $uploadedFiles; | |
} | |
/** | |
* Move an uploaded file to a directory. | |
* | |
* @see _normalizeFilesArray() | |
* @throws Exception If the file cannot be moved successfully or is not valid. | |
* @param array $file An entry of the normalized $_FILES array. | |
* @return string The new path to the moved file. | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
function moveUploadedFile( array $file ) | |
{ | |
$errors = array(); | |
if( $file[ 'error' ] !== UPLOAD_ERR_OK ) | |
{ | |
$this->_throwException( | |
$this->_getFileUploadErrorMessage( $file[ 'error' ] ) | |
); | |
} | |
if( $file[ 'type' ] && !$this->_isValidMimeType( $file[ 'type' ] ) ) | |
{ | |
$this->_throwException( | |
'Invalid MIME type.' | |
); | |
} | |
if( !$this->_hasValidExtension( $file[ 'name' ] ) ) | |
{ | |
$this->_throwException( | |
'Invalid extension.' | |
); | |
} | |
$filePathName = $this->getDestinationPath() . $file[ 'name' ]; | |
if( file_exists( $filePathName ) ) | |
{ | |
$filePathName = $this->_findFreeFileName( $file ); | |
} | |
if( !move_uploaded_file( $file[ 'tmp_name' ], $filePathName ) ) | |
{ | |
$this->_throwException( | |
'Moving ' . $file[ 'tmp_name' ] . | |
' to ' . $filePathName . ' failed. ' . | |
'Please check if the webserver has write permission in "' . | |
$this->getDestinationPath() . '".' | |
); | |
} | |
return $filePathName; | |
} | |
/** | |
* If the destination file already exists this method try to find | |
* a new destination filename which is not used in the destination directory. | |
* It does so by appending _1, _2, _3, etc. right before the extension. | |
* | |
* @param string $file The normalized file array. | |
* @return string The new filename. | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
protected function _findFreeFileName( array $file ) | |
{ | |
$folder = $this->getDestinationPath(); | |
$fileName = pathinfo( $file[ 'name' ], PATHINFO_FILENAME ); | |
$fileExt = pathinfo( $file[ 'name' ], PATHINFO_EXTENSION ); | |
$filePathName = $folder . $fileName . '.' . $fileExt; | |
$i = 0; | |
while( file_exists( $filePathName ) ) | |
{ | |
$filePathName = $folder . $fileName . '_' . ++$i . '.' . $fileExt; | |
} | |
return $filePathName; | |
} | |
/** | |
* Return an array of the valid MIME types. | |
* | |
* @return array | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function getValidMimeTypes() | |
{ | |
return $this->_validMimeTypes; | |
} | |
/** | |
* Set the valid MIME types. | |
* | |
* @param array $validMimeTypes The valid MIME types. | |
* @return RRFileUploader | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function setValidMimeTypes( array $validMimeTypes ) | |
{ | |
$this->_validMimeTypes = $validMimeTypes; | |
return $this; | |
} | |
/** | |
* Return an array of the valid file extensions. | |
* | |
* @return array | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function getValidExtensions() | |
{ | |
return $this->_validExtensions; | |
} | |
/** | |
* Set the valid file extensions. | |
* | |
* @param array $validExtensions THe valid file extensions. | |
* @return RRFileUploader | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function setValidExtensions( array $validExtensions ) | |
{ | |
$this->_validExtensions = $validExtensions; | |
return $this; | |
} | |
/** | |
* Get the destination path. | |
* | |
* @return string The destination path. | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function getDestinationPath() | |
{ | |
return $this->_destinationPath; | |
} | |
/** | |
* Set the destination path. | |
* | |
* @see _normalizePath() | |
* @param string $destinationPath The destination path. | |
* @return RRFileUploader | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function setDestinationPath( $destinationPath ) | |
{ | |
if( | |
!is_string( $destinationPath ) | |
|| | |
( | |
is_object( $destinationPath ) | |
&& | |
method_exists( $destinationPath, '__toString' ) | |
) | |
) | |
{ | |
throw new InvalidArgumentException( | |
'$destinationPath must be either a string or an object ' . | |
'with a __toString method. ' . | |
ucfirst( gettype( $destinationPath ) ) . ' supplied.' | |
); | |
} | |
$this->_destinationPath = $this->_normalizePath( $destinationPath ); | |
return $this; | |
} | |
/** | |
* Return an array of all the non-moved files. | |
* Each item in this array it a normalized $_FILES item. | |
* | |
* @see _normalizeFilesArray() | |
* @return array. | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function getNonMovedFiles() | |
{ | |
return $this->_nonMovedFiles; | |
} | |
/** | |
* Check if some files were not moved. | |
* | |
* @return boolean | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
public function hasNonMovedFiles() | |
{ | |
return count( $this->_nonMovedFiles > 0 ); | |
} | |
/** | |
* Check if the supplied MIME type is a valid one. | |
* | |
* @param string $type The MIME type to check. | |
* @return boolean | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
protected function _isValidMimeType( $type ) | |
{ | |
return in_array( $type, $this->_validMimeTypes, TRUE ); | |
} | |
/** | |
* Check if the supplied file has a valid extension. | |
* | |
* @param string $name The extension to check | |
* @return boolean | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
protected function _hasValidExtension( $name ) | |
{ | |
$extension = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) ); | |
return in_array( $extension, $this->_validExtensions, TRUE ); | |
} | |
/** | |
* Get the error message corresponding to an error code. | |
* | |
* @param string $errorCode The error code. | |
* @return string The corresponding error message. | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
protected function _getFileUploadErrorMessage( $errorCode ) | |
{ | |
if( isset( $this->_errorMessages[ $errorCode ] ) ) | |
{ | |
return $this->_errorMessages[ $errorCode ]; | |
} | |
return 'Unknow error.'; | |
} | |
/** | |
* Normalize a path according to the OS PHP is running on. | |
* This method also append a directory separator at the end of the | |
* path. | |
* | |
* @param string $path The path to normalize. | |
* @return string The normalized path. | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
protected function _normalizePath( $path ) | |
{ | |
$path = trim( $path ); | |
$path = strtr( $path, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR ); | |
$path = rtrim( $path, DIRECTORY_SEPARATOR ) | |
. DIRECTORY_SEPARATOR; | |
return $path; | |
} | |
/** | |
* Normalize a $_FILES-like array. | |
* Instead of having a key at the first level for each property of | |
* the uploaded files, this function returns an array with an entry | |
* per uploaded file which correponds to an array with all the property | |
* in it. | |
* | |
* @param array $files A $_FILES-like array. | |
* @return array The normalized array. | |
* @author Romain Ruetschi <[email protected]> | |
*/ | |
protected function _normalizeFilesArray( array $files ) | |
{ | |
if( !$files ) | |
{ | |
return array(); | |
} | |
$normalized = array(); | |
$properties = array_keys( $files ); | |
foreach( $files[ 'error' ] as $key => $error ) | |
{ | |
if( $error === UPLOAD_ERR_NO_FILE ) | |
{ | |
continue; | |
} | |
$normalized[ $key ] = array(); | |
foreach( $properties as $property ) | |
{ | |
$normalized[ $key ][ $property ] = $files[ $property ][ $key ]; | |
} | |
} | |
return $normalized; | |
} | |
/** | |
* Throw a pre-formatted exception. | |
* | |
* @param string $reason The reason of the failure. | |
* @return void | |
* @author Romain Ruetschi <[email protected]> | |
* @throws Exception | |
*/ | |
protected function _throwException( $reason ) | |
{ | |
throw new Exception( | |
'Unable to move the supplied file. Reason: ' . $reason | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment