3  * jQuery File Upload Plugin PHP Class
 
   4  * https://github.com/blueimp/jQuery-File-Upload
 
   6  * Copyright 2010, Sebastian Tschan
 
   9  * Licensed under the MIT license:
 
  10  * https://opensource.org/licenses/MIT
 
  18     // PHP File Upload error message codes:
 
  19     // https://php.net/manual/en/features.file-upload.errors.php
 
  20     protected $error_messages = array(
 
  21         1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
 
  22         2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
 
  23         3 => 'The uploaded file was only partially uploaded',
 
  24         4 => 'No file was uploaded',
 
  25         6 => 'Missing a temporary folder',
 
  26         7 => 'Failed to write file to disk',
 
  27         8 => 'A PHP extension stopped the file upload',
 
  28         'post_max_size' => 'The uploaded file exceeds the post_max_size directive in php.ini',
 
  29         'max_file_size' => 'File is too big',
 
  30         'min_file_size' => 'File is too small',
 
  31         'accept_file_types' => 'Filetype not allowed',
 
  32         'max_number_of_files' => 'Maximum number of files exceeded',
 
  33         'invalid_file_type' => 'Invalid file type',
 
  34         'max_width' => 'Image exceeds maximum width',
 
  35         'min_width' => 'Image requires a minimum width',
 
  36         'max_height' => 'Image exceeds maximum height',
 
  37         'min_height' => 'Image requires a minimum height',
 
  38         'abort' => 'File upload aborted',
 
  39         'image_resize' => 'Failed to resize image'
 
  42     const IMAGETYPE_GIF = 'image/gif';
 
  43     const IMAGETYPE_JPEG = 'image/jpeg';
 
  44     const IMAGETYPE_PNG = 'image/png';
 
  46     protected $image_objects = array();
 
  47     protected $response = array();
 
  49     public function __construct($options = null, $initialize = true, $error_messages = null) {
 
  50         $this->options = array(
 
  51             'script_url' => $this->get_full_url().'/'.$this->basename($this->get_server_var('SCRIPT_NAME')),
 
  52             'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/files/',
 
  53             'upload_url' => $this->get_full_url().'/files/',
 
  54             'input_stream' => 'php://input',
 
  57             'param_name' => 'files',
 
  58             // Set the following option to 'POST', if your server does not support
 
  59             // DELETE requests. This is a parameter sent to the client:
 
  60             'delete_type' => 'DELETE',
 
  61             'access_control_allow_origin' => '*',
 
  62             'access_control_allow_credentials' => false,
 
  63             'access_control_allow_methods' => array(
 
  72             'access_control_allow_headers' => array(
 
  77             // By default, allow redirects to the referer protocol+host:
 
  78             'redirect_allow_target' => '/^'.preg_quote(
 
  79                     parse_url($this->get_server_var('HTTP_REFERER'), PHP_URL_SCHEME)
 
  81                     .parse_url($this->get_server_var('HTTP_REFERER'), PHP_URL_HOST)
 
  82                     .'/', // Trailing slash to not match subdomains by mistake
 
  83                     '/' // preg_quote delimiter param
 
  85             // Enable to provide file downloads via GET requests to the PHP script:
 
  86             //     1. Set to 1 to download files via readfile method through PHP
 
  87             //     2. Set to 2 to send a X-Sendfile header for lighttpd/Apache
 
  88             //     3. Set to 3 to send a X-Accel-Redirect header for nginx
 
  89             // If set to 2 or 3, adjust the upload_url option to the base path of
 
  90             // the redirect parameter, e.g. '/files/'.
 
  91             'download_via_php' => false,
 
  92             // Read files in chunks to avoid memory limits when download_via_php
 
  93             // is enabled, set to 0 to disable chunked reading of files:
 
  94             'readfile_chunk_size' => 10 * 1024 * 1024, // 10 MiB
 
  95             // Defines which files can be displayed inline when downloaded:
 
  96             'inline_file_types' => '/\.(gif|jpe?g|png)$/i',
 
  97             // Defines which files (based on their names) are accepted for upload.
 
  98             // By default, only allows file uploads with image file extensions.
 
  99             // Only change this setting after making sure that any allowed file
 
 100             // types cannot be executed by the webserver in the files directory,
 
 101             // e.g. PHP scripts, nor executed by the browser when downloaded,
 
 102             // e.g. HTML files with embedded JavaScript code.
 
 103             // Please also read the SECURITY.md document in this repository.
 
 104             'accept_file_types' => '/\.(gif|jpe?g|png)$/i',
 
 105             // Replaces dots in filenames with the given string.
 
 106             // Can be disabled by setting it to false or an empty string.
 
 107             // Note that this is a security feature for servers that support
 
 108             // multiple file extensions, e.g. the Apache AddHandler Directive:
 
 109             // https://httpd.apache.org/docs/current/mod/mod_mime.html#addhandler
 
 110             // Before disabling it, make sure that files uploaded with multiple
 
 111             // extensions cannot be executed by the webserver, e.g.
 
 112             // "example.php.png" with embedded PHP code, nor executed by the
 
 113             // browser when downloaded, e.g. "example.html.gif" with embedded
 
 115             'replace_dots_in_filenames' => '-',
 
 116             // The php.ini settings upload_max_filesize and post_max_size
 
 117             // take precedence over the following max_file_size setting:
 
 118             'max_file_size' => null,
 
 119             'min_file_size' => 1,
 
 120             // The maximum number of files for the upload directory:
 
 121             'max_number_of_files' => null,
 
 122             // Reads first file bytes to identify and correct file extensions:
 
 123             'correct_image_extensions' => false,
 
 124             // Image resolution restrictions:
 
 126             'max_height' => null,
 
 129             // Set the following option to false to enable resumable uploads:
 
 130             'discard_aborted_uploads' => true,
 
 131             // Set to 0 to use the GD library to scale and orient images,
 
 132             // set to 1 to use imagick (if installed, falls back to GD),
 
 133             // set to 2 to use the ImageMagick convert binary directly:
 
 134             'image_library' => 1,
 
 135             // Uncomment the following to define an array of resource limits
 
 138             'imagick_resource_limits' => array(
 
 139                 imagick::RESOURCETYPE_MAP => 32,
 
 140                 imagick::RESOURCETYPE_MEMORY => 32
 
 143             // Command or path for to the ImageMagick convert binary:
 
 144             'convert_bin' => 'convert',
 
 145             // Uncomment the following to add parameters in front of each
 
 146             // ImageMagick convert call (the limit constraints seem only
 
 147             // to have an effect if put in front):
 
 149             'convert_params' => '-limit memory 32MiB -limit map 32MiB',
 
 151             // Command or path for to the ImageMagick identify binary:
 
 152             'identify_bin' => 'identify',
 
 153             'image_versions' => array(
 
 154                 // The empty image version key defines options for the original image.
 
 155                 // Keep in mind: these image manipulations are inherited by all other image versions from this point onwards.
 
 156                 // Also note that the property 'no_cache' is not inherited, since it's not a manipulation.
 
 158                     // Automatically rotate images based on EXIF meta data:
 
 159                     'auto_orient' => true
 
 161                 // You can add arrays to generate different versions.
 
 162                 // The name of the key is the name of the version (example: 'medium').
 
 163                 // the array contains the options to apply.
 
 170                 'thumbnail' => array(
 
 171                     // Uncomment the following to use a defined directory for the thumbnails
 
 172                     // instead of a subdirectory based on the version identifier.
 
 173                     // Make sure that this directory doesn't allow execution of files if you
 
 174                     // don't pose any restrictions on the type of uploaded files, e.g. by
 
 175                     // copying the .htaccess file from the files directory for Apache:
 
 176                     //'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/thumb/',
 
 177                     //'upload_url' => $this->get_full_url().'/thumb/',
 
 178                     // Uncomment the following to force the max
 
 179                     // dimensions and e.g. create square thumbnails:
 
 180                     // 'auto_orient' => true,
 
 182                     // 'jpeg_quality' => 70,
 
 183                     // 'no_cache' => true, (there's a caching option, but this remembers thumbnail sizes from a previous action!)
 
 184                     // 'strip' => true, (this strips EXIF tags, such as geolocation)
 
 185                     'max_width' => 80, // either specify width, or set to 0. Then width is automatically adjusted - keeping aspect ratio to a specified max_height.
 
 186                     'max_height' => 80 // either specify height, or set to 0. Then height is automatically adjusted - keeping aspect ratio to a specified max_width.
 
 189             'print_response' => true
 
 192             $this->options = $options + $this->options;
 
 194         if ($error_messages) {
 
 195             $this->error_messages = $error_messages + $this->error_messages;
 
 202     protected function initialize() {
 
 203         switch ($this->get_server_var('REQUEST_METHOD')) {
 
 209                 $this->get($this->options['print_response']);
 
 214                 $this->post($this->options['print_response']);
 
 217                 $this->delete($this->options['print_response']);
 
 220                 $this->header('HTTP/1.1 405 Method Not Allowed');
 
 224     protected function get_full_url() {
 
 225         $https = !empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'on') === 0 ||
 
 226             !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
 
 227             strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0;
 
 229             ($https ? 'https://' : 'http://').
 
 230             (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : '').
 
 231             (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'].
 
 232                 ($https && $_SERVER['SERVER_PORT'] === 443 ||
 
 233                 $_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))).
 
 234             substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/'));
 
 237     protected function get_user_id() {
 
 242     protected function get_user_path() {
 
 243         if ($this->options['user_dirs']) {
 
 244             return $this->get_user_id().'/';
 
 249     protected function get_upload_path($file_name = null, $version = null) {
 
 250         $file_name = $file_name ? $file_name : '';
 
 251         if (empty($version)) {
 
 254             $version_dir = @$this->options['image_versions'][$version]['upload_dir'];
 
 256                 return $version_dir.$this->get_user_path().$file_name;
 
 258             $version_path = $version.'/';
 
 260         return $this->options['upload_dir'].$this->get_user_path()
 
 261             .$version_path.$file_name;
 
 264     protected function get_query_separator($url) {
 
 265         return strpos($url, '?') === false ? '?' : '&';
 
 268     protected function get_download_url($file_name, $version = null, $direct = false) {
 
 269         if (!$direct && $this->options['download_via_php']) {
 
 270             $url = $this->options['script_url']
 
 271                 .$this->get_query_separator($this->options['script_url'])
 
 272                 .$this->get_singular_param_name()
 
 273                 .'='.rawurlencode($file_name);
 
 275                 $url .= '&version='.rawurlencode($version);
 
 277             return $url.'&download=1';
 
 279         if (empty($version)) {
 
 282             $version_url = @$this->options['image_versions'][$version]['upload_url'];
 
 284                 return $version_url.$this->get_user_path().rawurlencode($file_name);
 
 286             $version_path = rawurlencode($version).'/';
 
 288         return $this->options['upload_url'].$this->get_user_path()
 
 289             .$version_path.rawurlencode($file_name);
 
 292     protected function set_additional_file_properties($file) {
 
 293         $file->deleteUrl = $this->options['script_url']
 
 294             .$this->get_query_separator($this->options['script_url'])
 
 295             .$this->get_singular_param_name()
 
 296             .'='.rawurlencode($file->name);
 
 297         $file->deleteType = $this->options['delete_type'];
 
 298         if ($file->deleteType !== 'DELETE') {
 
 299             $file->deleteUrl .= '&_method=DELETE';
 
 301         if ($this->options['access_control_allow_credentials']) {
 
 302             $file->deleteWithCredentials = true;
 
 306     // Fix for overflowing signed 32 bit integers,
 
 307     // works for sizes up to 2^32-1 bytes (4 GiB - 1):
 
 308     protected function fix_integer_overflow($size) {
 
 310             $size += 2.0 * (PHP_INT_MAX + 1);
 
 315     protected function get_file_size($file_path, $clear_stat_cache = false) {
 
 316         if ($clear_stat_cache) {
 
 317             if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
 
 318                 clearstatcache(true, $file_path);
 
 323         return $this->fix_integer_overflow(filesize($file_path));
 
 326     protected function is_valid_file_object($file_name) {
 
 327         $file_path = $this->get_upload_path($file_name);
 
 328         if (strlen($file_name) > 0 && $file_name[0] !== '.' && is_file($file_path)) {
 
 334     protected function get_file_object($file_name) {
 
 335         if ($this->is_valid_file_object($file_name)) {
 
 336             $file = new \stdClass();
 
 337             $file->name = $file_name;
 
 338             $file->size = $this->get_file_size(
 
 339                 $this->get_upload_path($file_name)
 
 341             $file->url = $this->get_download_url($file->name);
 
 342             foreach ($this->options['image_versions'] as $version => $options) {
 
 343                 if (!empty($version)) {
 
 344                     if (is_file($this->get_upload_path($file_name, $version))) {
 
 345                         $file->{$version.'Url'} = $this->get_download_url(
 
 352             $this->set_additional_file_properties($file);
 
 358     protected function get_file_objects($iteration_method = 'get_file_object') {
 
 359         $upload_dir = $this->get_upload_path();
 
 360         if (!is_dir($upload_dir)) {
 
 363         return array_values(array_filter(array_map(
 
 364             array($this, $iteration_method),
 
 369     protected function count_file_objects() {
 
 370         return count($this->get_file_objects('is_valid_file_object'));
 
 373     protected function get_error_message($error) {
 
 374         return isset($this->error_messages[$error]) ?
 
 375             $this->error_messages[$error] : $error;
 
 378     public function get_config_bytes($val) {
 
 380         $last = strtolower($val[strlen($val)-1]);
 
 381         if (is_numeric($val)) {
 
 384             $val = (int)substr($val, 0, -1);
 
 394         return $this->fix_integer_overflow($val);
 
 397     protected function validate_image_file($uploaded_file, $file, $error, $index) {
 
 398         if ($this->imagetype($uploaded_file) !== $this->get_file_type($file->name)) {
 
 399             $file->error = $this->get_error_message('invalid_file_type');
 
 402         $max_width = @$this->options['max_width'];
 
 403         $max_height = @$this->options['max_height'];
 
 404         $min_width = @$this->options['min_width'];
 
 405         $min_height = @$this->options['min_height'];
 
 406         if ($max_width || $max_height || $min_width || $min_height) {
 
 407             list($img_width, $img_height) = $this->get_image_size($uploaded_file);
 
 408             // If we are auto rotating the image by default, do the checks on
 
 409             // the correct orientation
 
 411                 @$this->options['image_versions']['']['auto_orient'] &&
 
 412                 function_exists('exif_read_data') &&
 
 413                 ($exif = @exif_read_data($uploaded_file)) &&
 
 414                 (((int) @$exif['Orientation']) >= 5)
 
 417                 $img_width = $img_height;
 
 421             if (!empty($img_width) && !empty($img_height)) {
 
 422                 if ($max_width && $img_width > $max_width) {
 
 423                     $file->error = $this->get_error_message('max_width');
 
 426                 if ($max_height && $img_height > $max_height) {
 
 427                     $file->error = $this->get_error_message('max_height');
 
 430                 if ($min_width && $img_width < $min_width) {
 
 431                     $file->error = $this->get_error_message('min_width');
 
 434                 if ($min_height && $img_height < $min_height) {
 
 435                     $file->error = $this->get_error_message('min_height');
 
 443     protected function validate($uploaded_file, $file, $error, $index, $content_range) {
 
 445             $file->error = $this->get_error_message($error);
 
 448         $content_length = $this->fix_integer_overflow(
 
 449             (int)$this->get_server_var('CONTENT_LENGTH')
 
 451         $post_max_size = $this->get_config_bytes(ini_get('post_max_size'));
 
 452         if ($post_max_size && ($content_length > $post_max_size)) {
 
 453             $file->error = $this->get_error_message('post_max_size');
 
 456         if (!preg_match($this->options['accept_file_types'], $file->name)) {
 
 457             $file->error = $this->get_error_message('accept_file_types');
 
 460         if ($uploaded_file && is_uploaded_file($uploaded_file)) {
 
 461             $file_size = $this->get_file_size($uploaded_file);
 
 463             $file_size = $content_length;
 
 465         if ($this->options['max_file_size'] && (
 
 466                 $file_size > $this->options['max_file_size'] ||
 
 467                 $file->size > $this->options['max_file_size'])
 
 469             $file->error = $this->get_error_message('max_file_size');
 
 472         if ($this->options['min_file_size'] &&
 
 473             $file_size < $this->options['min_file_size']) {
 
 474             $file->error = $this->get_error_message('min_file_size');
 
 477         if (is_int($this->options['max_number_of_files']) &&
 
 478             ($this->count_file_objects() >= $this->options['max_number_of_files']) &&
 
 479             // Ignore additional chunks of existing files:
 
 480             !is_file($this->get_upload_path($file->name))) {
 
 481             $file->error = $this->get_error_message('max_number_of_files');
 
 484         if (!$content_range && $this->has_image_file_extension($file->name)) {
 
 485             return $this->validate_image_file($uploaded_file, $file, $error, $index);
 
 490     protected function upcount_name_callback($matches) {
 
 491         $index = isset($matches[1]) ? ((int)$matches[1]) + 1 : 1;
 
 492         $ext = isset($matches[2]) ? $matches[2] : '';
 
 493         return ' ('.$index.')'.$ext;
 
 496     protected function upcount_name($name) {
 
 497         return preg_replace_callback(
 
 498             '/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/',
 
 499             array($this, 'upcount_name_callback'),
 
 505     protected function get_unique_filename($file_path, $name, $size, $type, $error,
 
 506         $index, $content_range) {
 
 507         while(is_dir($this->get_upload_path($name))) {
 
 508             $name = $this->upcount_name($name);
 
 510         // Keep an existing filename if this is part of a chunked upload:
 
 511         $uploaded_bytes = $this->fix_integer_overflow((int)@$content_range[1]);
 
 512         while (is_file($this->get_upload_path($name))) {
 
 513             if ($uploaded_bytes === $this->get_file_size(
 
 514                     $this->get_upload_path($name))) {
 
 517             $name = $this->upcount_name($name);
 
 522     protected function get_valid_image_extensions($file_path) {
 
 523         switch ($this->imagetype($file_path)) {
 
 524             case self::IMAGETYPE_JPEG:
 
 525                 return array('jpg', 'jpeg');
 
 526             case self::IMAGETYPE_PNG:
 
 528             case self::IMAGETYPE_GIF:
 
 533     protected function fix_file_extension($file_path, $name, $size, $type, $error,
 
 534         $index, $content_range) {
 
 535         // Add missing file extension for known image types:
 
 536         if (strpos($name, '.') === false &&
 
 537             preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
 
 538             $name .= '.'.$matches[1];
 
 540         if ($this->options['correct_image_extensions']) {
 
 541             $extensions = $this->get_valid_image_extensions($file_path);
 
 542             // Adjust incorrect image file extensions:
 
 543             if (!empty($extensions)) {
 
 544                 $parts = explode('.', $name);
 
 545                 $extIndex = count($parts) - 1;
 
 546                 $ext = strtolower(@$parts[$extIndex]);
 
 547                 if (!in_array($ext, $extensions)) {
 
 548                     $parts[$extIndex] = $extensions[0];
 
 549                     $name = implode('.', $parts);
 
 556     protected function trim_file_name($file_path, $name, $size, $type, $error,
 
 557         $index, $content_range) {
 
 558         // Remove path information and dots around the filename, to prevent uploading
 
 559         // into different directories or replacing hidden system files.
 
 560         // Also remove control characters and spaces (\x00..\x20) around the filename:
 
 561         $name = trim($this->basename(stripslashes($name)), ".\x00..\x20");
 
 562         // Replace dots in filenames to avoid security issues with servers
 
 563         // that interpret multiple file extensions, e.g. "example.php.png":
 
 564         $replacement = $this->options['replace_dots_in_filenames'];
 
 565         if (!empty($replacement)) {
 
 566             $parts = explode('.', $name);
 
 567             if (count($parts) > 2) {
 
 568                 $ext = array_pop($parts);
 
 569                 $name = implode($replacement, $parts).'.'.$ext;
 
 572         // Use a timestamp for empty filenames:
 
 574             $name = str_replace('.', '-', microtime(true));
 
 579     protected function get_file_name($file_path, $name, $size, $type, $error,
 
 580         $index, $content_range) {
 
 581         $name = $this->trim_file_name($file_path, $name, $size, $type, $error,
 
 582             $index, $content_range);
 
 583         return $this->get_unique_filename(
 
 585             $this->fix_file_extension($file_path, $name, $size, $type, $error,
 
 586                 $index, $content_range),
 
 595     protected function get_scaled_image_file_paths($file_name, $version) {
 
 596         $file_path = $this->get_upload_path($file_name);
 
 597         if (!empty($version)) {
 
 598             $version_dir = $this->get_upload_path(null, $version);
 
 599             if (!is_dir($version_dir)) {
 
 600                 mkdir($version_dir, $this->options['mkdir_mode'], true);
 
 602             $new_file_path = $version_dir.'/'.$file_name;
 
 604             $new_file_path = $file_path;
 
 606         return array($file_path, $new_file_path);
 
 609     protected function gd_get_image_object($file_path, $func, $no_cache = false) {
 
 610         if (empty($this->image_objects[$file_path]) || $no_cache) {
 
 611             $this->gd_destroy_image_object($file_path);
 
 612             $this->image_objects[$file_path] = $func($file_path);
 
 614         return $this->image_objects[$file_path];
 
 617     protected function gd_set_image_object($file_path, $image) {
 
 618         $this->gd_destroy_image_object($file_path);
 
 619         $this->image_objects[$file_path] = $image;
 
 622     protected function gd_destroy_image_object($file_path) {
 
 623         $image = (isset($this->image_objects[$file_path])) ? $this->image_objects[$file_path] : null ;
 
 624         return $image && imagedestroy($image);
 
 627     protected function gd_imageflip($image, $mode) {
 
 628         if (function_exists('imageflip')) {
 
 629             return imageflip($image, $mode);
 
 631         $new_width = $src_width = imagesx($image);
 
 632         $new_height = $src_height = imagesy($image);
 
 633         $new_img = imagecreatetruecolor($new_width, $new_height);
 
 637             case '1': // flip on the horizontal axis
 
 638                 $src_y = $new_height - 1;
 
 639                 $src_height = -$new_height;
 
 641             case '2': // flip on the vertical axis
 
 642                 $src_x  = $new_width - 1;
 
 643                 $src_width = -$new_width;
 
 645             case '3': // flip on both axes
 
 646                 $src_y = $new_height - 1;
 
 647                 $src_height = -$new_height;
 
 648                 $src_x  = $new_width - 1;
 
 649                 $src_width = -$new_width;
 
 669     protected function gd_orient_image($file_path, $src_img) {
 
 670         if (!function_exists('exif_read_data')) {
 
 673         $exif = @exif_read_data($file_path);
 
 674         if ($exif === false) {
 
 677         $orientation = (int)@$exif['Orientation'];
 
 678         if ($orientation < 2 || $orientation > 8) {
 
 681         switch ($orientation) {
 
 683                 $new_img = $this->gd_imageflip(
 
 685                     defined('IMG_FLIP_VERTICAL') ? IMG_FLIP_VERTICAL : 2
 
 689                 $new_img = imagerotate($src_img, 180, 0);
 
 692                 $new_img = $this->gd_imageflip(
 
 694                     defined('IMG_FLIP_HORIZONTAL') ? IMG_FLIP_HORIZONTAL : 1
 
 698                 $tmp_img = $this->gd_imageflip(
 
 700                     defined('IMG_FLIP_HORIZONTAL') ? IMG_FLIP_HORIZONTAL : 1
 
 702                 $new_img = imagerotate($tmp_img, 270, 0);
 
 703                 imagedestroy($tmp_img);
 
 706                 $new_img = imagerotate($src_img, 270, 0);
 
 709                 $tmp_img = $this->gd_imageflip(
 
 711                     defined('IMG_FLIP_VERTICAL') ? IMG_FLIP_VERTICAL : 2
 
 713                 $new_img = imagerotate($tmp_img, 270, 0);
 
 714                 imagedestroy($tmp_img);
 
 717                 $new_img = imagerotate($src_img, 90, 0);
 
 722         $this->gd_set_image_object($file_path, $new_img);
 
 726     protected function gd_create_scaled_image($file_name, $version, $options) {
 
 727         if (!function_exists('imagecreatetruecolor')) {
 
 728             error_log('Function not found: imagecreatetruecolor');
 
 731         list($file_path, $new_file_path) =
 
 732             $this->get_scaled_image_file_paths($file_name, $version);
 
 733         $type = strtolower(substr(strrchr($file_name, '.'), 1));
 
 737                 $src_func = 'imagecreatefromjpeg';
 
 738                 $write_func = 'imagejpeg';
 
 739                 $image_quality = isset($options['jpeg_quality']) ?
 
 740                     $options['jpeg_quality'] : 75;
 
 743                 $src_func = 'imagecreatefromgif';
 
 744                 $write_func = 'imagegif';
 
 745                 $image_quality = null;
 
 748                 $src_func = 'imagecreatefrompng';
 
 749                 $write_func = 'imagepng';
 
 750                 $image_quality = isset($options['png_quality']) ?
 
 751                     $options['png_quality'] : 9;
 
 756         $src_img = $this->gd_get_image_object(
 
 759             !empty($options['no_cache'])
 
 761         $image_oriented = false;
 
 762         if (!empty($options['auto_orient']) && $this->gd_orient_image(
 
 766             $image_oriented = true;
 
 767             $src_img = $this->gd_get_image_object(
 
 772         $max_width = $img_width = imagesx($src_img);
 
 773         $max_height = $img_height = imagesy($src_img);
 
 774         if (!empty($options['max_width'])) {
 
 775             $max_width = $options['max_width'];
 
 777         if (!empty($options['max_height'])) {
 
 778             $max_height = $options['max_height'];
 
 781             $max_width / $img_width,
 
 782             $max_height / $img_height
 
 785             if ($image_oriented) {
 
 786                 return $write_func($src_img, $new_file_path, $image_quality);
 
 788             if ($file_path !== $new_file_path) {
 
 789                 return copy($file_path, $new_file_path);
 
 793         if (empty($options['crop'])) {
 
 794             $new_width = $img_width * $scale;
 
 795             $new_height = $img_height * $scale;
 
 798             $new_img = imagecreatetruecolor($new_width, $new_height);
 
 800             if (($img_width / $img_height) >= ($max_width / $max_height)) {
 
 801                 $new_width = $img_width / ($img_height / $max_height);
 
 802                 $new_height = $max_height;
 
 804                 $new_width = $max_width;
 
 805                 $new_height = $img_height / ($img_width / $max_width);
 
 807             $dst_x = 0 - ($new_width - $max_width) / 2;
 
 808             $dst_y = 0 - ($new_height - $max_height) / 2;
 
 809             $new_img = imagecreatetruecolor($max_width, $max_height);
 
 811         // Handle transparency in GIF and PNG images:
 
 814                 imagecolortransparent($new_img, imagecolorallocate($new_img, 0, 0, 0));
 
 817                 imagecolortransparent($new_img, imagecolorallocate($new_img, 0, 0, 0));
 
 818                 imagealphablending($new_img, false);
 
 819                 imagesavealpha($new_img, true);
 
 822         $success = imagecopyresampled(
 
 833             ) && $write_func($new_img, $new_file_path, $image_quality);
 
 834         $this->gd_set_image_object($file_path, $new_img);
 
 838     protected function imagick_get_image_object($file_path, $no_cache = false) {
 
 839         if (empty($this->image_objects[$file_path]) || $no_cache) {
 
 840             $this->imagick_destroy_image_object($file_path);
 
 841             $image = new \Imagick();
 
 842             if (!empty($this->options['imagick_resource_limits'])) {
 
 843                 foreach ($this->options['imagick_resource_limits'] as $type => $limit) {
 
 844                     $image->setResourceLimit($type, $limit);
 
 848                 $image->readImage($file_path);
 
 849             } catch (ImagickException $e) {
 
 850                 error_log($e->getMessage());
 
 853             $this->image_objects[$file_path] = $image;
 
 855         return $this->image_objects[$file_path];
 
 858     protected function imagick_set_image_object($file_path, $image) {
 
 859         $this->imagick_destroy_image_object($file_path);
 
 860         $this->image_objects[$file_path] = $image;
 
 863     protected function imagick_destroy_image_object($file_path) {
 
 864         $image = (isset($this->image_objects[$file_path])) ? $this->image_objects[$file_path] : null ;
 
 865         return $image && $image->destroy();
 
 868     protected function imagick_orient_image($image) {
 
 869         $orientation = $image->getImageOrientation();
 
 870         $background = new \ImagickPixel('none');
 
 871         switch ($orientation) {
 
 872             case \imagick::ORIENTATION_TOPRIGHT: // 2
 
 873                 $image->flopImage(); // horizontal flop around y-axis
 
 875             case \imagick::ORIENTATION_BOTTOMRIGHT: // 3
 
 876                 $image->rotateImage($background, 180);
 
 878             case \imagick::ORIENTATION_BOTTOMLEFT: // 4
 
 879                 $image->flipImage(); // vertical flip around x-axis
 
 881             case \imagick::ORIENTATION_LEFTTOP: // 5
 
 882                 $image->flopImage(); // horizontal flop around y-axis
 
 883                 $image->rotateImage($background, 270);
 
 885             case \imagick::ORIENTATION_RIGHTTOP: // 6
 
 886                 $image->rotateImage($background, 90);
 
 888             case \imagick::ORIENTATION_RIGHTBOTTOM: // 7
 
 889                 $image->flipImage(); // vertical flip around x-axis
 
 890                 $image->rotateImage($background, 270);
 
 892             case \imagick::ORIENTATION_LEFTBOTTOM: // 8
 
 893                 $image->rotateImage($background, 270);
 
 898         $image->setImageOrientation(\imagick::ORIENTATION_TOPLEFT); // 1
 
 902     protected function imagick_create_scaled_image($file_name, $version, $options) {
 
 903         list($file_path, $new_file_path) =
 
 904             $this->get_scaled_image_file_paths($file_name, $version);
 
 905         $image = $this->imagick_get_image_object(
 
 907             !empty($options['crop']) || !empty($options['no_cache'])
 
 909         if (is_null($image)) return false;
 
 910         if ($image->getImageFormat() === 'GIF') {
 
 911             // Handle animated GIFs:
 
 912             $images = $image->coalesceImages();
 
 913             foreach ($images as $frame) {
 
 915                 $this->imagick_set_image_object($file_name, $image);
 
 919         $image_oriented = false;
 
 920         if (!empty($options['auto_orient'])) {
 
 921             $image_oriented = $this->imagick_orient_image($image);
 
 923         $image_resize = false;
 
 924         $new_width = $max_width = $img_width = $image->getImageWidth();
 
 925         $new_height = $max_height = $img_height = $image->getImageHeight();
 
 926         // use isset(). User might be setting max_width = 0 (auto in regular resizing). Value 0 would be considered empty when you use empty()
 
 927         if (isset($options['max_width'])) {
 
 928             $image_resize = true;
 
 929             $new_width = $max_width = $options['max_width'];
 
 931         if (isset($options['max_height'])) {
 
 932             $image_resize = true;
 
 933             $new_height = $max_height = $options['max_height'];
 
 935         $image_strip = (isset($options['strip']) ? $options['strip'] : false);
 
 936         if ( !$image_oriented && ($max_width >= $img_width) && ($max_height >= $img_height) && !$image_strip && empty($options["jpeg_quality"]) ) {
 
 937             if ($file_path !== $new_file_path) {
 
 938                 return copy($file_path, $new_file_path);
 
 942         $crop = (isset($options['crop']) ? $options['crop'] : false);
 
 947             if (($img_width / $img_height) >= ($max_width / $max_height)) {
 
 948                 $new_width = 0; // Enables proportional scaling based on max_height
 
 949                 $x = ($img_width / ($img_height / $max_height) - $max_width) / 2;
 
 951                 $new_height = 0; // Enables proportional scaling based on max_width
 
 952                 $y = ($img_height / ($img_width / $max_width) - $max_height) / 2;
 
 955         $success = $image->resizeImage(
 
 958             isset($options['filter']) ? $options['filter'] : \imagick::FILTER_LANCZOS,
 
 959             isset($options['blur']) ? $options['blur'] : 1,
 
 960             $new_width && $new_height // fit image into constraints if not to be cropped
 
 962         if ($success && $crop) {
 
 963             $success = $image->cropImage(
 
 970                 $success = $image->setImagePage($max_width, $max_height, 0, 0);
 
 973         $type = strtolower(substr(strrchr($file_name, '.'), 1));
 
 977                 if (!empty($options['jpeg_quality'])) {
 
 978                     $image->setImageCompression(\imagick::COMPRESSION_JPEG);
 
 979                     $image->setImageCompressionQuality($options['jpeg_quality']);
 
 983         if ( $image_strip ) {
 
 984             $image->stripImage();
 
 986         return $success && $image->writeImage($new_file_path);
 
 989     protected function imagemagick_create_scaled_image($file_name, $version, $options) {
 
 990         list($file_path, $new_file_path) =
 
 991             $this->get_scaled_image_file_paths($file_name, $version);
 
 992         $resize = @$options['max_width']
 
 993             .(empty($options['max_height']) ? '' : 'X'.$options['max_height']);
 
 994         if (!$resize && empty($options['auto_orient'])) {
 
 995             if ($file_path !== $new_file_path) {
 
 996                 return copy($file_path, $new_file_path);
 
1000         $cmd = $this->options['convert_bin'];
 
1001         if (!empty($this->options['convert_params'])) {
 
1002             $cmd .= ' '.$this->options['convert_params'];
 
1004         $cmd .= ' '.escapeshellarg($file_path);
 
1005         if (!empty($options['auto_orient'])) {
 
1006             $cmd .= ' -auto-orient';
 
1009             // Handle animated GIFs:
 
1010             $cmd .= ' -coalesce';
 
1011             if (empty($options['crop'])) {
 
1012                 $cmd .= ' -resize '.escapeshellarg($resize.'>');
 
1014                 $cmd .= ' -resize '.escapeshellarg($resize.'^');
 
1015                 $cmd .= ' -gravity center';
 
1016                 $cmd .= ' -crop '.escapeshellarg($resize.'+0+0');
 
1018             // Make sure the page dimensions are correct (fixes offsets of animated GIFs):
 
1021         if (!empty($options['convert_params'])) {
 
1022             $cmd .= ' '.$options['convert_params'];
 
1024         $cmd .= ' '.escapeshellarg($new_file_path);
 
1025         exec($cmd, $output, $error);
 
1027             error_log(implode('\n', $output));
 
1033     protected function get_image_size($file_path) {
 
1034         if ($this->options['image_library']) {
 
1035             if (extension_loaded('imagick')) {
 
1036                 $image = new \Imagick();
 
1038                     if (@$image->pingImage($file_path)) {
 
1039                         $dimensions = array($image->getImageWidth(), $image->getImageHeight());
 
1044                 } catch (\Exception $e) {
 
1045                     error_log($e->getMessage());
 
1048             if ($this->options['image_library'] === 2) {
 
1049                 $cmd = $this->options['identify_bin'];
 
1050                 $cmd .= ' -ping '.escapeshellarg($file_path);
 
1051                 exec($cmd, $output, $error);
 
1052                 if (!$error && !empty($output)) {
 
1053                     // image.jpg JPEG 1920x1080 1920x1080+0+0 8-bit sRGB 465KB 0.000u 0:00.000
 
1054                     $infos = preg_split('/\s+/', substr($output[0], strlen($file_path)));
 
1055                     $dimensions = preg_split('/x/', $infos[2]);
 
1061         if (!function_exists('getimagesize')) {
 
1062             error_log('Function not found: getimagesize');
 
1065         return @getimagesize($file_path);
 
1068     protected function create_scaled_image($file_name, $version, $options) {
 
1070             if ($this->options['image_library'] === 2) {
 
1071                 return $this->imagemagick_create_scaled_image($file_name, $version, $options);
 
1073             if ($this->options['image_library'] && extension_loaded('imagick')) {
 
1074                 return $this->imagick_create_scaled_image($file_name, $version, $options);
 
1076             return $this->gd_create_scaled_image($file_name, $version, $options);
 
1077         } catch (\Exception $e) {
 
1078             error_log($e->getMessage());
 
1083     protected function destroy_image_object($file_path) {
 
1084         if ($this->options['image_library'] && extension_loaded('imagick')) {
 
1085             return $this->imagick_destroy_image_object($file_path);
 
1089     protected function imagetype($file_path) {
 
1090         $fp = fopen($file_path, 'r');
 
1091         $data = fread($fp, 4);
 
1094         if ($data === 'GIF8') {
 
1095             return self::IMAGETYPE_GIF;
 
1098         if (bin2hex(substr($data, 0, 3)) === 'ffd8ff') {
 
1099             return self::IMAGETYPE_JPEG;
 
1102         if (bin2hex(@$data[0]).substr($data, 1, 4) === '89PNG') {
 
1103             return self::IMAGETYPE_PNG;
 
1108     protected function is_valid_image_file($file_path) {
 
1109         return !!$this->imagetype($file_path);
 
1112     protected function has_image_file_extension($file_path) {
 
1113         return !!preg_match('/\.(gif|jpe?g|png)$/i', $file_path);
 
1116     protected function handle_image_file($file_path, $file) {
 
1117         $failed_versions = array();
 
1118         foreach ($this->options['image_versions'] as $version => $options) {
 
1119             if ($this->create_scaled_image($file->name, $version, $options)) {
 
1120                 if (!empty($version)) {
 
1121                     $file->{$version.'Url'} = $this->get_download_url(
 
1126                     $file->size = $this->get_file_size($file_path, true);
 
1129                 $failed_versions[] = $version ? $version : 'original';
 
1132         if (count($failed_versions)) {
 
1133             $file->error = $this->get_error_message('image_resize')
 
1134                 .' ('.implode(', ', $failed_versions).')';
 
1137         $this->destroy_image_object($file_path);
 
1140     protected function handle_file_upload($uploaded_file, $name, $size, $type, $error,
 
1141         $index = null, $content_range = null) {
 
1142         $file = new \stdClass();
 
1143         $file->name = $this->get_file_name($uploaded_file, $name, $size, $type, $error,
 
1144             $index, $content_range);
 
1145         $file->size = $this->fix_integer_overflow((int)$size);
 
1146         $file->type = $type;
 
1147         if ($this->validate($uploaded_file, $file, $error, $index, $content_range)) {
 
1148             $this->handle_form_data($file, $index);
 
1149             $upload_dir = $this->get_upload_path();
 
1150             if (!is_dir($upload_dir)) {
 
1151                 mkdir($upload_dir, $this->options['mkdir_mode'], true);
 
1153             $file_path = $this->get_upload_path($file->name);
 
1154             $append_file = $content_range && is_file($file_path) &&
 
1155                 $file->size > $this->get_file_size($file_path);
 
1156             if ($uploaded_file && is_uploaded_file($uploaded_file)) {
 
1157                 // multipart/formdata uploads (POST method uploads)
 
1161                         fopen($uploaded_file, 'r'),
 
1165                     move_uploaded_file($uploaded_file, $file_path);
 
1168                 // Non-multipart uploads (PUT method support)
 
1171                     fopen($this->options['input_stream'], 'r'),
 
1172                     $append_file ? FILE_APPEND : 0
 
1175             $file_size = $this->get_file_size($file_path, $append_file);
 
1176             if ($file_size === $file->size) {
 
1177                 $file->url = $this->get_download_url($file->name);
 
1178                 if ($this->has_image_file_extension($file->name)) {
 
1179                     if ($content_range && !$this->validate_image_file($file_path, $file, $error, $index)) {
 
1182                         $this->handle_image_file($file_path, $file);
 
1186                 $file->size = $file_size;
 
1187                 if (!$content_range && $this->options['discard_aborted_uploads']) {
 
1189                     $file->error = $this->get_error_message('abort');
 
1192             $this->set_additional_file_properties($file);
 
1197     protected function readfile($file_path) {
 
1198         $file_size = $this->get_file_size($file_path);
 
1199         $chunk_size = $this->options['readfile_chunk_size'];
 
1200         if ($chunk_size && $file_size > $chunk_size) {
 
1201             $handle = fopen($file_path, 'rb');
 
1202             while (!feof($handle)) {
 
1203                 echo fread($handle, $chunk_size);
 
1210         return readfile($file_path);
 
1213     protected function body($str) {
 
1217     protected function header($str) {
 
1221     protected function get_upload_data($id) {
 
1222         return @$_FILES[$id];
 
1225     protected function get_post_param($id) {
 
1226         return @$_POST[$id];
 
1229     protected function get_query_param($id) {
 
1233     protected function get_server_var($id) {
 
1234         return @$_SERVER[$id];
 
1237     protected function handle_form_data($file, $index) {
 
1238         // Handle form data, e.g. $_POST['description'][$index]
 
1241     protected function get_version_param() {
 
1242         return $this->basename(stripslashes($this->get_query_param('version')));
 
1245     protected function get_singular_param_name() {
 
1246         return substr($this->options['param_name'], 0, -1);
 
1249     protected function get_file_name_param() {
 
1250         $name = $this->get_singular_param_name();
 
1251         return $this->basename(stripslashes($this->get_query_param($name)));
 
1254     protected function get_file_names_params() {
 
1255         $params = $this->get_query_param($this->options['param_name']);
 
1259         foreach ($params as $key => $value) {
 
1260             $params[$key] = $this->basename(stripslashes($value));
 
1265     protected function get_file_type($file_path) {
 
1266         switch (strtolower(pathinfo($file_path, PATHINFO_EXTENSION))) {
 
1269                 return self::IMAGETYPE_JPEG;
 
1271                 return self::IMAGETYPE_PNG;
 
1273                 return self::IMAGETYPE_GIF;
 
1279     protected function download() {
 
1280         switch ($this->options['download_via_php']) {
 
1282                 $redirect_header = null;
 
1285                 $redirect_header = 'X-Sendfile';
 
1288                 $redirect_header = 'X-Accel-Redirect';
 
1291                 return $this->header('HTTP/1.1 403 Forbidden');
 
1293         $file_name = $this->get_file_name_param();
 
1294         if (!$this->is_valid_file_object($file_name)) {
 
1295             return $this->header('HTTP/1.1 404 Not Found');
 
1297         if ($redirect_header) {
 
1298             return $this->header(
 
1299                 $redirect_header.': '.$this->get_download_url(
 
1301                     $this->get_version_param(),
 
1306         $file_path = $this->get_upload_path($file_name, $this->get_version_param());
 
1307         // Prevent browsers from MIME-sniffing the content-type:
 
1308         $this->header('X-Content-Type-Options: nosniff');
 
1309         if (!preg_match($this->options['inline_file_types'], $file_name)) {
 
1310             $this->header('Content-Type: application/octet-stream');
 
1311             $this->header('Content-Disposition: attachment; filename="'.$file_name.'"');
 
1313             $this->header('Content-Type: '.$this->get_file_type($file_path));
 
1314             $this->header('Content-Disposition: inline; filename="'.$file_name.'"');
 
1316         $this->header('Content-Length: '.$this->get_file_size($file_path));
 
1317         $this->header('Last-Modified: '.gmdate('D, d M Y H:i:s T', filemtime($file_path)));
 
1318         $this->readfile($file_path);
 
1321     protected function send_content_type_header() {
 
1322         $this->header('Vary: Accept');
 
1323         if (strpos($this->get_server_var('HTTP_ACCEPT'), 'application/json') !== false) {
 
1324             $this->header('Content-type: application/json');
 
1326             $this->header('Content-type: text/plain');
 
1330     protected function send_access_control_headers() {
 
1331         $this->header('Access-Control-Allow-Origin: '.$this->options['access_control_allow_origin']);
 
1332         $this->header('Access-Control-Allow-Credentials: '
 
1333             .($this->options['access_control_allow_credentials'] ? 'true' : 'false'));
 
1334         $this->header('Access-Control-Allow-Methods: '
 
1335             .implode(', ', $this->options['access_control_allow_methods']));
 
1336         $this->header('Access-Control-Allow-Headers: '
 
1337             .implode(', ', $this->options['access_control_allow_headers']));
 
1340     public function generate_response($content, $print_response = true) {
 
1341         $this->response = $content;
 
1342         if ($print_response) {
 
1343             $json = json_encode($content);
 
1344             $redirect = stripslashes($this->get_post_param('redirect'));
 
1345             if ($redirect && preg_match($this->options['redirect_allow_target'], $redirect)) {
 
1346                 return $this->header('Location: '.sprintf($redirect, rawurlencode($json)));
 
1349             if ($this->get_server_var('HTTP_CONTENT_RANGE')) {
 
1350                 $files = isset($content[$this->options['param_name']]) ?
 
1351                     $content[$this->options['param_name']] : null;
 
1352                 if ($files && is_array($files) && is_object($files[0]) && $files[0]->size) {
 
1353                     $this->header('Range: 0-'.(
 
1354                         $this->fix_integer_overflow((int)$files[0]->size) - 1
 
1363     public function get_response () {
 
1364         return $this->response;
 
1367     public function head() {
 
1368         $this->header('Pragma: no-cache');
 
1369         $this->header('Cache-Control: no-store, no-cache, must-revalidate');
 
1370         $this->header('Content-Disposition: inline; filename="files.json"');
 
1371         // Prevent Internet Explorer from MIME-sniffing the content-type:
 
1372         $this->header('X-Content-Type-Options: nosniff');
 
1373         if ($this->options['access_control_allow_origin']) {
 
1374             $this->send_access_control_headers();
 
1376         $this->send_content_type_header();
 
1379     public function get($print_response = true) {
 
1380         if ($print_response && $this->get_query_param('download')) {
 
1381             return $this->download();
 
1383         $file_name = $this->get_file_name_param();
 
1386                 $this->get_singular_param_name() => $this->get_file_object($file_name)
 
1390                 $this->options['param_name'] => $this->get_file_objects()
 
1393         return $this->generate_response($response, $print_response);
 
1396     public function post($print_response = true) {
 
1397         if ($this->get_query_param('_method') === 'DELETE') {
 
1398             return $this->delete($print_response);
 
1400         $upload = $this->get_upload_data($this->options['param_name']);
 
1401         // Parse the Content-Disposition header, if available:
 
1402         $content_disposition_header = $this->get_server_var('HTTP_CONTENT_DISPOSITION');
 
1403         $file_name = $content_disposition_header ?
 
1404             rawurldecode(preg_replace(
 
1407                 $content_disposition_header
 
1409         // Parse the Content-Range header, which has the following form:
 
1410         // Content-Range: bytes 0-524287/2000000
 
1411         $content_range_header = $this->get_server_var('HTTP_CONTENT_RANGE');
 
1412         $content_range = $content_range_header ?
 
1413             preg_split('/[^0-9]+/', $content_range_header) : null;
 
1414         $size =  @$content_range[3];
 
1417             if (is_array($upload['tmp_name'])) {
 
1418                 // param_name is an array identifier like "files[]",
 
1419                 // $upload is a multi-dimensional array:
 
1420                 foreach ($upload['tmp_name'] as $index => $value) {
 
1421                     $files[] = $this->handle_file_upload(
 
1422                         $upload['tmp_name'][$index],
 
1423                         $file_name ? $file_name : $upload['name'][$index],
 
1424                         $size ? $size : $upload['size'][$index],
 
1425                         $upload['type'][$index],
 
1426                         $upload['error'][$index],
 
1432                 // param_name is a single object identifier like "file",
 
1433                 // $upload is a one-dimensional array:
 
1434                 $files[] = $this->handle_file_upload(
 
1435                     isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
 
1436                     $file_name ? $file_name : (isset($upload['name']) ?
 
1437                         $upload['name'] : null),
 
1438                     $size ? $size : (isset($upload['size']) ?
 
1439                         $upload['size'] : $this->get_server_var('CONTENT_LENGTH')),
 
1440                     isset($upload['type']) ?
 
1441                         $upload['type'] : $this->get_server_var('CONTENT_TYPE'),
 
1442                     isset($upload['error']) ? $upload['error'] : null,
 
1448         $response = array($this->options['param_name'] => $files);
 
1449         return $this->generate_response($response, $print_response);
 
1452     public function delete($print_response = true) {
 
1453         $file_names = $this->get_file_names_params();
 
1454         if (empty($file_names)) {
 
1455             $file_names = array($this->get_file_name_param());
 
1457         $response = array();
 
1458         foreach ($file_names as $file_name) {
 
1459             $file_path = $this->get_upload_path($file_name);
 
1460             $success = strlen($file_name) > 0 && $file_name[0] !== '.' && is_file($file_path) && unlink($file_path);
 
1462                 foreach ($this->options['image_versions'] as $version => $options) {
 
1463                     if (!empty($version)) {
 
1464                         $file = $this->get_upload_path($file_name, $version);
 
1465                         if (is_file($file)) {
 
1471             $response[$file_name] = $success;
 
1473         return $this->generate_response($response, $print_response);
 
1476     protected function basename($filepath, $suffix = null) {
 
1477         $splited = preg_split('/\//', rtrim ($filepath, '/ '));
 
1478         return substr(basename('X'.$splited[count($splited)-1], $suffix), 1);