-
-
Save voku/6ca2740380d87f22b82fc2a4c09b5503 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* jQuery File Upload Plugin PHP Class 8.4.1 | |
* https://github.com/blueimp/jQuery-File-Upload | |
* | |
* Copyright 2010, Sebastian Tschan | |
* https://blueimp.net | |
* | |
* Licensed under the MIT license: | |
* http://www.opensource.org/licenses/MIT | |
*/ | |
final class UploadHandler { | |
/** | |
* @var null|array | |
*/ | |
private $options; | |
/** | |
* @var array | |
*/ | |
public $response; | |
// PHP File Upload error message codes: | |
// http://php.net/manual/en/features.file-upload.errors.php | |
private $error_messages = [ | |
1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini', | |
2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', | |
3 => 'The uploaded file was only partially uploaded', | |
4 => 'No file was uploaded', | |
6 => 'Missing a temporary folder', | |
7 => 'Failed to write file to disk', | |
8 => 'A PHP extension stopped the file upload', | |
'post_max_size' => 'The uploaded file exceeds the post_max_size directive in php.ini', | |
'max_file_size' => 'File is too big', | |
'min_file_size' => 'File is too small', | |
'accept_file_types' => 'Filetype not allowed', | |
'max_number_of_files' => 'Maximum number of files exceeded', | |
'max_width' => 'Image exceeds maximum width', | |
'min_width' => 'Image requires a minimum width', | |
'max_height' => 'Image exceeds maximum height', | |
'min_height' => 'Image requires a minimum height', | |
'abort' => 'File upload aborted', | |
'image_resize' => 'Failed to resize image', | |
]; | |
/** | |
* @var array|\Imagick[]|resource[] | |
*/ | |
private $image_objects = []; | |
/** | |
* UploadHandler constructor. | |
* | |
* @param null|array $options | |
* @param bool $initialize | |
* @param null|string $error_messages | |
*/ | |
public function __construct($options = null, $initialize = true, $error_messages = null) { | |
$this->response = []; | |
$this->options = [ | |
'script_url' => $this->get_full_url() . '/' . basename($this->get_server_var('SCRIPT_NAME')), | |
'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')) . '/files/', | |
'upload_url' => $this->get_full_url() . '/files/', | |
'user_dirs' => false, | |
'mkdir_mode' => 0755, | |
'param_name' => 'files', | |
// Set the following option to 'POST', if your server does not support | |
// DELETE requests. This is a parameter sent to the client: | |
'delete_type' => 'DELETE', | |
'access_control_allow_origin' => '*', | |
'access_control_allow_credentials' => false, | |
'access_control_allow_methods' => [ | |
'OPTIONS', | |
'HEAD', | |
'GET', | |
'POST', | |
'PUT', | |
'PATCH', | |
'DELETE', | |
], | |
'access_control_allow_headers' => [ | |
'Content-Type', | |
'Content-Range', | |
'Content-Disposition', | |
], | |
// By default, allow redirects to the referer protocol+host: | |
'redirect_allow_target' => '/^' . preg_quote( | |
parse_url($this->get_server_var('HTTP_REFERER'), \PHP_URL_SCHEME) | |
. '://' | |
. parse_url($this->get_server_var('HTTP_REFERER'), \PHP_URL_HOST) | |
. '/', // Trailing slash to not match subdomains by mistake | |
'/' // preg_quote delimiter param | |
) . '/', | |
// Enable to provide file downloads via GET requests to the PHP script: | |
// 1. Set to 1 to download files via readfile method through PHP | |
// 2. Set to 2 to send a X-Sendfile header for lighttpd/Apache | |
// 3. Set to 3 to send a X-Accel-Redirect header for nginx | |
// If set to 2 or 3, adjust the upload_url option to the base path of | |
// the redirect parameter, e.g. '/files/'. | |
'download_via_php' => 1, | |
// Read files in chunks to avoid memory limits when download_via_php | |
// is enabled, set to 0 to disable chunked reading of files: | |
'readfile_chunk_size' => 10 * 1024 * 1024, // 10 MiB | |
// Defines which files can be displayed inline when downloaded: | |
'inline_file_types' => '/\.(gif|jpe?g|png)$/i', | |
// Defines which files (based on their names) are accepted for upload: | |
'accept_file_types' => '/.+$/i', | |
// The php.ini settings upload_max_filesize and post_max_size | |
// take precedence over the following max_file_size setting: | |
'max_file_size' => null, | |
'min_file_size' => 1, | |
// The maximum number of files for the upload directory: | |
'max_number_of_files' => null, | |
// Defines which files are handled as image files: | |
'image_file_types' => '/\.(gif|jpe?g|png)$/i', | |
// Use exif_imagetype on all files to correct file extensions: | |
'correct_image_extensions' => false, | |
// Image resolution restrictions: | |
'max_width' => null, | |
'max_height' => null, | |
'min_width' => 1, | |
'min_height' => 1, | |
// Set the following option to false to enable resumable uploads: | |
'discard_aborted_uploads' => true, | |
// Set to 0 to use the GD library to scale and orient images, | |
// set to 1 to use imagick (if installed, falls back to GD), | |
// set to 2 to use the ImageMagick convert binary directly: | |
'image_library' => 1, | |
// Uncomment the following to define an array of resource limits | |
// for imagick: | |
/* | |
'imagick_resource_limits' => array( | |
imagick::RESOURCETYPE_MAP => 32, | |
imagick::RESOURCETYPE_MEMORY => 32 | |
), | |
*/ | |
// Command or path for to the ImageMagick convert binary: | |
'convert_bin' => 'convert', | |
// Uncomment the following to add parameters in front of each | |
// ImageMagick convert call (the limit constraints seem only | |
// to have an effect if put in front): | |
/* | |
'convert_params' => '-limit memory 32MiB -limit map 32MiB', | |
*/ | |
// Command or path for to the ImageMagick identify binary: | |
'identify_bin' => 'identify', | |
'image_versions' => [ | |
// The empty image version key defines options for the original image: | |
'' => [ | |
// Automatically rotate images based on EXIF meta data: | |
'auto_orient' => false, | |
], | |
// Uncomment the following to create medium sized images: | |
/* | |
'medium' => array( | |
'max_width' => 800, | |
'max_height' => 600 | |
), | |
*/ | |
'thumbnail' => [ | |
// Uncomment the following to use a defined directory for the thumbnails | |
// instead of a subdirectory based on the version identifier. | |
// Make sure that this directory doesn't allow execution of files if you | |
// don't pose any restrictions on the type of uploaded files, e.g. by | |
// copying the .htaccess file from the files directory for Apache: | |
//'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/thumb/', | |
//'upload_url' => $this->get_full_url().'/thumb/', | |
// Uncomment the following to force the max | |
// dimensions and e.g. create square thumbnails: | |
//'crop' => true, | |
'max_width' => 80, | |
'max_height' => 80, | |
], | |
], | |
'print_response' => false, | |
]; | |
if ($options) { | |
$this->options = $options + $this->options; | |
} | |
if ($error_messages) { | |
$this->error_messages = $error_messages + $this->error_messages; | |
} | |
if ($initialize) { | |
$this->initialize(); | |
} | |
} | |
/** | |
* @return void | |
*/ | |
private function initialize() { | |
switch ($this->get_server_var('REQUEST_METHOD')) { | |
case 'OPTIONS': | |
case 'HEAD': | |
$this->head(); | |
break; | |
case 'GET': | |
$this->get($this->options['print_response']); | |
break; | |
case 'PATCH': | |
case 'PUT': | |
case 'POST': | |
$this->post($this->options['print_response']); | |
break; | |
case 'DELETE': | |
$this->delete($this->options['print_response']); | |
break; | |
default: | |
$this->header('HTTP/1.1 405 Method Not Allowed'); | |
} | |
} | |
/** | |
* @return string | |
*/ | |
private function get_full_url() { | |
$https = | |
( | |
!empty($_SERVER['HTTPS']) | |
&& | |
strcasecmp($_SERVER['HTTPS'], 'on') === 0 | |
) | |
|| | |
( | |
!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) | |
&& | |
strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0 | |
); | |
return ($https ? 'https://' : 'http://') . | |
(!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] . '@' : '') . | |
($_SERVER['HTTP_HOST'] ?? ($_SERVER['SERVER_NAME'] . ((($https && $_SERVER['SERVER_PORT'] === 443) || $_SERVER['SERVER_PORT'] === 80) ? '' : ':' . $_SERVER['SERVER_PORT']))) . | |
substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/')); | |
} | |
/** | |
* @return string | |
*/ | |
private function get_user_id() { | |
/* @noinspection PhpUsageOfSilenceOperatorInspection */ | |
@session_start(); | |
return session_id(); | |
} | |
/** | |
* @return string | |
*/ | |
private function get_user_path() { | |
if ($this->options['user_dirs']) { | |
return $this->get_user_id() . '/'; | |
} | |
return ''; | |
} | |
/** | |
* @param null|string $file_name | |
* @param null|string $version | |
* | |
* @return string | |
*/ | |
private function get_upload_path($file_name = null, $version = null) { | |
$file_name = $file_name ? $file_name : ''; | |
if (empty($version)) { | |
$version_path = ''; | |
} else { | |
$version_dir = $this->options['image_versions'][$version]['upload_dir'] ?? ''; | |
if ($version_dir) { | |
return $version_dir . $this->get_user_path() . $file_name; | |
} | |
$version_path = $version . '/'; | |
} | |
return $this->options['upload_dir'] . $this->get_user_path() | |
. $version_path . $file_name; | |
} | |
/** | |
* @param string $url | |
* | |
* @return string | |
*/ | |
private function get_query_separator($url) { | |
return strpos($url, '?') === false ? '?' : '&'; | |
} | |
/** | |
* @param string $file_name | |
* @param null|string $version | |
* @param bool $direct | |
* | |
* @return string | |
*/ | |
private function get_download_url($file_name, $version = null, $direct = false) { | |
if (!$direct && $this->options['download_via_php']) { | |
$url = $this->options['script_url'] | |
. $this->get_query_separator($this->options['script_url']) | |
. $this->get_singular_param_name() | |
. '=' . rawurlencode($file_name); | |
if ($version) { | |
$url .= '&version=' . rawurlencode($version); | |
} | |
return $url . '&download=1'; | |
} | |
if (empty($version)) { | |
$version_path = ''; | |
} else { | |
$version_url = $this->options['image_versions'][$version]['upload_url'] ?? ''; | |
if ($version_url) { | |
return $version_url . $this->get_user_path() . rawurlencode($file_name); | |
} | |
$version_path = rawurlencode($version) . '/'; | |
} | |
return $this->options['upload_url'] . $this->get_user_path() . $version_path . rawurlencode($file_name); | |
} | |
/** | |
* @param \stdClass $file | |
*/ | |
private function set_additional_file_properties($file) { | |
$file->deleteUrl = $this->options['script_url'] | |
. $this->get_query_separator($this->options['script_url']) | |
. $this->get_singular_param_name() | |
. '=' . rawurlencode($file->name); | |
$file->deleteType = $this->options['delete_type']; | |
if ($file->deleteType !== 'DELETE') { | |
$file->deleteUrl .= '&_method=DELETE'; | |
} | |
if ($this->options['access_control_allow_credentials']) { | |
$file->deleteWithCredentials = true; | |
} | |
} | |
/** | |
* Fix for overflowing signed 32 bit integers, | |
* works for sizes up to 2^32-1 bytes (4 GiB - 1): | |
* | |
* @param int $size | |
* | |
* @return float|int | |
*/ | |
private function fix_integer_overflow($size) { | |
if ($size < 0) { | |
$size += 2.0 * (\PHP_INT_MAX + 1); | |
} | |
return $size; | |
} | |
/** | |
* @param string $file_path | |
* @param bool $clear_stat_cache | |
* | |
* @return float | |
*/ | |
private function get_file_size($file_path, $clear_stat_cache = false) { | |
if ($clear_stat_cache) { | |
if (version_compare(\PHP_VERSION, '5.3.0') >= 0) { | |
clearstatcache(true, $file_path); | |
} else { | |
clearstatcache(); | |
} | |
} | |
return $this->fix_integer_overflow(filesize($file_path)); | |
} | |
/** | |
* @param string $file_name | |
* | |
* @return bool | |
*/ | |
private function is_valid_file_object($file_name) { | |
$file_path = $this->get_upload_path($file_name); | |
if (is_file($file_path) && $file_name[0] !== '.') { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* @param string $file_name | |
* | |
* @return null|stdClass | |
*/ | |
private function get_file_object($file_name) { | |
if ($this->is_valid_file_object($file_name)) { | |
$file = new \stdClass(); | |
$file->name = $file_name; | |
$file->size = $this->get_file_size( | |
$this->get_upload_path($file_name) | |
); | |
$file->url = $this->get_download_url($file->name); | |
foreach ($this->options['image_versions'] as $version => $options) { | |
if (!empty($version)) { | |
if (is_file($this->get_upload_path($file_name, $version))) { | |
$file->{$version . 'Url'} = $this->get_download_url( | |
$file->name, | |
$version | |
); | |
} | |
} | |
} | |
$this->set_additional_file_properties($file); | |
return $file; | |
} | |
return null; | |
} | |
/** | |
* @param string $iteration_method | |
* | |
* @return array | |
*/ | |
private function get_file_objects($iteration_method = 'get_file_object') { | |
$upload_dir = $this->get_upload_path(); | |
if (!is_dir($upload_dir)) { | |
return []; | |
} | |
return array_values( | |
array_filter( | |
array_map( | |
[$this, $iteration_method], | |
scandir($upload_dir, \SCANDIR_SORT_ASCENDING) | |
) | |
) | |
); | |
} | |
/** | |
* @return int | |
*/ | |
private function count_file_objects() { | |
return count($this->get_file_objects('is_valid_file_object')); | |
} | |
/** | |
* @param int|string $error | |
* | |
* @return mixed | |
*/ | |
private function get_error_message($error) { | |
return $this->error_messages[$error] ?? $error; | |
} | |
/** | |
* @param string $val | |
* | |
* @return float | |
*/ | |
public function get_config_bytes($val) { | |
$val = trim($val); | |
$last = strtolower($val[strlen($val) - 1]); | |
switch ($last) { | |
case 'g': | |
$val *= 1024; | |
// no break | |
case 'm': | |
$val *= 1024; | |
// no break | |
case 'k': | |
$val *= 1024; | |
} | |
return $this->fix_integer_overflow($val); | |
} | |
/** | |
* @param string $uploaded_file | |
* @param \stdClass $file | |
* @param int|string $error | |
* @param null $index <p>Warning: not used in the code</p> | |
* | |
* @return bool | |
*/ | |
private function validate($uploaded_file, $file, $error, $index) { | |
if ($error) { | |
$file->error = $this->get_error_message($error); | |
return false; | |
} | |
$content_length = $this->fix_integer_overflow((int)$this->get_server_var('CONTENT_LENGTH')); | |
$post_max_size = $this->get_config_bytes(ini_get('post_max_size')); | |
if ($post_max_size && ($content_length > $post_max_size)) { | |
$file->error = $this->get_error_message('post_max_size'); | |
return false; | |
} | |
if (!preg_match($this->options['accept_file_types'], $file->name)) { | |
$file->error = $this->get_error_message('accept_file_types'); | |
return false; | |
} | |
if ($uploaded_file && is_uploaded_file($uploaded_file)) { | |
$file_size = $this->get_file_size($uploaded_file); | |
} else { | |
$file_size = $content_length; | |
} | |
if ( | |
$this->options['max_file_size'] | |
&& | |
( | |
$file_size > $this->options['max_file_size'] | |
|| | |
$file->size > $this->options['max_file_size'] | |
) | |
) { | |
$file->error = $this->get_error_message('max_file_size'); | |
return false; | |
} | |
if ( | |
$this->options['min_file_size'] | |
&& | |
$file_size < $this->options['min_file_size'] | |
) { | |
$file->error = $this->get_error_message('min_file_size'); | |
return false; | |
} | |
if ( | |
is_int($this->options['max_number_of_files']) | |
&& | |
($this->count_file_objects() >= $this->options['max_number_of_files']) | |
&& | |
// Ignore additional chunks of existing files: | |
!is_file($this->get_upload_path($file->name)) | |
) { | |
$file->error = $this->get_error_message('max_number_of_files'); | |
return false; | |
} | |
$max_width = $this->options['max_width']; | |
$max_height = $this->options['max_height']; | |
$min_width = $this->options['min_width']; | |
$min_height = $this->options['min_height']; | |
if ( | |
( | |
$max_width | |
|| | |
$max_height | |
|| | |
$min_width | |
|| | |
$min_height | |
) | |
&& | |
preg_match($this->options['image_file_types'], $file->name) | |
) { | |
list($img_width, $img_height) = $this->get_image_size($uploaded_file); | |
// If we are auto rotating the image by default, do the checks on | |
// the correct orientation | |
/* @noinspection PhpUsageOfSilenceOperatorInspection */ | |
if ( | |
$this->options['image_versions']['']['auto_orient'] | |
&& | |
function_exists('exif_read_data') | |
&& | |
($exif = @exif_read_data($uploaded_file)) | |
&& | |
(((int)isset($exif['Orientation']) ? $exif['Orientation'] : '') >= 5) | |
) { | |
$tmp = $img_width; | |
$img_width = $img_height; | |
$img_height = $tmp; | |
unset($tmp); | |
} | |
} | |
if (!empty($img_width) && !empty($img_height)) { | |
if ($max_width && $img_width > $max_width) { | |
$file->error = $this->get_error_message('max_width'); | |
return false; | |
} | |
if ($max_height && $img_height > $max_height) { | |
$file->error = $this->get_error_message('max_height'); | |
return false; | |
} | |
if ($min_width && $img_width < $min_width) { | |
$file->error = $this->get_error_message('min_width'); | |
return false; | |
} | |
if ($min_height && $img_height < $min_height) { | |
$file->error = $this->get_error_message('min_height'); | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* @param array $matches | |
* | |
* @return string | |
*/ | |
private function upcount_name_callback($matches) { | |
$index = isset($matches[1]) ? ((int)$matches[1]) + 1 : 1; | |
$ext = $matches[2] ?? ''; | |
return ' (' . $index . ')' . $ext; | |
} | |
/** | |
* @param string $name | |
* | |
* @return null|string|string[] | |
*/ | |
private function upcount_name($name) { | |
return preg_replace_callback( | |
'/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/', | |
[$this, 'upcount_name_callback'], | |
$name, | |
1 | |
); | |
} | |
/** | |
* @param null $file_path <p>Warning: not used in the code</p> | |
* @param string $name | |
* @param null $size <p>Warning: not used in the code</p> | |
* @param null $type <p>Warning: not used in the code</p> | |
* @param null $error <p>Warning: not used in the code</p> | |
* @param null $index <p>Warning: not used in the code</p> | |
* @param int $content_range | |
* | |
* @return null|string|string[] | |
*/ | |
private function get_unique_filename( | |
$file_path, | |
$name, | |
$size, | |
$type, | |
$error, | |
$index, | |
$content_range | |
) { | |
while (is_dir($this->get_upload_path($name))) { | |
$name = $this->upcount_name($name); | |
} | |
// Keep an existing filename if this is part of a chunked upload: | |
$uploaded_bytes = $this->fix_integer_overflow((int)$content_range[1]); | |
while (is_file($this->get_upload_path($name))) { | |
if ($uploaded_bytes === $this->get_file_size($this->get_upload_path($name))) { | |
break; | |
} | |
$name = $this->upcount_name($name); | |
} | |
return $name; | |
} | |
/** | |
* @param string $file_path | |
* @param string $name | |
* @param null $size <p>Warning: not used in the code</p> | |
* @param string $type | |
* @param null $error <p>Warning: not used in the code</p> | |
* @param null $index <p>Warning: not used in the code</p> | |
* @param null $content_range <p>Warning: not used in the code</p> | |
* | |
* @return string | |
*/ | |
private function fix_file_extension( | |
$file_path, | |
$name, | |
$size, | |
$type, | |
$error, | |
$index, | |
$content_range | |
) { | |
// Add missing file extension for known image types: | |
if (strpos($name, '.') === false && | |
preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) { | |
$name .= '.' . $matches[1]; | |
} | |
if ($this->options['correct_image_extensions'] && | |
function_exists('exif_imagetype')) { | |
/* @noinspection PhpUsageOfSilenceOperatorInspection */ | |
switch (@exif_imagetype($file_path)) { | |
case \IMAGETYPE_JPEG: | |
$extensions = ['jpg', 'jpeg']; | |
break; | |
case \IMAGETYPE_PNG: | |
$extensions = ['png']; | |
break; | |
case \IMAGETYPE_GIF: | |
$extensions = ['gif']; | |
break; | |
} | |
// Adjust incorrect image file extensions: | |
if (!empty($extensions)) { | |
$parts = explode('.', $name); | |
$extIndex = count($parts) - 1; | |
$ext = strtolower($parts[$extIndex]); | |
if (!in_array($ext, $extensions)) { | |
$parts[$extIndex] = $extensions[0]; | |
$name = implode('.', $parts); | |
} | |
} | |
} | |
return $name; | |
} | |
/** | |
* @param null $file_path <p>Warning: not used in the code</p> | |
* @param string $name | |
* @param null $size <p>Warning: not used in the code</p> | |
* @param null $type <p>Warning: not used in the code</p> | |
* @param null $error <p>Warning: not used in the code</p> | |
* @param null $index <p>Warning: not used in the code</p> | |
* @param null $content_range <p>Warning: not used in the code</p> | |
* | |
* @return mixed|string | |
*/ | |
private function trim_file_name( | |
$file_path, | |
$name, | |
$size, | |
$type, | |
$error, | |
$index, | |
$content_range | |
) { | |
// Remove path information and dots around the filename, to prevent uploading | |
// into different directories or replacing hidden system files. | |
// Also remove control characters and spaces (\x00..\x20) around the filename: | |
$name = trim(basename(stripslashes($name)), ".\x00..\x20"); | |
// Use a timestamp for empty filenames: | |
if (!$name) { | |
$name = str_replace('.', '-', microtime(true)); | |
} | |
return $name; | |
} | |
/** | |
* @param string $file_path | |
* @param string $name | |
* @param int $size | |
* @param string $type | |
* @param mixed $error | |
* @param int|string $index | |
* @param int $content_range | |
* | |
* @return null|string|string[] | |
*/ | |
private function get_file_name( | |
$file_path, | |
$name, | |
$size, | |
$type, | |
$error, | |
$index, | |
$content_range | |
) { | |
$name = $this->trim_file_name($file_path, $name, $size, $type, $error, $index, $content_range); | |
return $this->get_unique_filename( | |
$file_path, | |
$this->fix_file_extension($file_path, $name, $size, $type, $error, $index, $content_range), | |
$size, | |
$type, | |
$error, | |
$index, | |
$content_range | |
); | |
} | |
/** | |
* @param string $file_name | |
* @param string $version | |
* | |
* @return array | |
*/ | |
private function get_scaled_image_file_paths($file_name, $version) { | |
$file_path = $this->get_upload_path($file_name); | |
if (!empty($version)) { | |
$version_dir = $this->get_upload_path(null, $version); | |
if (!is_dir($version_dir)) { | |
if ( | |
!mkdir($version_dir, $this->options['mkdir_mode'], true) | |
&& | |
!is_dir($version_dir) | |
) { | |
throw new \RuntimeException(sprintf('Directory "%s" was not created', $version_dir)); | |
} | |
} | |
$new_file_path = $version_dir . '/' . $file_name; | |
} else { | |
$new_file_path = $file_path; | |
} | |
return [$file_path, $new_file_path]; | |
} | |
/** | |
* @param string $file_path | |
* @param string $func | |
* @param bool $no_cache | |
* | |
* @return mixed | |
*/ | |
private function gd_get_image_object($file_path, $func, $no_cache = false) { | |
if (empty($this->image_objects[$file_path]) || $no_cache) { | |
$this->gd_destroy_image_object($file_path); | |
$this->image_objects[$file_path] = $func($file_path); | |
} | |
return $this->image_objects[$file_path]; | |
} | |
/** | |
* @param string $file_path | |
* @param resource $image | |
*/ | |
private function gd_set_image_object($file_path, $image) { | |
$this->gd_destroy_image_object($file_path); | |
$this->image_objects[$file_path] = $image; | |
} | |
/** | |
* @param string $file_path | |
* | |
* @return bool | |
*/ | |
private function gd_destroy_image_object($file_path) { | |
$image = $this->image_objects[$file_path] ?? null; | |
return $image && imagedestroy($image); | |
} | |
/** | |
* @param resource $image | |
* @param int $mode | |
* | |
* @return bool|resource | |
*/ | |
private function gd_imageflip($image, $mode) { | |
if (function_exists('imageflip')) { | |
return imageflip($image, $mode); | |
} | |
$new_width = $src_width = imagesx($image); | |
$new_height = $src_height = imagesy($image); | |
$new_img = imagecreatetruecolor($new_width, $new_height); | |
$src_x = 0; | |
$src_y = 0; | |
switch ($mode) { | |
case '1': // flip on the horizontal axis | |
$src_y = $new_height - 1; | |
$src_height = -$new_height; | |
break; | |
case '2': // flip on the vertical axis | |
$src_x = $new_width - 1; | |
$src_width = -$new_width; | |
break; | |
case '3': // flip on both axes | |
$src_y = $new_height - 1; | |
$src_height = -$new_height; | |
$src_x = $new_width - 1; | |
$src_width = -$new_width; | |
break; | |
default: | |
return $image; | |
} | |
imagecopyresampled( | |
$new_img, | |
$image, | |
0, | |
0, | |
$src_x, | |
$src_y, | |
$new_width, | |
$new_height, | |
$src_width, | |
$src_height | |
); | |
return $new_img; | |
} | |
/** | |
* @param string $file_path | |
* @param resource $src_img | |
* | |
* @return bool | |
*/ | |
private function gd_orient_image($file_path, $src_img) { | |
if (!function_exists('exif_read_data')) { | |
return false; | |
} | |
/* @noinspection PhpUsageOfSilenceOperatorInspection */ | |
$exif = @exif_read_data($file_path); | |
if ($exif === false) { | |
return false; | |
} | |
$orientation = (int)isset($exif['Orientation']) ? $exif['Orientation'] : 0; | |
if ($orientation < 2 || $orientation > 8) { | |
return false; | |
} | |
switch ($orientation) { | |
case 2: | |
$new_img = $this->gd_imageflip( | |
$src_img, | |
defined('IMG_FLIP_VERTICAL') ? \IMG_FLIP_VERTICAL : 2 | |
); | |
break; | |
case 3: | |
$new_img = imagerotate($src_img, 180, 0); | |
break; | |
case 4: | |
$new_img = $this->gd_imageflip( | |
$src_img, | |
defined('IMG_FLIP_HORIZONTAL') ? \IMG_FLIP_HORIZONTAL : 1 | |
); | |
break; | |
case 5: | |
$tmp_img = $this->gd_imageflip( | |
$src_img, | |
defined('IMG_FLIP_HORIZONTAL') ? \IMG_FLIP_HORIZONTAL : 1 | |
); | |
$new_img = imagerotate($tmp_img, 270, 0); | |
imagedestroy($tmp_img); | |
break; | |
case 6: | |
$new_img = imagerotate($src_img, 270, 0); | |
break; | |
case 7: | |
$tmp_img = $this->gd_imageflip( | |
$src_img, | |
defined('IMG_FLIP_VERTICAL') ? \IMG_FLIP_VERTICAL : 2 | |
); | |
$new_img = imagerotate($tmp_img, 270, 0); | |
imagedestroy($tmp_img); | |
break; | |
case 8: | |
$new_img = imagerotate($src_img, 90, 0); | |
break; | |
default: | |
return false; | |
} | |
$this->gd_set_image_object($file_path, $new_img); | |
return true; | |
} | |
/** | |
* @param string $file_name | |
* @param string $version | |
* @param array $options | |
* | |
* @return bool | |
*/ | |
private function gd_create_scaled_image($file_name, $version, $options) { | |
if (!function_exists('imagecreatetruecolor')) { | |
ErrorHandlerLib::vdmgReportError('Function not found: imagecreatetruecolor'); | |
return false; | |
} | |
list($file_path, $new_file_path) = $this->get_scaled_image_file_paths($file_name, $version); | |
$type = strtolower(substr(strrchr($file_name, '.'), 1)); | |
switch ($type) { | |
case 'jpg': | |
case 'jpeg': | |
$src_func = 'imagecreatefromjpeg'; | |
$write_func = 'imagejpeg'; | |
$image_quality = $options['jpeg_quality'] ?? 75; | |
break; | |
case 'gif': | |
$src_func = 'imagecreatefromgif'; | |
$write_func = 'imagegif'; | |
$image_quality = null; | |
break; | |
case 'png': | |
$src_func = 'imagecreatefrompng'; | |
$write_func = 'imagepng'; | |
$image_quality = $options['png_quality'] ?? 9; | |
break; | |
default: | |
return false; | |
} | |
$src_img = $this->gd_get_image_object($file_path, $src_func, !empty($options['no_cache'])); | |
$image_oriented = false; | |
if ( | |
!empty($options['auto_orient']) | |
&& | |
$this->gd_orient_image($file_path, $src_img) | |
) { | |
$image_oriented = true; | |
$src_img = $this->gd_get_image_object($file_path, $src_func); | |
} | |
$max_width = $img_width = imagesx($src_img); | |
$max_height = $img_height = imagesy($src_img); | |
if (!empty($options['max_width'])) { | |
$max_width = $options['max_width']; | |
} | |
if (!empty($options['max_height'])) { | |
$max_height = $options['max_height']; | |
} | |
$scale = min( | |
$max_width / $img_width, | |
$max_height / $img_height | |
); | |
if ($scale >= 1) { | |
if ($image_oriented) { | |
switch ($write_func) { | |
case 'imagejpeg': | |
return imagejpeg($src_img, $new_file_path, $image_quality); | |
case 'imagegif': | |
return imagegif($src_img, $new_file_path); | |
case 'imagepng': | |
return imagepng($src_img, $new_file_path, $image_quality); | |
default: | |
return false; | |
} | |
} | |
if ($file_path !== $new_file_path) { | |
return copy($file_path, $new_file_path); | |
} | |
return true; | |
} | |
if (empty($options['crop'])) { | |
$new_width = $img_width * $scale; | |
$new_height = $img_height * $scale; | |
$dst_x = 0; | |
$dst_y = 0; | |
$new_img = imagecreatetruecolor($new_width, $new_height); | |
} else { | |
if (($img_width / $img_height) >= ($max_width / $max_height)) { | |
$new_width = $img_width / ($img_height / $max_height); | |
$new_height = $max_height; | |
} else { | |
$new_width = $max_width; | |
$new_height = $img_height / ($img_width / $max_width); | |
} | |
$dst_x = 0 - ($new_width - $max_width) / 2; | |
$dst_y = 0 - ($new_height - $max_height) / 2; | |
$new_img = imagecreatetruecolor($max_width, $max_height); | |
} | |
// Handle transparency in GIF and PNG images: | |
switch ($type) { | |
case 'gif': | |
case 'png': | |
imagecolortransparent($new_img, imagecolorallocate($new_img, 0, 0, 0)); | |
imagealphablending($new_img, false); | |
imagesavealpha($new_img, true); | |
break; | |
} | |
$success = imagecopyresampled( | |
$new_img, | |
$src_img, | |
$dst_x, | |
$dst_y, | |
0, | |
0, | |
$new_width, | |
$new_height, | |
$img_width, | |
$img_height | |
); | |
if ($success) { | |
switch ($write_func) { | |
case 'imagejpeg': | |
$success = imagejpeg($new_img, $new_file_path, $image_quality); | |
break; | |
case 'imagegif': | |
$success = imagegif($new_img, $new_file_path); | |
break; | |
case 'imagepng': | |
$success = imagepng($new_img, $new_file_path, $image_quality); | |
break; | |
default: | |
return false; | |
} | |
} | |
$this->gd_set_image_object($file_path, $new_img); | |
return $success; | |
} | |
/** | |
* @param string $file_path | |
* @param bool $no_cache | |
* | |
* @return mixed | |
*/ | |
private function imagick_get_image_object($file_path, $no_cache = false) { | |
if (empty($this->image_objects[$file_path]) || $no_cache) { | |
$this->imagick_destroy_image_object($file_path); | |
$image = new \Imagick(); | |
if (!empty($this->options['imagick_resource_limits'])) { | |
foreach ($this->options['imagick_resource_limits'] as $type => $limit) { | |
$image::setResourceLimit($type, $limit); | |
} | |
} | |
$image->readImage($file_path); | |
$this->image_objects[$file_path] = $image; | |
} | |
return $this->image_objects[$file_path]; | |
} | |
/** | |
* @param string $file_path | |
* @param resource $image | |
*/ | |
private function imagick_set_image_object($file_path, $image) { | |
$this->imagick_destroy_image_object($file_path); | |
$this->image_objects[$file_path] = $image; | |
} | |
/** | |
* @param string $file_path | |
* | |
* @return bool | |
*/ | |
private function imagick_destroy_image_object($file_path) { | |
$image = $this->image_objects[$file_path] ?? null; | |
return $image && $image->destroy(); | |
} | |
/** | |
* @param \Imagick $image | |
* | |
* @return bool | |
*/ | |
private function imagick_orient_image($image) { | |
$orientation = $image->getImageOrientation(); | |
$background = new \ImagickPixel('none'); | |
switch ($orientation) { | |
case \Imagick::ORIENTATION_TOPRIGHT: // 2 | |
$image->flopImage(); // horizontal flop around y-axis | |
break; | |
case \Imagick::ORIENTATION_BOTTOMRIGHT: // 3 | |
$image->rotateImage($background, 180); | |
break; | |
case \Imagick::ORIENTATION_BOTTOMLEFT: // 4 | |
$image->flipImage(); // vertical flip around x-axis | |
break; | |
case \Imagick::ORIENTATION_LEFTTOP: // 5 | |
$image->flopImage(); // horizontal flop around y-axis | |
$image->rotateImage($background, 270); | |
break; | |
case \Imagick::ORIENTATION_RIGHTTOP: // 6 | |
$image->rotateImage($background, 90); | |
break; | |
case \Imagick::ORIENTATION_RIGHTBOTTOM: // 7 | |
$image->flipImage(); // vertical flip around x-axis | |
$image->rotateImage($background, 270); | |
break; | |
case \Imagick::ORIENTATION_LEFTBOTTOM: // 8 | |
$image->rotateImage($background, 270); | |
break; | |
default: | |
return false; | |
} | |
$image->setImageOrientation(\Imagick::ORIENTATION_TOPLEFT); // 1 | |
return true; | |
} | |
/** | |
* @param string $file_name | |
* @param string $version | |
* @param array $options | |
* | |
* @return bool | |
*/ | |
private function imagick_create_scaled_image($file_name, $version, $options) { | |
list($file_path, $new_file_path) = $this->get_scaled_image_file_paths($file_name, $version); | |
$image = $this->imagick_get_image_object($file_path, !empty($options['crop']) || !empty($options['no_cache'])); | |
if ($image->getImageFormat() === 'GIF') { | |
// Handle animated GIFs: | |
$images = $image->coalesceImages(); | |
foreach ($images as $frame) { | |
$image = $frame; | |
$this->imagick_set_image_object($file_name, $image); | |
break; | |
} | |
} | |
$image_oriented = false; | |
if (!empty($options['auto_orient'])) { | |
$image_oriented = $this->imagick_orient_image($image); | |
} | |
$new_width = $max_width = $img_width = $image->getImageWidth(); | |
$new_height = $max_height = $img_height = $image->getImageHeight(); | |
if (!empty($options['max_width'])) { | |
$new_width = $max_width = $options['max_width']; | |
} | |
if (!empty($options['max_height'])) { | |
$new_height = $max_height = $options['max_height']; | |
} | |
if (!($image_oriented || $max_width < $img_width || $max_height < $img_height)) { | |
if ($file_path !== $new_file_path) { | |
return copy($file_path, $new_file_path); | |
} | |
return true; | |
} | |
$crop = !empty($options['crop']); | |
$x = null; | |
$y = null; | |
if ($crop) { | |
$x = 0; | |
$y = 0; | |
if (($img_width / $img_height) >= ($max_width / $max_height)) { | |
$new_width = 0; // Enables proportional scaling based on max_height | |
$x = ($img_width / ($img_height / $max_height) - $max_width) / 2; | |
} else { | |
$new_height = 0; // Enables proportional scaling based on max_width | |
$y = ($img_height / ($img_width / $max_width) - $max_height) / 2; | |
} | |
} | |
$success = $image->resizeImage( | |
$new_width, | |
$new_height, | |
$options['filter'] ?? \Imagick::FILTER_LANCZOS, | |
$options['blur'] ?? 1, | |
$new_width && $new_height // fit image into constraints if not to be cropped | |
); | |
if ($success && $crop) { | |
$success = $image->cropImage( | |
$max_width, | |
$max_height, | |
$x, | |
$y | |
); | |
if ($success) { | |
$success = $image->setImagePage($max_width, $max_height, 0, 0); | |
} | |
} | |
$type = strtolower(substr(strrchr($file_name, '.'), 1)); | |
switch ($type) { | |
case 'jpg': | |
case 'jpeg': | |
if (!empty($options['jpeg_quality'])) { | |
$image->setImageCompression(\Imagick::COMPRESSION_JPEG); | |
$image->setImageCompressionQuality($options['jpeg_quality']); | |
} | |
break; | |
} | |
if (!empty($options['strip'])) { | |
$image->stripImage(); | |
} | |
return $success && $image->writeImage($new_file_path); | |
} | |
/** | |
* @param string $file_name | |
* @param string $version | |
* @param array $options | |
* | |
* @return bool | |
*/ | |
private function imagemagick_create_scaled_image($file_name, $version, $options) { | |
list($file_path, $new_file_path) = $this->get_scaled_image_file_paths($file_name, $version); | |
$resize = $options['max_width'] . (empty($options['max_height']) ? '' : 'X' . $options['max_height']); | |
if (!$resize && empty($options['auto_orient'])) { | |
if ($file_path !== $new_file_path) { | |
return copy($file_path, $new_file_path); | |
} | |
return true; | |
} | |
$cmd = $this->options['convert_bin']; | |
if (!empty($this->options['convert_params'])) { | |
$cmd .= ' ' . $this->options['convert_params']; | |
} | |
$cmd .= ' ' . escapeshellarg($file_path); | |
if (!empty($options['auto_orient'])) { | |
$cmd .= ' -auto-orient'; | |
} | |
if ($resize) { | |
// Handle animated GIFs: | |
$cmd .= ' -coalesce'; | |
if (empty($options['crop'])) { | |
$cmd .= ' -resize ' . escapeshellarg($resize . '>'); | |
} else { | |
$cmd .= ' -resize ' . escapeshellarg($resize . '^'); | |
$cmd .= ' -gravity center'; | |
$cmd .= ' -crop ' . escapeshellarg($resize . '+0+0'); | |
} | |
// Make sure the page dimensions are correct (fixes offsets of animated GIFs): | |
$cmd .= ' +repage'; | |
} | |
if (!empty($options['convert_params'])) { | |
$cmd .= ' ' . $options['convert_params']; | |
} | |
$cmd .= ' ' . escapeshellarg($new_file_path); | |
exec($cmd, $output, $error); | |
if ($error) { | |
ErrorHandlerLib::vdmgReportError(implode('\n', $output)); | |
return false; | |
} | |
return true; | |
} | |
/** | |
* @param string $file_path | |
* | |
* @return array|array[]|bool|false|string[] | |
*/ | |
private function get_image_size($file_path) { | |
if ($this->options['image_library']) { | |
if (extension_loaded('imagick')) { | |
$image = new \Imagick(); | |
try { | |
if ($image->pingImage($file_path)) { | |
$dimensions = [$image->getImageWidth(), $image->getImageHeight()]; | |
$image->destroy(); | |
return $dimensions; | |
} | |
return false; | |
} catch (\Exception $e) { | |
ErrorHandlerLib::vdmgReportError($e->getMessage()); | |
} | |
} | |
if ($this->options['image_library'] === 2) { | |
$cmd = $this->options['identify_bin']; | |
$cmd .= ' -ping ' . escapeshellarg($file_path); | |
exec($cmd, $output, $error); | |
if (!$error && !empty($output)) { | |
// image.jpg JPEG 1920x1080 1920x1080+0+0 8-bit sRGB 465KB 0.000u 0:00.000 | |
$infos = preg_split('/\s+/', substr($output[0], strlen($file_path))); | |
$dimensions = preg_split('/x/', $infos[2]); | |
return $dimensions; | |
} | |
return false; | |
} | |
} | |
if (!function_exists('getimagesize')) { | |
ErrorHandlerLib::vdmgReportError('Function not found: getimagesize'); | |
return false; | |
} | |
/* @noinspection PhpUsageOfSilenceOperatorInspection */ | |
return @getimagesize($file_path); | |
} | |
/** | |
* @param string $file_name | |
* @param string $version | |
* @param array $options | |
* | |
* @return bool | |
*/ | |
private function create_scaled_image($file_name, $version, $options) { | |
if ($this->options['image_library'] === 2) { | |
return $this->imagemagick_create_scaled_image($file_name, $version, $options); | |
} | |
if ($this->options['image_library'] && extension_loaded('imagick')) { | |
return $this->imagick_create_scaled_image($file_name, $version, $options); | |
} | |
return $this->gd_create_scaled_image($file_name, $version, $options); | |
} | |
/** | |
* @param string $file_path | |
* | |
* @return bool | |
*/ | |
private function destroy_image_object($file_path) { | |
if ( | |
$this->options['image_library'] | |
&& | |
extension_loaded('imagick') | |
) { | |
return $this->imagick_destroy_image_object($file_path); | |
} | |
return false; | |
} | |
/** | |
* @param string $file_path | |
* | |
* @return bool|int | |
*/ | |
private function is_valid_image_file($file_path) { | |
if (!preg_match($this->options['image_file_types'], $file_path)) { | |
return false; | |
} | |
if (function_exists('exif_imagetype')) { | |
/* @noinspection PhpUsageOfSilenceOperatorInspection */ | |
return @exif_imagetype($file_path); | |
} | |
$image_info = $this->get_image_size($file_path); | |
return $image_info && $image_info[0] && $image_info[1]; | |
} | |
/** | |
* @param string $file_path | |
* @param \stdClass $file | |
*/ | |
private function handle_image_file($file_path, $file) { | |
$failed_versions = []; | |
foreach ($this->options['image_versions'] as $version => $options) { | |
if ($this->create_scaled_image($file->name, $version, $options)) { | |
if (!empty($version)) { | |
$file->{$version . 'Url'} = $this->get_download_url( | |
$file->name, | |
$version | |
); | |
} else { | |
$file->size = $this->get_file_size($file_path, true); | |
} | |
} else { | |
$failed_versions[] = $version ? $version : 'original'; | |
} | |
} | |
if (count($failed_versions)) { | |
$file->error = $this->get_error_message('image_resize') | |
. ' (' . implode(', ', $failed_versions) . ')'; | |
} | |
// Free memory: | |
$this->destroy_image_object($file_path); | |
} | |
/** | |
* @param string $uploaded_file | |
* @param string $name | |
* @param int $size | |
* @param string $type | |
* @param mixed $error | |
* @param null|int $index | |
* @param null|mixed $content_range | |
* | |
* @return stdClass | |
*/ | |
private function handle_file_upload($uploaded_file, $name, $size, $type, $error, $index = null, $content_range = null) { | |
$file = new \stdClass(); | |
$file->name = $this->get_file_name($uploaded_file, $name, $size, $type, $error, $index, $content_range); | |
$file->size = $this->fix_integer_overflow((int)$size); | |
$file->type = $type; | |
if ($this->validate($uploaded_file, $file, $error, $index)) { | |
$this->handle_form_data($file, $index); | |
$upload_dir = $this->get_upload_path(); | |
if (!is_dir($upload_dir)) { | |
if ( | |
!mkdir($upload_dir, $this->options['mkdir_mode'], true) | |
&& | |
!is_dir($upload_dir) | |
) { | |
throw new \RuntimeException(sprintf('Directory "%s" was not created', $upload_dir)); | |
} | |
} | |
$file_path = $this->get_upload_path($file->name); | |
$append_file = $content_range && is_file($file_path) && $file->size > $this->get_file_size($file_path); | |
if ($uploaded_file && is_uploaded_file($uploaded_file)) { | |
// multipart/formdata uploads (POST method uploads) | |
if ($append_file) { | |
file_put_contents( | |
$file_path, | |
fopen($uploaded_file, 'rb'), | |
\FILE_APPEND | |
); | |
} else { | |
move_uploaded_file($uploaded_file, $file_path); | |
} | |
} else { | |
// Non-multipart uploads (PUT method support) | |
file_put_contents( | |
$file_path, | |
fopen('php://input', 'rb'), | |
$append_file ? \FILE_APPEND : 0 | |
); | |
} | |
$file_size = $this->get_file_size($file_path, $append_file); | |
if ($file_size === $file->size) { | |
$file->url = $this->get_download_url($file->name); | |
if ($this->is_valid_image_file($file_path)) { | |
$this->handle_image_file($file_path, $file); | |
} | |
} else { | |
$file->size = $file_size; | |
if (!$content_range && $this->options['discard_aborted_uploads']) { | |
unlink($file_path); | |
$file->error = $this->get_error_message('abort'); | |
} | |
} | |
$this->set_additional_file_properties($file); | |
} | |
return $file; | |
} | |
/** | |
* @param string $file_path | |
* | |
* @return false|float|int | |
*/ | |
private function readfile($file_path) { | |
$file_size = $this->get_file_size($file_path); | |
$chunk_size = $this->options['readfile_chunk_size']; | |
if ($chunk_size && $file_size > $chunk_size) { | |
$handle = fopen($file_path, 'rb'); | |
while (!feof($handle)) { | |
echo fread($handle, $chunk_size); | |
/* @noinspection PhpUsageOfSilenceOperatorInspection */ | |
@ob_flush(); | |
/* @noinspection PhpUsageOfSilenceOperatorInspection */ | |
@flush(); | |
} | |
fclose($handle); | |
return $file_size; | |
} | |
return readfile($file_path); | |
} | |
/** | |
* @param string $str | |
*/ | |
private function body($str) { | |
echo $str; | |
} | |
/** | |
* @param string $str | |
*/ | |
private function header($str) { | |
header($str); | |
} | |
/** | |
* @param int $id | |
* | |
* @return array|string | |
*/ | |
private function get_upload_data($id) { | |
return $_FILES[$id] ?? ''; | |
} | |
/** | |
* @param string $id | |
* | |
* @return null|string | |
*/ | |
private function get_post_param($id) { | |
return $_POST[$id]; | |
} | |
/** | |
* @param string $id | |
* | |
* @return string|string[]|string[][] | |
*/ | |
private function get_query_param($id) { | |
return $_GET[$id] ?? ''; | |
} | |
/** | |
* @param string $id | |
* | |
* @return string | |
*/ | |
private function get_server_var($id) { | |
return $_SERVER[$id] ?? ''; | |
} | |
/** | |
* @param \stdClass $file | |
* @param null|int $index | |
*/ | |
private function handle_form_data($file, $index) { | |
// Handle form data, e.g. $_POST['description'][$index] | |
} | |
/** | |
* @return string | |
*/ | |
private function get_version_param() { | |
return basename(stripslashes($this->get_query_param('version'))); | |
} | |
/** | |
* @return bool|string | |
*/ | |
private function get_singular_param_name() { | |
return substr($this->options['param_name'], 0, -1); | |
} | |
/** | |
* @return string | |
*/ | |
private function get_file_name_param() { | |
$name = $this->get_singular_param_name(); | |
return basename(stripslashes($this->get_query_param($name))); | |
} | |
/** | |
* @return null|array|string | |
*/ | |
private function get_file_names_params() { | |
$params = $this->get_query_param($this->options['param_name']); | |
if (!$params) { | |
return null; | |
} | |
if (is_array($params)) { | |
foreach ($params as $key => $value) { | |
$params[$key] = basename(stripslashes($value)); | |
} | |
} | |
return $params; | |
} | |
/** | |
* @param string $file_path | |
* | |
* @return string | |
*/ | |
private function get_file_type($file_path) { | |
switch (strtolower(pathinfo($file_path, \PATHINFO_EXTENSION))) { | |
case 'jpeg': | |
case 'jpg': | |
return 'image/jpeg'; | |
case 'png': | |
return 'image/png'; | |
case 'gif': | |
return 'image/gif'; | |
default: | |
return ''; | |
} | |
} | |
/** | |
* @return void | |
*/ | |
private function download() { | |
switch ($this->options['download_via_php']) { | |
case 1: | |
$redirect_header = null; | |
break; | |
case 2: | |
$redirect_header = 'X-Sendfile'; | |
break; | |
case 3: | |
$redirect_header = 'X-Accel-Redirect'; | |
break; | |
default: | |
$this->header('HTTP/1.1 403 Forbidden'); | |
return; | |
} | |
$file_name = $this->get_file_name_param(); | |
if (!$this->is_valid_file_object($file_name)) { | |
$this->header('HTTP/1.1 404 Not Found'); | |
return; | |
} | |
if ($redirect_header) { | |
$this->header( | |
$redirect_header . ': ' . $this->get_download_url( | |
$file_name, | |
$this->get_version_param(), | |
true | |
) | |
); | |
return; | |
} | |
$file_path = $this->get_upload_path($file_name, $this->get_version_param()); | |
// Prevent browsers from MIME-sniffing the content-type: | |
$this->header('X-Content-Type-Options: nosniff'); | |
if (!preg_match($this->options['inline_file_types'], $file_name)) { | |
$this->header('Content-Type: application/octet-stream'); | |
$this->header('Content-Disposition: attachment; filename="' . $file_name . '"'); | |
} else { | |
$this->header('Content-Type: ' . $this->get_file_type($file_path)); | |
$this->header('Content-Disposition: inline; filename="' . $file_name . '"'); | |
} | |
$this->header('Content-Length: ' . $this->get_file_size($file_path)); | |
$this->header('Last-Modified: ' . gmdate('D, d M Y H:i:s T', filemtime($file_path))); | |
$this->readfile($file_path); | |
} | |
/** | |
* @return void | |
*/ | |
private function send_content_type_header() { | |
$this->header('Vary: Accept'); | |
if (strpos($this->get_server_var('HTTP_ACCEPT'), 'application/json') !== false) { | |
$this->header('Content-type: application/json'); | |
} else { | |
$this->header('Content-type: text/plain'); | |
} | |
} | |
/** | |
* @return void | |
*/ | |
private function send_access_control_headers() { | |
$this->header('Access-Control-Allow-Origin: ' . $this->options['access_control_allow_origin']); | |
$this->header('Access-Control-Allow-Credentials: ' . ($this->options['access_control_allow_credentials'] ? 'true' : 'false')); | |
$this->header('Access-Control-Allow-Methods: ' . implode(', ', $this->options['access_control_allow_methods'])); | |
$this->header('Access-Control-Allow-Headers: ' . implode(', ', $this->options['access_control_allow_headers'])); | |
} | |
/** | |
* @param mixed $content | |
* @param bool $print_response | |
* | |
* @return mixed|void | |
*/ | |
public function generate_response($content, $print_response = true) { | |
$this->response = $content; | |
if ($print_response) { | |
$json = json_encode($content); | |
$redirect = stripslashes($this->get_post_param('redirect')); | |
if ($redirect && preg_match($this->options['redirect_allow_target'], $redirect)) { | |
$this->header('Location: ' . sprintf($redirect, rawurlencode($json))); | |
return; | |
} | |
$this->head(); | |
if ($this->get_server_var('HTTP_CONTENT_RANGE')) { | |
$files = $content[$this->options['param_name']] ?? null; | |
if ($files && is_array($files) && is_object($files[0]) && $files[0]->size) { | |
$this->header( | |
'Range: 0-' . ( | |
$this->fix_integer_overflow((int)$files[0]->size) - 1 | |
) | |
); | |
} | |
} | |
$this->body($json); | |
} | |
return $content; | |
} | |
/** | |
* @return array | |
*/ | |
public function get_response() { | |
return $this->response; | |
} | |
/** | |
* @return void | |
*/ | |
public function head() { | |
$this->header('Pragma: no-cache'); | |
$this->header('Cache-Control: no-store, no-cache, must-revalidate'); | |
$this->header('Content-Disposition: inline; filename="files.json"'); | |
// Prevent Internet Explorer from MIME-sniffing the content-type: | |
$this->header('X-Content-Type-Options: nosniff'); | |
if ($this->options['access_control_allow_origin']) { | |
$this->send_access_control_headers(); | |
} | |
$this->send_content_type_header(); | |
} | |
/** | |
* @param bool $print_response | |
* | |
* @return mixed|void | |
*/ | |
public function get($print_response = true) { | |
if ($print_response && $this->get_query_param('download')) { | |
$this->download(); | |
return; | |
} | |
$file_name = $this->get_file_name_param(); | |
if ($file_name) { | |
$response = [ | |
$this->get_singular_param_name() => $this->get_file_object($file_name), | |
]; | |
} else { | |
$response = [ | |
$this->options['param_name'] => $this->get_file_objects(), | |
]; | |
} | |
return $this->generate_response($response, $print_response); | |
} | |
/** | |
* @param bool $print_response | |
* | |
* @return mixed|void | |
*/ | |
public function post($print_response = true) { | |
if ($this->get_query_param('_method') === 'DELETE') { | |
return $this->delete($print_response); | |
} | |
$upload = $this->get_upload_data($this->options['param_name']); | |
// Parse the Content-Disposition header, if available: | |
$content_disposition_header = $this->get_server_var('HTTP_CONTENT_DISPOSITION'); | |
$file_name = $content_disposition_header ? | |
rawurldecode( | |
preg_replace( | |
'/(^[^"]+")|("$)/', | |
'', | |
$content_disposition_header | |
) | |
) : null; | |
// Parse the Content-Range header, which has the following form: | |
// Content-Range: bytes 0-524287/2000000 | |
$content_range_header = $this->get_server_var('HTTP_CONTENT_RANGE'); | |
$content_range = $content_range_header ? | |
preg_split('/[^0-9]+/', $content_range_header) : null; | |
$size = $content_range ? $content_range[3] : null; | |
$files = []; | |
if ($upload) { | |
if (is_array($upload['tmp_name'])) { | |
// param_name is an array identifier like "files[]", | |
// $upload is a multi-dimensional array: | |
foreach ($upload['tmp_name'] as $index => $value) { | |
$files[] = $this->handle_file_upload( | |
$upload['tmp_name'][$index], | |
$file_name ? $file_name : $upload['name'][$index], | |
$size ? $size : $upload['size'][$index], | |
$upload['type'][$index], | |
$upload['error'][$index], | |
$index, | |
$content_range | |
); | |
} | |
} else { | |
// param_name is a single object identifier like "file", | |
// $upload is a one-dimensional array: | |
$files[] = $this->handle_file_upload( | |
$upload['tmp_name'] ?? null, | |
$file_name ? $file_name : ($upload['name'] ?? null), | |
$size ? $size : ($upload['size'] ?? $this->get_server_var('CONTENT_LENGTH')), | |
$upload['type'] ?? $this->get_server_var('CONTENT_TYPE'), | |
$upload['error'] ?? null, | |
null, | |
$content_range | |
); | |
} | |
} | |
$response = [$this->options['param_name'] => $files]; | |
return $this->generate_response($response, $print_response); | |
} | |
/** | |
* @param bool $print_response | |
* | |
* @return mixed|void | |
*/ | |
public function delete($print_response = true) { | |
$file_names = $this->get_file_names_params(); | |
if (empty($file_names)) { | |
$file_names = [$this->get_file_name_param()]; | |
} | |
$response = []; | |
foreach ($file_names as $file_name) { | |
$file_path = $this->get_upload_path($file_name); | |
$success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path); | |
if ($success) { | |
foreach ($this->options['image_versions'] as $version => $options) { | |
if (!empty($version)) { | |
$file = $this->get_upload_path($file_name, $version); | |
if (is_file($file)) { | |
unlink($file); | |
} | |
} | |
} | |
} | |
$response[$file_name] = $success; | |
} | |
return $this->generate_response($response, $print_response); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment