├── .gitignore ├── .gitmodules ├── package.json ├── Helper ├── ServerExceptions.php ├── Post.class.php └── Transfer.class.php ├── Vagrantfile ├── README.md ├── config.php ├── config_doka.php ├── submit.php ├── index.php └── FilePond.class.php /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant/ 2 | tmp/ 3 | uploads/ 4 | _todo.md 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Doka"] 2 | path = Doka 3 | url = https://github.com/pqina/doka-php 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filepond-php-server", 3 | "version": "2.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": {} 13 | } 14 | -------------------------------------------------------------------------------- /Helper/ServerExceptions.php: -------------------------------------------------------------------------------- 1 | getMessage() . '" line="' . $ex->getLine() . '"'); 11 | ob_end_clean(); 12 | http_response_code(500); 13 | } 14 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | 3 | config.vm.box = "scotch/box" 4 | config.vm.network "private_network", ip: "192.168.33.11" 5 | config.vm.hostname = "scotchbox" 6 | config.vm.synced_folder ".", "/var/www", :mount_options => ["dmode=777", "fmode=666"] 7 | 8 | config.vm.provision "shell", inline: <<-SHELL 9 | mv /var/www /var/www/public_html 10 | sudo sed -i s,/var/www/public,/var/www,g /etc/apache2/sites-available/000-default.conf 11 | sudo sed -i s,/var/www/public,/var/www,g /etc/apache2/sites-available/scotchbox.local.conf 12 | sudo service apache2 restart 13 | SHELL 14 | 15 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FilePond PHP Server API 2 | 3 | A PHP server to handle [FilePond](https://pqina.nl/filepond/) file uploads and [Doka](https://pqina.nl/doka/) image transforms. 4 | 5 | **⚠️ If you're using a version of the PHP API released before 2019-09-19, please update to the latest version to fix a potential security vulnerability** 6 | 7 | ## Instructions 8 | 9 | Comment this line in both the `index.php` and `submit.php` files to prevent posting to the server from other domains. 10 | 11 | ```php 12 | header('Access-Control-Allow-Origin: *'); 13 | ``` 14 | 15 | 16 | ## Targets 17 | 18 | The `tmp` and `upload` file paths can be configured in the `config.php` file. 19 | 20 | ```php 21 | 22 | // where to get files from, can also be an array of fields 23 | const ENTRY_FIELD = 'filepond'; 24 | 25 | // where to write files to 26 | const TRANSFER_DIR = 'tmp'; 27 | const UPLOAD_DIR = 'uploads'; 28 | const VARIANTS_DIR = 'variants'; 29 | 30 | // name to use for the file metadata object 31 | const METADATA_FILENAME = '.metadata'; 32 | 33 | ``` 34 | 35 | 36 | ## Image Transforms 37 | 38 | To do image transforms on the server instead of the client we can uncomment the `require_once('config_doka.php')` line. 39 | 40 | Transform instructions found in the `.metadata` file are now automatically applied to the first file in the upload list (when it's transfered from the transfer dir to the upload dir). 41 | 42 | 43 | ## Example 44 | 45 | See [FilePond PHP Boiler Plate](https://github.com/pqina/filepond-boilerplate-php) for an example implementation. 46 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | $tmpName ) { 20 | $file = array( 21 | 'tmp_name' => $value['tmp_name'][$index], 22 | 'name' => $value['name'][$index], 23 | 'size' => $value['size'][$index], 24 | 'error' => $value['error'][$index], 25 | 'type' => $value['type'][$index] 26 | ); 27 | array_push( $results, $file ); 28 | } 29 | return $results; 30 | } 31 | return to_array($value); 32 | } 33 | 34 | function is_encoded_file($value) { 35 | $data = @json_decode($value); 36 | return is_object($data); 37 | } 38 | 39 | class Post { 40 | 41 | private $format; 42 | private $values; 43 | 44 | public function __construct($entry) { 45 | 46 | if (isset($_FILES[$entry])) { 47 | $this->values = to_array_of_files($_FILES[$entry]); 48 | $this->format = 'FILE_OBJECTS'; 49 | } 50 | 51 | if (isset($_POST[$entry])) { 52 | $this->values = to_array($_POST[$entry]); 53 | if (is_encoded_file($this->values[0])) { 54 | $this->format = 'BASE64_ENCODED_FILE_OBJECTS'; 55 | } 56 | else { 57 | $this->format = 'TRANSFER_IDS'; 58 | } 59 | } 60 | 61 | } 62 | 63 | public function getFormat() { 64 | return $this->format; 65 | } 66 | 67 | public function getValues() { 68 | return $this->values; 69 | } 70 | } -------------------------------------------------------------------------------- /config_doka.php: -------------------------------------------------------------------------------- 1 | transform) ? [ 40 | 'quality' => $metadata->transform->quality, 41 | 'type' => $metadata->transform->type 42 | ] : []; 43 | 44 | // Transform and save the file 45 | $result = Doka\transform( 46 | $source, 47 | $target, 48 | $transforms, 49 | $output 50 | ); 51 | 52 | // we'll put it back where it came from 53 | if ($result) { 54 | array_push($files, FilePond\create_file_object($target)); 55 | } 56 | 57 | return $files; 58 | } -------------------------------------------------------------------------------- /Helper/Transfer.class.php: -------------------------------------------------------------------------------- 1 | id = $id ? $id : UniqueIdDispenser::dispense(); 24 | } 25 | 26 | public function getid() { 27 | return $this->id; 28 | } 29 | 30 | public function getMetadata() { 31 | return $this->metadata; 32 | } 33 | 34 | public function getChunks() { 35 | return $this->chunks; 36 | } 37 | 38 | public function getFiles($mutator = null) { 39 | if ($this->file === null) return null; 40 | $files = array_merge(isset($this->file) ? [$this->file] : [], $this->variants); 41 | return $mutator === null ? $files : call_user_func($mutator, $files, $this->metadata); 42 | } 43 | 44 | public function restore($file, $variants = [], $chunks = [], $metadata = []) { 45 | $this->file = $file; 46 | $this->variants = $variants; 47 | $this->chunks = $chunks; 48 | $this->metadata = $metadata; 49 | } 50 | 51 | public function populate($entry) { 52 | 53 | $files = isset($_FILES[$entry]) ? to_array_of_files($_FILES[$entry]) : null; 54 | $metadata = isset($_POST[$entry]) ? to_array($_POST[$entry]) : []; 55 | 56 | // parse metadata 57 | if (count($metadata)) { 58 | $this->metadata = @json_decode($metadata[0]); 59 | } 60 | 61 | // no files 62 | if ($files === null) return; 63 | 64 | // files should always be available, first file is always the main file 65 | $this->file = $files[0]; 66 | 67 | // if variants submitted, set to variants array 68 | $this->variants = array_slice($files, 1); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /submit.php: -------------------------------------------------------------------------------- 1 | 'handle_file_post', 19 | 'BASE64_ENCODED_FILE_OBJECTS' => 'handle_base64_encoded_file_post', 20 | 'TRANSFER_IDS' => 'handle_transfer_ids_post' 21 | ]); 22 | 23 | function handle_file_post($files) { 24 | 25 | // This is a very basic implementation of a classic PHP upload function, please properly 26 | // validate all submitted files before saving to disk or database, more information here 27 | // http://php.net/manual/en/features.file-upload.php 28 | 29 | foreach($files as $file) { 30 | FilePond\move_file($file, UPLOAD_DIR); 31 | } 32 | } 33 | 34 | function handle_base64_encoded_file_post($files) { 35 | 36 | foreach ($files as $file) { 37 | 38 | // Suppress error messages, we'll assume these file objects are valid 39 | /* Expected format: 40 | { 41 | "id": "iuhv2cpsu", 42 | "name": "picture.jpg", 43 | "type": "image/jpeg", 44 | "size": 20636, 45 | "metadata" : {...} 46 | "data": "/9j/4AAQSkZJRgABAQEASABIAA..." 47 | } 48 | */ 49 | $file = @json_decode($file); 50 | 51 | // Skip files that failed to decode 52 | if (!is_object($file)) continue; 53 | 54 | // write file to disk 55 | FilePond\write_file( 56 | UPLOAD_DIR, 57 | base64_decode($file->data), 58 | FilePond\sanitize_filename($file->name) 59 | ); 60 | } 61 | 62 | } 63 | 64 | function handle_transfer_ids_post($ids) { 65 | 66 | foreach ($ids as $id) { 67 | 68 | // create transfer wrapper around upload 69 | $transfer = FilePond\get_transfer(TRANSFER_DIR, $id); 70 | 71 | // transfer not found 72 | if (!$transfer) continue; 73 | 74 | // move files 75 | $files = $transfer->getFiles(defined('TRANSFER_PROCESSOR') ? TRANSFER_PROCESSOR : null); 76 | 77 | if($files != null){ 78 | foreach($files as $file) { 79 | FilePond\move_file($file, UPLOAD_DIR); 80 | } 81 | } 82 | 83 | 84 | // remove transfer directory 85 | FilePond\remove_transfer_directory(TRANSFER_DIR, $id); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 'handle_file_transfer', 27 | 'PATCH_FILE_TRANSFER' => 'handle_patch_file_transfer', 28 | 'REVERT_FILE_TRANSFER' => 'handle_revert_file_transfer', 29 | 'RESTORE_FILE_TRANSFER' => 'handle_restore_file_transfer', 30 | 'LOAD_LOCAL_FILE' => 'handle_load_local_file', 31 | 'FETCH_REMOTE_FILE' => 'handle_fetch_remote_file' 32 | ]); 33 | 34 | function handle_file_transfer($transfer) { 35 | 36 | $metadata = $transfer->getMetadata(); 37 | $files = $transfer->getFiles(); 38 | 39 | // something went wrong, most likely a field name mismatch 40 | if ($files !== null && count($files) === 0) return http_response_code(400); 41 | 42 | // test if server had trouble copying files 43 | $file_transfers_with_errors = array_filter($files, function($file) { return $file['error'] !== 0; }); 44 | if (count($file_transfers_with_errors)) { 45 | foreach ($file_transfers_with_errors as $file) { 46 | trigger_error(sprintf("Uploading file \"%s\" failed with code \"" . $file['error'] . "\".", $file['name']), E_USER_WARNING); 47 | } 48 | return http_response_code(500); 49 | } 50 | 51 | // test if files are of invalid format 52 | $file_transfers_with_invalid_file_type = count(ALLOWED_FILE_FORMATS) ? array_filter($files, function($file) { return !in_array($file['type'], ALLOWED_FILE_FORMATS); }) : array(); 53 | if (count($file_transfers_with_invalid_file_type)) { 54 | foreach ($file_transfers_with_invalid_file_type as $file) { 55 | trigger_error(sprintf("Uploading file \"%s\" failed with code \"" . $file['type'] . " is not allowed.\".", $file['name']), E_USER_WARNING); 56 | } 57 | return http_response_code(415); 58 | } 59 | 60 | // store data 61 | FilePond\store_transfer(TRANSFER_DIR, $transfer); 62 | 63 | // created the temp entry 64 | http_response_code(201); 65 | 66 | // returns plain text content 67 | header('Content-Type: text/plain'); 68 | 69 | // remove item from array Response contains uploaded file server id 70 | echo $transfer->getId(); 71 | } 72 | 73 | function handle_patch_file_transfer($id) { 74 | 75 | // location of patch files 76 | $dir = TRANSFER_DIR . DIRECTORY_SEPARATOR . $id . DIRECTORY_SEPARATOR; 77 | 78 | // exit if is get 79 | if ($_SERVER['REQUEST_METHOD'] === 'HEAD') { 80 | $patch = glob($dir . '.patch.*'); 81 | $offsets = array(); 82 | $size = ''; 83 | $last_offset = 0; 84 | foreach ($patch as $filename) { 85 | 86 | // get size of chunk 87 | $size = filesize($filename); 88 | 89 | // get offset of chunk 90 | list($dir, $offset) = explode('.patch.', $filename, 2); 91 | 92 | // offsets 93 | array_push($offsets, intval($offset)); 94 | } 95 | 96 | sort($offsets); 97 | 98 | foreach ($offsets as $offset) { 99 | // test if is missing previous chunk 100 | // don't test first chunk (previous chunk is non existent) 101 | if ($offset > 0 && !in_array($offset - $size, $offsets)) { 102 | $last_offset = $offset - $size; 103 | break; 104 | } 105 | 106 | // last offset is at least next offset 107 | $last_offset = $offset + $size; 108 | } 109 | 110 | // return offset 111 | http_response_code(200); 112 | header('Upload-Offset: ' . $last_offset); 113 | return; 114 | } 115 | 116 | // get patch data 117 | $offset = $_SERVER['HTTP_UPLOAD_OFFSET']; 118 | $length = $_SERVER['HTTP_UPLOAD_LENGTH']; 119 | 120 | // should be numeric values, else exit 121 | if (!is_numeric($offset) || !is_numeric($length)) { 122 | return http_response_code(400); 123 | } 124 | 125 | // get sanitized name 126 | $name = FilePond\sanitize_filename($_SERVER['HTTP_UPLOAD_NAME']); 127 | 128 | // write patch file for this request 129 | file_put_contents($dir . '.patch.' . $offset, fopen('php://input', 'r')); 130 | 131 | // calculate total size of patches 132 | $size = 0; 133 | $patch = glob($dir . '.patch.*'); 134 | foreach ($patch as $filename) { 135 | $size += filesize($filename); 136 | } 137 | 138 | // if total size equals length of file we have gathered all patch files 139 | if ($size == $length) { 140 | 141 | // create output file 142 | $file_handle = fopen($dir . $name, 'w'); 143 | 144 | // write patches to file 145 | foreach ($patch as $filename) { 146 | 147 | // get offset from filename 148 | list($dir, $offset) = explode('.patch.', $filename, 2); 149 | 150 | // read patch and close 151 | $patch_handle = fopen($filename, 'r'); 152 | $patch_contents = fread($patch_handle, filesize($filename)); 153 | fclose($patch_handle); 154 | 155 | // apply patch 156 | fseek($file_handle, $offset); 157 | fwrite($file_handle, $patch_contents); 158 | } 159 | 160 | // remove patches 161 | foreach ($patch as $filename) { 162 | unlink($filename); 163 | } 164 | 165 | // done with file 166 | fclose($file_handle); 167 | } 168 | 169 | http_response_code(204); 170 | } 171 | 172 | function handle_revert_file_transfer($id) { 173 | 174 | // test if id was supplied 175 | if (!isset($id) || !FilePond\is_valid_transfer_id($id)) return http_response_code(400); 176 | 177 | // remove transfer directory 178 | FilePond\remove_transfer_directory(TRANSFER_DIR, $id); 179 | 180 | // no content to return 181 | http_response_code(204); 182 | } 183 | 184 | function handle_restore_file_transfer($id) { 185 | 186 | // Stop here if no id supplied 187 | if (empty($id) || !FilePond\is_valid_transfer_id($id)) return http_response_code(400); 188 | 189 | // create transfer wrapper around upload 190 | $transfer = FilePond\get_transfer(TRANSFER_DIR, $id); 191 | 192 | // Let's get the temp file content 193 | $files = $transfer->getFiles(); 194 | 195 | // No file returned, file not found 196 | if (count($files) === 0) return http_response_code(404); 197 | 198 | // Return file 199 | FilePond\echo_file($files[0]); 200 | } 201 | 202 | function handle_load_local_file($ref) { 203 | 204 | // Stop here if no id supplied 205 | if (empty($ref)) return http_response_code(400); 206 | 207 | // In this example implementation the file id is simply the filename and 208 | // we request the file from the uploads folder, it could very well be 209 | // that the file should be fetched from a database or a totally different system. 210 | 211 | // path to file 212 | $path = UPLOAD_DIR . DIRECTORY_SEPARATOR . FilePond\sanitize_filename($ref); 213 | 214 | // Return file 215 | FilePond\echo_file($path); 216 | } 217 | 218 | function handle_fetch_remote_file($url) { 219 | 220 | // Stop here if no data supplied 221 | if (empty($url)) return http_response_code(400); 222 | 223 | // Is this a valid url 224 | if (!FilePond\is_url($url)) return http_response_code(400); 225 | 226 | // Let's try to get the remote file content 227 | $file = FilePond\fetch($url); 228 | 229 | // Something went wrong 230 | if (!$file) return http_response_code(500); 231 | 232 | // remote server returned invalid response 233 | if ($file['error'] !== 0) return http_response_code($file['error']); 234 | 235 | // if we only return headers we store the file in the transfer folder 236 | if ($_SERVER['REQUEST_METHOD'] === 'HEAD') { 237 | 238 | // deal with this file as if it's a file transfer, will return unique id to client 239 | $transfer = new FilePond\Transfer(); 240 | $transfer->restore($file); 241 | FilePond\store_transfer(TRANSFER_DIR, $transfer); 242 | 243 | // send transfer id back to client 244 | header('X-Content-Transfer-Id: ' . $transfer->getId()); 245 | } 246 | 247 | // time to return the file to the client 248 | FilePond\echo_file($file); 249 | } -------------------------------------------------------------------------------- /FilePond.class.php: -------------------------------------------------------------------------------- 1 | stream_get_meta_data($out)['uri'], 31 | 'name' => sanitize_filename(pathinfo($url)['basename']), 32 | 'type' => $type, 33 | 'length' => $length, 34 | 'error' => $code >= 200 && $code < 300 ? 0 : $code, 35 | 'ref' => $out, // need this so the file is not automatically removed 36 | ); 37 | } 38 | catch(Exception $e) { 39 | return false; 40 | } 41 | } 42 | 43 | function sanitize_filename($filename) { 44 | $info = pathinfo($filename); 45 | $name = sanitize_filename_part($info['filename']); 46 | $extension = sanitize_filename_part($info['extension']); 47 | return (strlen($name) > 0 ? $name : '_') . '.' . $extension; 48 | } 49 | 50 | function sanitize_filename_part($str) { 51 | return preg_replace("/[^a-zA-Z0-9\_\s]/", "", $str); 52 | } 53 | 54 | function remove_directory($path) { 55 | if (!is_dir($path)) {return;} 56 | $files = glob($path . DIRECTORY_SEPARATOR . '{.,}*', GLOB_BRACE); 57 | @array_map('unlink', $files); 58 | @rmdir($path); 59 | } 60 | 61 | function remove_transfer_directory($path, $id) { 62 | 63 | // don't remove anything if the transfer id is not valid (just a security precaution) 64 | if (!is_valid_transfer_id($id)) return; 65 | 66 | remove_directory($path . DIRECTORY_SEPARATOR . $id . DIRECTORY_SEPARATOR . VARIANTS_DIR); 67 | remove_directory($path . DIRECTORY_SEPARATOR . $id); 68 | } 69 | 70 | function create_directory($path) { 71 | if (!is_dir($path)) { 72 | mkdir($path, 0755, true); 73 | return true; 74 | } 75 | return false; 76 | } 77 | 78 | function secure_directory($path) { 79 | $content = '# Don\'t list directory contents 80 | IndexIgnore * 81 | # Disable script execution 82 | AddHandler cgi-script .php .pl .jsp .asp .sh .cgi 83 | Options -ExecCGI -Indexes'; 84 | file_put_contents($path . DIRECTORY_SEPARATOR . '.htaccess', $content); 85 | } 86 | 87 | function create_secure_directory($path) { 88 | $created = create_directory($path); 89 | if ($created) { 90 | secure_directory($path); 91 | } 92 | } 93 | 94 | function write_file($path, $data, $filename) { 95 | $handle = fopen($path . DIRECTORY_SEPARATOR . $filename, 'w'); 96 | fwrite($handle, $data); 97 | fclose($handle); 98 | } 99 | 100 | function is_url($str) { 101 | if (!filter_var($str, FILTER_VALIDATE_URL)) return false; 102 | return in_array(parse_url($str, PHP_URL_SCHEME),['http', 'https', 'ftp']); 103 | } 104 | 105 | function echo_file($file) { 106 | 107 | // read file object 108 | if (is_string($file)) $file = read_file($file); 109 | 110 | // something went wrong while reading the file 111 | if (!$file) http_response_code(500); 112 | 113 | // Allow to read Content Disposition (so we can read the file name on the client side) 114 | header('Access-Control-Expose-Headers: Content-Disposition, Content-Length, X-Content-Transfer-Id'); 115 | header('Content-Type: ' . $file['type']); 116 | header('Content-Length: ' . $file['length']); 117 | header('Content-Disposition: inline; filename="' . $file['name'] . '"'); 118 | echo isset($file['content']) ? $file['content'] : read_file_contents($file['tmp_name']); 119 | } 120 | 121 | function read_file_contents($filename) { 122 | $file = read_file($filename); 123 | if (!$file) return false; 124 | return $file['content']; 125 | } 126 | 127 | function read_file($filename) { 128 | $handle = fopen($filename, 'r'); 129 | if (!$handle) return false; 130 | $content = fread($handle, filesize($filename)); 131 | fclose($handle); 132 | if (!$content) return false; 133 | return array( 134 | 'tmp_name' => $filename, 135 | 'name' => basename($filename), 136 | 'content' => $content, 137 | 'type' => mime_content_type($filename), 138 | 'length' => filesize($filename), 139 | 'error' => 0 140 | ); 141 | } 142 | 143 | function move_temp_file($file, $path) { 144 | move_uploaded_file($file['tmp_name'], $path . DIRECTORY_SEPARATOR . sanitize_filename($file['name'])); 145 | } 146 | 147 | function move_file($file, $path) { 148 | if (is_uploaded_file($file['tmp_name'])) { 149 | return move_temp_file($file, $path); 150 | } 151 | return rename($file['tmp_name'], $path . DIRECTORY_SEPARATOR . sanitize_filename($file['name'])); 152 | } 153 | 154 | function store_transfer($path, $transfer) { 155 | 156 | // create transfer directory 157 | $path = $path . DIRECTORY_SEPARATOR . $transfer->getId(); 158 | create_secure_directory($path); 159 | 160 | // store metadata 161 | if ($transfer->getMetadata()) { 162 | write_file($path, @json_encode($transfer->getMetadata()), METADATA_FILENAME); 163 | } 164 | 165 | // store main file if set (if not set, we expect to receive chunks in the near future) 166 | $files = $transfer->getFiles(); 167 | 168 | if ($files === null) return; 169 | $file = $files[0]; 170 | move_file($file, $path); 171 | 172 | // store variants 173 | if (count($transfer->getFiles()) > 1) { 174 | 175 | $files = array_slice($files, 1); 176 | $variants = $path . DIRECTORY_SEPARATOR . VARIANTS_DIR; 177 | create_secure_directory($variants); 178 | 179 | foreach($files as $file) { 180 | move_file($file, $variants); 181 | } 182 | } 183 | } 184 | 185 | function get_files($path, $pattern) { 186 | $results = []; 187 | $files = glob($path . DIRECTORY_SEPARATOR . $pattern); 188 | foreach($files as $file) { 189 | array_push($results, create_file_object($file)); 190 | } 191 | return $results; 192 | } 193 | 194 | function get_file($path, $pattern) { 195 | $result = get_files($path, $pattern); 196 | if (count($result) > 0) { 197 | return $result[0]; 198 | } 199 | return; 200 | } 201 | 202 | function create_file_object($filename) { 203 | return array( 204 | 'tmp_name' => $filename, 205 | 'name' => basename($filename), 206 | 'type' => mime_content_type($filename), 207 | 'length' => filesize($filename), 208 | 'error' => 0 209 | ); 210 | } 211 | 212 | function is_valid_transfer_id($id) { 213 | return preg_match('/^[0-9a-fA-F]{32}$/', $id); 214 | } 215 | 216 | function get_transfer($path, $id) { 217 | 218 | if (!is_valid_transfer_id($id)) return false; 219 | 220 | $transfer = new Transfer($id); 221 | 222 | $path = $path . DIRECTORY_SEPARATOR . $id; 223 | 224 | $file = get_file($path, '*.*'); 225 | 226 | $metadata = get_file($path, METADATA_FILENAME); 227 | 228 | $variants = get_files($path . DIRECTORY_SEPARATOR . VARIANTS_DIR, '*.*'); 229 | 230 | $transfer->restore($file, $variants, null, $metadata); 231 | 232 | return $transfer; 233 | } 234 | 235 | function get_post($entry) { 236 | return isset($_FILES[$entry]) || isset($_POST[$entry]) ? new Post($entry) : false; 237 | } 238 | 239 | function route_form_post($entries, $routes) { 240 | 241 | // if a singly field entry is supplied, turn it into an array 242 | if (is_string($entries)) $entries = array($entries); 243 | 244 | foreach ($entries as $entry) { 245 | $post = get_post($entry); 246 | if (!$post) continue; 247 | if (!isset($routes[$post->getFormat()])) continue; 248 | call_user_func($routes[$post->getFormat()], $post->getValues()); 249 | } 250 | } 251 | 252 | function route_api_request($entries, $routes) { 253 | 254 | // if a singly field entry is supplied, turn it into an array 255 | if (is_string($entries)) $entries = array($entries); 256 | 257 | // get the request method so we don't have to use $_SERVER each time 258 | $request_method = $_SERVER['REQUEST_METHOD']; 259 | 260 | // loop over all set entry fields to find posted values 261 | foreach ($entries as $entry) { 262 | 263 | // post new files 264 | if ($request_method === 'POST') { 265 | $post = get_post($entry); 266 | if (!$post) continue; 267 | $transfer = new Transfer(); 268 | $transfer->populate($entry); 269 | return call_user_func($routes['FILE_TRANSFER'], $transfer); 270 | } 271 | 272 | // revert existing transfer 273 | if ($request_method === 'DELETE') { 274 | return call_user_func($routes['REVERT_FILE_TRANSFER'], file_get_contents('php://input')); 275 | } 276 | 277 | // fetch, load, restore 278 | if ($request_method === 'GET' || $request_method === 'HEAD' || $request_method === 'PATCH') { 279 | $handlers = array( 280 | 'fetch' => 'FETCH_REMOTE_FILE', 281 | 'restore' => 'RESTORE_FILE_TRANSFER', 282 | 'load' => 'LOAD_LOCAL_FILE', 283 | 'patch' => 'PATCH_FILE_TRANSFER' 284 | ); 285 | foreach ($handlers as $param => $handler) { 286 | if (isset($_GET[$param])) { 287 | return call_user_func($routes[$handler], $_GET[$param], $entry); 288 | } 289 | } 290 | } 291 | 292 | } 293 | } 294 | --------------------------------------------------------------------------------