<?php
/**
* Makes the PHP upload quick and easy. Allows the upload of multiple files.
* @package Sami's upload class
* @version $Id: samis_captcha.php v.1.0 2012 $
* @author Samuil Banti
* @copyright (C) 2012 - Samuil Banti
* @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
*/
class samis_upload {
    
    private $error = false;
    private $errors = array();
    
    // Custom error codes:
    private $no_upload_error    = 'There was no file to upload';
    private $small_size_error   = 'The file size is too small!';
    private $large_size_error   = 'The file size is too big!';
    private $file_type_error    = 'The file type is not alowed!';
    private $location_errror    = 'The upload path do not exist!';
    private $file_exists_error  = 'The file already exists!';
    private $many_files_error   = 'The maximum number of images allowed at a time is ';
    
    // PHP error codes:
    private $code_1_error = 'The uploaded file exceeds the upload_max_filesize directive in php.ini';
    private $code_2_error = 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.';
    private $code_3_error = 'The uploaded file was only partially uploaded.';
    private $code_4_error = 'No file was uploaded.';
    private $code_6_error = 'Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3.';
    private $code_7_error = 'Failed to write file to disk. Introduced in PHP 5.1.0.';
    private $code_8_error = 'A PHP extension stopped the file upload. PHP does not provide a way to ascertain which extension caused the file upload to stop; examining the list of loaded extensions with phpinfo() may help. Introduced in PHP 5.2.0.';
    
    /**
    * Set errors texts 
    * @param string $error_key - The error key prefix.
    * @param string $error_text - The text of the error.
    */
    public function set_error_text($error_key, $error_text)
    {
	if(isset($this->{$error_key.'_error'})) {
	    $this->{$error_key.'_error'} = $error_text;
	}
    }
    
    /**
    * Get the error in case of single file upload.
    */
    public function get_error()
    {
	return $this->error;
    }
    
    /**
    * Get the errors in case of multiple file upload.
    */
    public function get_errors()
    {
	return $this->errors;
    }
    
    /**
     * The function uploads multiple files to desired location if the files meets the requirements.
     * 
     * @param string $location - The location to upload to
     * @param string $input_name - The name of the file input in the HTML form 
     * @param string $file_name - The new name of the uploaden files folowed by the file number. If "false" the original file name will be used
     * @param boolean $if_replace - Wether or not to replace the file if exists
     * @param int $min_size - The minimum file size in bytes. If "false" it will not be used.
     * @param int $max_size - The maximum file size in bytes. If "false" it will not be used.
     * @param array $alowed_extentions - The alowed file extentions. If it's empty no file extention type check will be done.
     * @param array $alowed_mime_types - The alowed MIME types. If it's empty no MIME type check will be done.
     * @param int $max_files_number - The mahimum number of files that could be uploaded at a time
     * @return true on success false on falure
     */
    public function upload_multiple_files($location, $input_name, $file_name = false, $if_replace = false, $min_size = false, $max_size = false, $alowed_extentions = array(), $alowed_mime_types = array(), $max_files_number = false) 
    {
        if( empty( $_FILES[$input_name] ) ) {
            $this->error = $this->no_upload_error;
            return false;
        }

        $all_files = array();
        foreach($_FILES[$input_name]['name'] as $k => $v) {
            $all_files[] = array(
                'name'      => $v,
                'type'      => $_FILES[$input_name]['type'][$k],
                'tmp_name'  => $_FILES[$input_name]['tmp_name'][$k],
                'error'     => $_FILES[$input_name]['error'][$k],
                'size'      => $_FILES[$input_name]['size'][$k]
            );
        }
        
        if($max_files_number && $max_files_number < count($all_files)) {
            $this->errors['max_files_number'] = $this->many_files_error.$max_files_number;
            return false;
        }
        
        foreach( $all_files as $k => $f) {
            $extention = $this->file_extention_to_lower($f['name']);
            $this_file_name = empty($file_name) ? false : $file_name.'('.$k.').'.$extention;
            $upload_result = $this->upload_file($location, $input_name, $this_file_name, $if_replace, $min_size, $max_size, $alowed_extentions, $alowed_mime_types, $f);
            if( empty($upload_result) ) {
                $this->errors[ $f['name'] ] = $this->error.' - '.$f['name'];
            }
        }
        return true;
    }
    
    /**
     * The function uploads file to desired location if the file meets the requirements.
     * 
     * @param string $location - The location to upload in
     * @param string $input_name - The name of the file input in the HTML form 
     * @param string $file_name - The new name of the uploaden file. If "false" the original file name will be used
     * @param boolean $if_replace - Wether or not to replace the file if exists
     * @param int $min_size - The minimum file size in bytes. If "false" it will not be used.
     * @param int $max_size - The maximum file size in bytes. If "false" it will not be used.
     * @param array $alowed_extentions - The alowed file extentions. If it's empty no file extention type check will be done. For example: array('jpg', 'png')
     * @param array $alowed_mime_types - The alowed MIME types. If it's empty no MIME type check will be done.
     * @return true on success false on falure
     */
    public function upload_single_file($location, $input_name, $file_name = false, $if_replace = false, $min_size = false, $max_size = false, $alowed_extentions = array(), $alowed_mime_types = array()) 
    {
        if( empty( $_FILES[$input_name] ) ) {
            $this->error = $this->no_upload_error;
            return false;
        }
        $extention = $this->file_extention_to_lower($_FILES[$input_name]['name']);
        $file_name = empty($file_name) ? false : $file_name.'.'.$extention;
        $f = $_FILES[$input_name];
        $upload_result = $this->upload_file($location, $input_name, $file_name, $if_replace, $min_size, $max_size, $alowed_extentions, $alowed_mime_types, $f);
        return $upload_result;
    }
    
    /**
     * The function uploads file to desired location if the file meets the requirements.
     * 
     * WARNING: This function is private and dont need to be called out of the class.
     * 
     * @param string $location - The location to upload to
     * @param string $input_name - The name of the file input in the HTML form 
     * @param string $file_name - The new name of the uploaden file. If "false" the original file name will be used
     * @param boolean $if_replace - Wether or not to replace the file if exists
     * @param int $min_size - The minimum file size in bytes. If "false" it will not be used. The following would be treated as megabites: "2MB".
     * @param int $max_size - The maximum file size in bytes. If "false" it will not be used. The following would be treated as megabites: "2MB".
     * @param array $alowed_extentions - The alowed file extentions. If it's empty no file extention type check will be done.
     * @param array $alowed_mime_types - The alowed MIME types. If it's empty no MIME type check will be done.
     * @param file resourse $file - The file resours to upload from
     * @return true on success false on falure
     */
    private function upload_file($location, $input_name, $file_name = false, $if_replace = false, $min_size = false, $max_size = false, $alowed_extentions = array(), $alowed_mime_types = array(), $file) 
    {
        $location = rtrim($location, '/ ').'/';
        
        $max_size = strpos($max_size, 'MB') ? (1048576 * trim($max_size, ' MB')) : $max_size;
        $min_size = strpos($max_size, 'MB') ? (1048576 * trim($min_size, ' MB')) : $min_size;
        
        if( empty( $file ) ) {
            $this->error = $this->no_upload_error;
            return false;
        }
        
        $file_name = empty($file_name) ? $file['name'] : $file_name;
        $file_name = str_replace('%20', ' ', $file_name);
        
        if ($file['error'] > 0) {
            $this->error = $this->error_code_handler($file['error']);
            return false;
        }
        
        if( !is_dir($location) ) {
            $this->error = $this->location_errror;
            return false;
        }
        
        if( !$if_replace && file_exists($location.$file_name) ) {
            $this->error = $this->file_exists_error;
            return false;
        }
        
        if( !empty($alowed_extentions) ) {
            $extention = $this->file_extention_to_lower($location.$file_name);
            if( !in_array($extention, $alowed_extentions) ) {
                $this->error = $this->file_type_error;
                return false;
            }
        }
        
        if( !empty($alowed_mime_types) ) {
            if( !in_array($file['type'], $alowed_mime_types) ) {
                $this->error = $this->file_type_error;
                return false;
            }
        }
        
        if( $min_size && $file['size'] < $min_size ) {
            $this->error = $this->small_size_error;
            return false;
        }
        
        if( $max_size && $file['size'] > $max_size ) {
            $this->error = $this->large_size_error;
            return false;
        }
        
        move_uploaded_file($file['tmp_name'], $location.$file_name);
        return $location.$file_name;
    }
    
    /**
     * The function terurns file extention of a given path in lowercase charecters.
     * @param string $path - The file path
     * @return string $extention - The file extention
     */
    private function file_extention_to_lower($path) 
    {
        $path = trim($path);
        $path_info = pathinfo($path);
        $extention = empty($path_info['extension']) ? false : strtolower($path_info['extension']);
        return($extention);
    }
    
    /**
    * Set the error text if it's PHP upload error.
    * @param int $error_code - The PHP upload error code.
    * @return string - The description of the error.
    */
    private function error_code_handler($error_code) 
    {
        $error_code = "code_{$error_code}_error";
        return $this->{$error_code};
    }
    
}