2000000,//bytes ~ 1MB 'allowed_file_types' => [ 'image' => ['.jpeg', '.jpg', '.gif', '.png'], 'script' => ['.css', '.js'], ], //2. uploaded by client 'uid' => '', 'token' => '', 'time' => '', 'item_type' => 'product', // product|article|media 'target_path' => '', 'upload_method' => 'content', //file || content "file_content" => "", //only needed if upload_method = content "file_name" => "",//only needed if upload_method = content ]; private $tmp_file_prop = null; //array, temporary file's props in tmp folder private $accepted_file_input_names = [ "file", // //"qqfile", //name use for files uploaded through FineUploader library ]; private $user_tmp_dir = ''; public function __construct(){ } protected function createToken($id, $time) { return sha1(join("-", [$id , $time , static::SECURITY_SALT])); } public function start() { $this->_get_post_info(); if(!$this->validateUser()) { return $this->set_return_result('error', 'User failed to verify', []); } //receive files $file = $this->receive_file(); //return file-location to upload API to return to client application return $this->set_return_result('success', 'Upload succeeded', $file ); } protected function validateUser() { return ($this->config['token'] == $this->createToken($this->config['uid'], $this->config['time'])); } protected function _get_post_info() { $expected_keys = [ 'upload_method', 'file_name', 'target_path', 'file_content', 'uid', 'time', 'token', 'item_type', ]; foreach ($expected_keys as $key) { $this->config[$key] = isset($_POST[$key]) ? $_POST[$key] : null; } } //return boolean protected function remove_tmp_file() { $this->deleteDirectory($this->user_tmp_dir); return true; } //return boolean protected function validate_file() { //$this->tmp_file_prop //validate file size if($this->config['max_file_size'] && $this->config['max_file_size'] < $this->tmp_file_prop['size']) { return 'Size too large'; } //validate allowed extension: allow image but upload .docx files $has_extension = false; $file_type = ''; foreach ( $this->config['allowed_file_types'] as $group => $group_ext ) { if(in_array( $this->tmp_file_prop['ext'], $group_ext )) { $has_extension = true; $file_type = $group; break ; } } if( ! $has_extension ) return "File type not allowed"; //validate claimed extension: claim image but not actual image $full_file_path = $this->tmp_file_prop['tmp_location'] . DIRECTORY_SEPARATOR . $this->tmp_file_prop['name']; if( $file_type == 'image' && ! v::image()->validate( $full_file_path )) { return "Not actual image"; } return ''; } //return mixed : array $tmp_file_prop or false protected function receive_file() { //check upload method if( $this->config['upload_method'] == 'content' ) { //upload by content - file created on server $file_ext = $this->get_ext($this->config['file_name']); $upload_folder = $this->create_upload_folder() ; if( ! $this->move_file($upload_folder)) { return false; } return [ "ext" => $file_ext, "name" => $this->config['file_name'], "folder" => $upload_folder, "size" => strlen($this->config['file_content']), ]; } else { //upload by file //get list of files to be uploaded $file_uploaded = null; foreach ($this->accepted_file_input_names as $_name) { if(isset($_FILES[$_name])) { $file_uploaded = $_FILES[$_name]; break; } } if ( ! $file_uploaded ) return false; //if file name too long, then error if ( strlen($file_uploaded['name']) > 225 ) return false; //move file to tmp folder $file_ext = $this->get_ext($file_uploaded['name']); $upload_folder = $this->create_upload_folder() ; if( ! $this->move_file($upload_folder) ){ return false; } return [ "ext" => $file_ext, "name" => $this->config['file_name'], "folder" => $upload_folder, "size" => $file_uploaded['size'], ]; } } protected function set_return_result($status = 'success', $message = 'Upload success', $content = []) { return [ "status" => $status, "message" => $message, "files" => $content, ]; } //create upload folder for user: use current date, user id or app id based on $this->config private function create_upload_folder() { //$build_folder = [$this->tmp_dir]; /*$user_id = (isset($this->config['user_id'])) ? intval($this->config['user_id']) : null; if($user_id) { $build_folder[] = $user_id; } $build_folder[] = date("Ymd");*/ //rebuild upload folder $build_folder = array_filter(explode("/", $this->config['target_path'])); $folder = join("/", $build_folder); $this->createDir($folder); return $folder; } //get a file's extension private function get_ext($fileName){ return strtolower(strrchr($fileName, '.')); } // check if find is image private function isImage($fileName) { return (in_array( $this->get_ext($fileName), ['.jpeg', '.jpg', '.gif', '.png'] )); } //for security reason: only allow a-z0-9_- in file name (no other dot . except for the extension) //example: bad-file.php.js -> bad-filephp.js private function rename_file($uploaded_file_name, $ext) { $new_name = preg_replace("/[^a-z0-9_\-]/i", "", str_replace( strrchr($uploaded_file_name, '.'), "", $uploaded_file_name ) ) . $ext; return strtolower($new_name); } private function move_file($folder) { $new_file = $folder . '/' . $this->config['file_name']; if( $this->config['upload_method'] == 'content' ) { // image upload by content if( $this->isImage($new_file) ) { //Store in the filesystem. $fp = fopen($new_file, "w+"); $status = fwrite($fp, $this->config['file_content']); fclose($fp); return ($status !== false); } // file upload else if( file_put_contents($new_file, $this->config['file_content'])){ return true; } } return false; } protected function createDir($path, $folder_permission = 0750){ if(file_exists($path)) { return true; } return mkdir($path, $folder_permission, true); } protected function deleteDirectory($dirPath) { if (!is_dir($dirPath)) { return false; } $objects = scandir($dirPath); foreach ($objects as $object) { if ($object != "." && $object !="..") { if (filetype($dirPath . DIRECTORY_SEPARATOR . $object) == "dir") { $this->deleteDirectory($dirPath . DIRECTORY_SEPARATOR . $object); } else { unlink($dirPath . DIRECTORY_SEPARATOR . $object); } } } return rmdir($dirPath); } }