PageRenderTime 59ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/lhc_web/lib/core/lhcore/UploadHandler.php

https://github.com/keyanmca/livehelperchat
PHP | 1302 lines | 1142 code | 66 blank | 94 comment | 184 complexity | 0cbc5c66cf2ea22510b05732ad49c005 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. * jQuery File Upload Plugin PHP Class 7.0.1
  4. * https://github.com/blueimp/jQuery-File-Upload
  5. *
  6. * Copyright 2010, Sebastian Tschan
  7. * https://blueimp.net
  8. *
  9. * Licensed under the MIT license:
  10. * http://www.opensource.org/licenses/MIT
  11. */
  12. class UploadHandler
  13. {
  14. protected $options;
  15. // PHP File Upload error message codes:
  16. // http://php.net/manual/en/features.file-upload.errors.php
  17. protected $error_messages = array(
  18. 1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
  19. 2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
  20. 3 => 'The uploaded file was only partially uploaded',
  21. 4 => 'No file was uploaded',
  22. 6 => 'Missing a temporary folder',
  23. 7 => 'Failed to write file to disk',
  24. 8 => 'A PHP extension stopped the file upload',
  25. 'post_max_size' => 'The uploaded file exceeds the post_max_size directive in php.ini',
  26. 'max_file_size' => 'File is too big',
  27. 'min_file_size' => 'File is too small',
  28. 'accept_file_types' => 'Filetype not allowed',
  29. 'max_number_of_files' => 'Maximum number of files exceeded',
  30. 'max_width' => 'Image exceeds maximum width',
  31. 'min_width' => 'Image requires a minimum width',
  32. 'max_height' => 'Image exceeds maximum height',
  33. 'min_height' => 'Image requires a minimum height'
  34. );
  35. protected $image_objects = array();
  36. function __construct($options = null, $initialize = true, $error_messages = null) {
  37. $this->options = array(
  38. 'script_url' => $this->get_full_url().'/',
  39. 'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/files/',
  40. 'upload_url' => $this->get_full_url().'/files/',
  41. 'user_dirs' => false,
  42. 'mkdir_mode' => 0755,
  43. 'param_name' => 'files',
  44. // Set the following option to 'POST', if your server does not support
  45. // DELETE requests. This is a parameter sent to the client:
  46. 'delete_type' => 'DELETE',
  47. 'access_control_allow_origin' => '*',
  48. 'access_control_allow_credentials' => false,
  49. 'access_control_allow_methods' => array(
  50. 'OPTIONS',
  51. 'HEAD',
  52. 'GET',
  53. 'POST',
  54. 'PUT',
  55. 'PATCH',
  56. 'DELETE'
  57. ),
  58. 'access_control_allow_headers' => array(
  59. 'Content-Type',
  60. 'Content-Range',
  61. 'Content-Disposition'
  62. ),
  63. // Enable to provide file downloads via GET requests to the PHP script:
  64. // 1. Set to 1 to download files via readfile method through PHP
  65. // 2. Set to 2 to send a X-Sendfile header for lighttpd/Apache
  66. // 3. Set to 3 to send a X-Accel-Redirect header for nginx
  67. // If set to 2 or 3, adjust the upload_url option to the base path of
  68. // the redirect parameter, e.g. '/files/'.
  69. 'download_via_php' => false,
  70. // Read files in chunks to avoid memory limits when download_via_php
  71. // is enabled, set to 0 to disable chunked reading of files:
  72. 'readfile_chunk_size' => 10 * 1024 * 1024, // 10 MiB
  73. // Defines which files can be displayed inline when downloaded:
  74. 'inline_file_types' => '/\.(gif|jpe?g|png)$/i',
  75. // Defines which files (based on their names) are accepted for upload:
  76. 'accept_file_types' => '/.+$/i',
  77. // The php.ini settings upload_max_filesize and post_max_size
  78. // take precedence over the following max_file_size setting:
  79. 'max_file_size' => null,
  80. 'min_file_size' => 1,
  81. // The maximum number of files for the upload directory:
  82. 'max_number_of_files' => null,
  83. // Defines which files are handled as image files:
  84. 'image_file_types' => '/\.(gif|jpe?g|png)$/i',
  85. // Image resolution restrictions:
  86. 'max_width' => null,
  87. 'max_height' => null,
  88. 'min_width' => 1,
  89. 'min_height' => 1,
  90. // Set the following option to false to enable resumable uploads:
  91. 'discard_aborted_uploads' => true,
  92. // Set to 0 to use the GD library to scale and orient images,
  93. // set to 1 to use imagick (if installed, falls back to GD),
  94. // set to 2 to use the ImageMagick convert binary directly:
  95. 'image_library' => 1,
  96. // Uncomment the following to define an array of resource limits
  97. // for imagick:
  98. /*
  99. 'imagick_resource_limits' => array(
  100. imagick::RESOURCETYPE_MAP => 32,
  101. imagick::RESOURCETYPE_MEMORY => 32
  102. ),
  103. */
  104. // Command or path for to the ImageMagick convert binary:
  105. 'convert_bin' => 'convert',
  106. // Uncomment the following to add parameters in front of each
  107. // ImageMagick convert call (the limit constraints seem only
  108. // to have an effect if put in front):
  109. /*
  110. 'convert_params' => '-limit memory 32MiB -limit map 32MiB',
  111. */
  112. // Command or path for to the ImageMagick identify binary:
  113. 'identify_bin' => 'identify',
  114. 'image_versions' => array(
  115. // The empty image version key defines options for the original image:
  116. '' => array(
  117. // Automatically rotate images based on EXIF meta data:
  118. 'auto_orient' => true
  119. ),
  120. // Uncomment the following to create medium sized images:
  121. /*
  122. 'medium' => array(
  123. 'max_width' => 800,
  124. 'max_height' => 600
  125. ),
  126. */
  127. 'thumbnail' => array(
  128. // Uncomment the following to use a defined directory for the thumbnails
  129. // instead of a subdirectory based on the version identifier.
  130. // Make sure that this directory doesn't allow execution of files if you
  131. // don't pose any restrictions on the type of uploaded files, e.g. by
  132. // copying the .htaccess file from the files directory for Apache:
  133. //'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/thumb/',
  134. //'upload_url' => $this->get_full_url().'/thumb/',
  135. // Uncomment the following to force the max
  136. // dimensions and e.g. create square thumbnails:
  137. //'crop' => true,
  138. 'max_width' => 80,
  139. 'max_height' => 80
  140. )
  141. )
  142. );
  143. if ($options) {
  144. $this->options = $options + $this->options;
  145. }
  146. if ($error_messages) {
  147. $this->error_messages = $error_messages + $this->error_messages;
  148. }
  149. if ($initialize) {
  150. $this->initialize();
  151. }
  152. }
  153. protected function initialize() {
  154. switch ($this->get_server_var('REQUEST_METHOD')) {
  155. case 'OPTIONS':
  156. case 'HEAD':
  157. $this->head();
  158. break;
  159. case 'GET':
  160. $this->get();
  161. break;
  162. case 'PATCH':
  163. case 'PUT':
  164. case 'POST':
  165. $this->post();
  166. break;
  167. case 'DELETE':
  168. $this->delete();
  169. break;
  170. default:
  171. $this->header('HTTP/1.1 405 Method Not Allowed');
  172. }
  173. }
  174. protected function get_full_url() {
  175. $https = !empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'on') === 0;
  176. return
  177. ($https ? 'https://' : 'http://').
  178. (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : '').
  179. (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'].
  180. ($https && $_SERVER['SERVER_PORT'] === 443 ||
  181. $_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))).
  182. substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/'));
  183. }
  184. protected function get_user_id() {
  185. @session_start();
  186. return session_id();
  187. }
  188. protected function get_user_path() {
  189. if ($this->options['user_dirs']) {
  190. return $this->get_user_id().'/';
  191. }
  192. return '';
  193. }
  194. protected function get_upload_path($file_name = null, $version = null) {
  195. $file_name = $file_name ? $file_name : '';
  196. if (empty($version)) {
  197. $version_path = '';
  198. } else {
  199. $version_dir = @$this->options['image_versions'][$version]['upload_dir'];
  200. if ($version_dir) {
  201. return $version_dir.$this->get_user_path().$file_name;
  202. }
  203. $version_path = $version.'/';
  204. }
  205. return $this->options['upload_dir'].$this->get_user_path()
  206. .$version_path.$file_name;
  207. }
  208. protected function get_query_separator($url) {
  209. return strpos($url, '?') === false ? '?' : '&';
  210. }
  211. protected function get_download_url($file_name, $version = null, $direct = false) {
  212. if (!$direct && $this->options['download_via_php']) {
  213. $url = $this->options['script_url']
  214. .$this->get_query_separator($this->options['script_url'])
  215. .'file='.rawurlencode($file_name);
  216. if ($version) {
  217. $url .= '&version='.rawurlencode($version);
  218. }
  219. return $url.'&download=1';
  220. }
  221. if (empty($version)) {
  222. $version_path = '';
  223. } else {
  224. $version_url = @$this->options['image_versions'][$version]['upload_url'];
  225. if ($version_url) {
  226. return $version_url.$this->get_user_path().rawurlencode($file_name);
  227. }
  228. $version_path = rawurlencode($version).'/';
  229. }
  230. return $this->options['upload_url'].$this->get_user_path()
  231. .$version_path.rawurlencode($file_name);
  232. }
  233. protected function set_additional_file_properties($file) {
  234. $file->deleteUrl = $this->options['script_url']
  235. .$this->get_query_separator($this->options['script_url'])
  236. .$this->get_singular_param_name()
  237. .'='.rawurlencode($file->name);
  238. $file->deleteType = $this->options['delete_type'];
  239. if ($file->deleteType !== 'DELETE') {
  240. $file->deleteUrl .= '&_method=DELETE';
  241. }
  242. if ($this->options['access_control_allow_credentials']) {
  243. $file->deleteWithCredentials = true;
  244. }
  245. }
  246. // Fix for overflowing signed 32 bit integers,
  247. // works for sizes up to 2^32-1 bytes (4 GiB - 1):
  248. protected function fix_integer_overflow($size) {
  249. if ($size < 0) {
  250. $size += 2.0 * (PHP_INT_MAX + 1);
  251. }
  252. return $size;
  253. }
  254. protected function get_file_size($file_path, $clear_stat_cache = false) {
  255. if ($clear_stat_cache) {
  256. clearstatcache(true, $file_path);
  257. }
  258. return $this->fix_integer_overflow(filesize($file_path));
  259. }
  260. protected function is_valid_file_object($file_name) {
  261. $file_path = $this->get_upload_path($file_name);
  262. if (is_file($file_path) && $file_name[0] !== '.') {
  263. return true;
  264. }
  265. return false;
  266. }
  267. protected function get_file_object($file_name) {
  268. if ($this->is_valid_file_object($file_name)) {
  269. $file = new stdClass();
  270. $file->name = $file_name;
  271. $file->size = $this->get_file_size(
  272. $this->get_upload_path($file_name)
  273. );
  274. $file->url = $this->get_download_url($file->name);
  275. foreach($this->options['image_versions'] as $version => $options) {
  276. if (!empty($version)) {
  277. if (is_file($this->get_upload_path($file_name, $version))) {
  278. $file->{$version.'Url'} = $this->get_download_url(
  279. $file->name,
  280. $version
  281. );
  282. }
  283. }
  284. }
  285. $this->set_additional_file_properties($file);
  286. return $file;
  287. }
  288. return null;
  289. }
  290. protected function get_file_objects($iteration_method = 'get_file_object') {
  291. $upload_dir = $this->get_upload_path();
  292. if (!is_dir($upload_dir)) {
  293. return array();
  294. }
  295. return array_values(array_filter(array_map(
  296. array($this, $iteration_method),
  297. scandir($upload_dir)
  298. )));
  299. }
  300. protected function count_file_objects() {
  301. return count($this->get_file_objects('is_valid_file_object'));
  302. }
  303. protected function get_error_message($error) {
  304. return array_key_exists($error, $this->error_messages) ?
  305. $this->error_messages[$error] : $error;
  306. }
  307. function get_config_bytes($val) {
  308. $val = trim($val);
  309. $last = strtolower($val[strlen($val)-1]);
  310. switch($last) {
  311. case 'g':
  312. $val *= 1024;
  313. case 'm':
  314. $val *= 1024;
  315. case 'k':
  316. $val *= 1024;
  317. }
  318. return $this->fix_integer_overflow($val);
  319. }
  320. protected function validate($uploaded_file, $file, $error, $index) {
  321. if ($error) {
  322. $file->error = $this->get_error_message($error);
  323. return false;
  324. }
  325. $content_length = $this->fix_integer_overflow(intval(
  326. $this->get_server_var('CONTENT_LENGTH')
  327. ));
  328. $post_max_size = $this->get_config_bytes(ini_get('post_max_size'));
  329. if ($post_max_size && ($content_length > $post_max_size)) {
  330. $file->error = $this->get_error_message('post_max_size');
  331. return false;
  332. }
  333. if (!preg_match($this->options['accept_file_types'], $file->name)) {
  334. $file->error = $this->get_error_message('accept_file_types');
  335. return false;
  336. }
  337. if ($uploaded_file && is_uploaded_file($uploaded_file)) {
  338. $file_size = $this->get_file_size($uploaded_file);
  339. } else {
  340. $file_size = $content_length;
  341. }
  342. if ($this->options['max_file_size'] && (
  343. $file_size > $this->options['max_file_size'] ||
  344. $file->size > $this->options['max_file_size'])
  345. ) {
  346. $file->error = $this->get_error_message('max_file_size');
  347. return false;
  348. }
  349. if ($this->options['min_file_size'] &&
  350. $file_size < $this->options['min_file_size']) {
  351. $file->error = $this->get_error_message('min_file_size');
  352. return false;
  353. }
  354. if (is_int($this->options['max_number_of_files']) && (
  355. $this->count_file_objects() >= $this->options['max_number_of_files'])
  356. ) {
  357. $file->error = $this->get_error_message('max_number_of_files');
  358. return false;
  359. }
  360. $max_width = @$this->options['max_width'];
  361. $max_height = @$this->options['max_height'];
  362. $min_width = @$this->options['min_width'];
  363. $min_height = @$this->options['min_height'];
  364. if (($max_width || $max_height || $min_width || $min_height)) {
  365. list($img_width, $img_height) = $this->get_image_size($uploaded_file);
  366. }
  367. if (!empty($img_width)) {
  368. if ($max_width && $img_width > $max_width) {
  369. $file->error = $this->get_error_message('max_width');
  370. return false;
  371. }
  372. if ($max_height && $img_height > $max_height) {
  373. $file->error = $this->get_error_message('max_height');
  374. return false;
  375. }
  376. if ($min_width && $img_width < $min_width) {
  377. $file->error = $this->get_error_message('min_width');
  378. return false;
  379. }
  380. if ($min_height && $img_height < $min_height) {
  381. $file->error = $this->get_error_message('min_height');
  382. return false;
  383. }
  384. }
  385. return true;
  386. }
  387. protected function upcount_name_callback($matches) {
  388. $index = isset($matches[1]) ? intval($matches[1]) + 1 : 1;
  389. $ext = isset($matches[2]) ? $matches[2] : '';
  390. return ' ('.$index.')'.$ext;
  391. }
  392. protected function upcount_name($name) {
  393. return preg_replace_callback(
  394. '/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/',
  395. array($this, 'upcount_name_callback'),
  396. $name,
  397. 1
  398. );
  399. }
  400. protected function get_unique_filename($name,
  401. $type = null, $index = null, $content_range = null) {
  402. while(is_dir($this->get_upload_path($name))) {
  403. $name = $this->upcount_name($name);
  404. }
  405. // Keep an existing filename if this is part of a chunked upload:
  406. $uploaded_bytes = $this->fix_integer_overflow(intval($content_range[1]));
  407. while(is_file($this->get_upload_path($name))) {
  408. if ($uploaded_bytes === $this->get_file_size(
  409. $this->get_upload_path($name))) {
  410. break;
  411. }
  412. $name = $this->upcount_name($name);
  413. }
  414. return $name;
  415. }
  416. protected function trim_file_name($name,
  417. $type = null, $index = null, $content_range = null) {
  418. // Remove path information and dots around the filename, to prevent uploading
  419. // into different directories or replacing hidden system files.
  420. // Also remove control characters and spaces (\x00..\x20) around the filename:
  421. $name = trim(basename(stripslashes($name)), ".\x00..\x20");
  422. // Use a timestamp for empty filenames:
  423. if (!$name) {
  424. $name = str_replace('.', '-', microtime(true));
  425. }
  426. // Add missing file extension for known image types:
  427. if (strpos($name, '.') === false &&
  428. preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
  429. $name .= '.'.$matches[1];
  430. }
  431. return $name;
  432. }
  433. protected function get_file_name($name,
  434. $type = null, $index = null, $content_range = null) {
  435. return $this->get_unique_filename(
  436. $this->trim_file_name($name, $type, $index, $content_range),
  437. $type,
  438. $index,
  439. $content_range
  440. );
  441. }
  442. protected function handle_form_data($file, $index) {
  443. // Handle form data, e.g. $_REQUEST['description'][$index]
  444. }
  445. protected function get_scaled_image_file_paths($file_name, $version) {
  446. $file_path = $this->get_upload_path($file_name);
  447. if (!empty($version)) {
  448. $version_dir = $this->get_upload_path(null, $version);
  449. if (!is_dir($version_dir)) {
  450. mkdir($version_dir, $this->options['mkdir_mode'], true);
  451. }
  452. $new_file_path = $version_dir.'/'.$file_name;
  453. } else {
  454. $new_file_path = $file_path;
  455. }
  456. return array($file_path, $new_file_path);
  457. }
  458. protected function gd_get_image_object($file_path, $func, $no_cache = false) {
  459. if (empty($this->image_objects[$file_path]) || $no_cache) {
  460. $this->gd_destroy_image_object($file_path);
  461. $this->image_objects[$file_path] = $func($file_path);
  462. }
  463. return $this->image_objects[$file_path];
  464. }
  465. protected function gd_set_image_object($file_path, $image) {
  466. $this->gd_destroy_image_object($file_path);
  467. $this->image_objects[$file_path] = $image;
  468. }
  469. protected function gd_destroy_image_object($file_path) {
  470. $image = @$this->image_objects[$file_path];
  471. return $image && imagedestroy($image);
  472. }
  473. protected function gd_imageflip($image, $mode) {
  474. if (function_exists('imageflip')) {
  475. return imageflip($image, $mode);
  476. }
  477. $new_width = $src_width = imagesx($image);
  478. $new_height = $src_height = imagesy($image);
  479. $new_img = imagecreatetruecolor($new_width, $new_height);
  480. $src_x = 0;
  481. $src_y = 0;
  482. switch ($mode) {
  483. case '1': // flip on the horizontal axis
  484. $src_y = $new_height - 1;
  485. $src_height = -$new_height;
  486. break;
  487. case '2': // flip on the vertical axis
  488. $src_x = $new_width - 1;
  489. $src_width = -$new_width;
  490. break;
  491. case '3': // flip on both axes
  492. $src_y = $new_height - 1;
  493. $src_height = -$new_height;
  494. $src_x = $new_width - 1;
  495. $src_width = -$new_width;
  496. break;
  497. default:
  498. return $image;
  499. }
  500. imagecopyresampled(
  501. $new_img,
  502. $image,
  503. 0,
  504. 0,
  505. $src_x,
  506. $src_y,
  507. $new_width,
  508. $new_height,
  509. $src_width,
  510. $src_height
  511. );
  512. return $new_img;
  513. }
  514. protected function gd_orient_image($file_path, $src_img) {
  515. if (!function_exists('exif_read_data')) {
  516. return false;
  517. }
  518. $exif = @exif_read_data($file_path);
  519. if ($exif === false) {
  520. return false;
  521. }
  522. $orientation = intval(@$exif['Orientation']);
  523. if ($orientation < 2 || $orientation > 8) {
  524. return false;
  525. }
  526. switch ($orientation) {
  527. case 2:
  528. $new_img = $this->gd_imageflip(
  529. $src_img,
  530. defined('IMG_FLIP_VERTICAL') ? IMG_FLIP_VERTICAL : 2
  531. );
  532. break;
  533. case 3:
  534. $new_img = imagerotate($src_img, 180, 0);
  535. break;
  536. case 4:
  537. $new_img = $this->gd_imageflip(
  538. $src_img,
  539. defined('IMG_FLIP_HORIZONTAL') ? IMG_FLIP_HORIZONTAL : 1
  540. );
  541. break;
  542. case 5:
  543. $tmp_img = $this->gd_imageflip(
  544. $src_img,
  545. defined('IMG_FLIP_HORIZONTAL') ? IMG_FLIP_HORIZONTAL : 1
  546. );
  547. $new_img = imagerotate($tmp_img, 270, 0);
  548. imagedestroy($tmp_img);
  549. break;
  550. case 6:
  551. $new_img = imagerotate($src_img, 270, 0);
  552. break;
  553. case 7:
  554. $tmp_img = $this->gd_imageflip(
  555. $src_img,
  556. defined('IMG_FLIP_VERTICAL') ? IMG_FLIP_VERTICAL : 2
  557. );
  558. $new_img = imagerotate($tmp_img, 270, 0);
  559. imagedestroy($tmp_img);
  560. break;
  561. case 8:
  562. $new_img = imagerotate($src_img, 90, 0);
  563. break;
  564. default:
  565. return false;
  566. }
  567. $this->gd_set_image_object($file_path, $new_img);
  568. return true;
  569. }
  570. protected function gd_create_scaled_image($file_name, $version, $options) {
  571. if (!function_exists('imagecreatetruecolor')) {
  572. error_log('Function not found: imagecreatetruecolor');
  573. return false;
  574. }
  575. list($file_path, $new_file_path) =
  576. $this->get_scaled_image_file_paths($file_name, $version);
  577. $type = strtolower(substr(strrchr($file_name, '.'), 1));
  578. switch ($type) {
  579. case 'jpg':
  580. case 'jpeg':
  581. $src_func = 'imagecreatefromjpeg';
  582. $write_func = 'imagejpeg';
  583. $image_quality = isset($options['jpeg_quality']) ?
  584. $options['jpeg_quality'] : 75;
  585. break;
  586. case 'gif':
  587. $src_func = 'imagecreatefromgif';
  588. $write_func = 'imagegif';
  589. $image_quality = null;
  590. break;
  591. case 'png':
  592. $src_func = 'imagecreatefrompng';
  593. $write_func = 'imagepng';
  594. $image_quality = isset($options['png_quality']) ?
  595. $options['png_quality'] : 9;
  596. break;
  597. default:
  598. return false;
  599. }
  600. $src_img = $this->gd_get_image_object(
  601. $file_path,
  602. $src_func,
  603. !empty($options['no_cache'])
  604. );
  605. $image_oriented = false;
  606. if (!empty($options['auto_orient']) && $this->gd_orient_image(
  607. $file_path,
  608. $src_img
  609. )) {
  610. $image_oriented = true;
  611. $src_img = $this->gd_get_image_object(
  612. $file_path,
  613. $src_func
  614. );
  615. }
  616. $max_width = $img_width = imagesx($src_img);
  617. $max_height = $img_height = imagesy($src_img);
  618. if (!empty($options['max_width'])) {
  619. $max_width = $options['max_width'];
  620. }
  621. if (!empty($options['max_height'])) {
  622. $max_height = $options['max_height'];
  623. }
  624. $scale = min(
  625. $max_width / $img_width,
  626. $max_height / $img_height
  627. );
  628. if ($scale >= 1) {
  629. if ($image_oriented) {
  630. return $write_func($src_img, $new_file_path, $image_quality);
  631. }
  632. if ($file_path !== $new_file_path) {
  633. return copy($file_path, $new_file_path);
  634. }
  635. return true;
  636. }
  637. if (empty($options['crop'])) {
  638. $new_width = $img_width * $scale;
  639. $new_height = $img_height * $scale;
  640. $dst_x = 0;
  641. $dst_y = 0;
  642. $new_img = imagecreatetruecolor($new_width, $new_height);
  643. } else {
  644. if (($img_width / $img_height) >= ($max_width / $max_height)) {
  645. $new_width = $img_width / ($img_height / $max_height);
  646. $new_height = $max_height;
  647. } else {
  648. $new_width = $max_width;
  649. $new_height = $img_height / ($img_width / $max_width);
  650. }
  651. $dst_x = 0 - ($new_width - $max_width) / 2;
  652. $dst_y = 0 - ($new_height - $max_height) / 2;
  653. $new_img = imagecreatetruecolor($max_width, $max_height);
  654. }
  655. // Handle transparency in GIF and PNG images:
  656. switch ($type) {
  657. case 'gif':
  658. case 'png':
  659. imagecolortransparent($new_img, imagecolorallocate($new_img, 0, 0, 0));
  660. case 'png':
  661. imagealphablending($new_img, false);
  662. imagesavealpha($new_img, true);
  663. break;
  664. }
  665. $success = imagecopyresampled(
  666. $new_img,
  667. $src_img,
  668. $dst_x,
  669. $dst_y,
  670. 0,
  671. 0,
  672. $new_width,
  673. $new_height,
  674. $img_width,
  675. $img_height
  676. ) && $write_func($new_img, $new_file_path, $image_quality);
  677. $this->gd_set_image_object($file_path, $new_img);
  678. return $success;
  679. }
  680. protected function imagick_get_image_object($file_path, $no_cache = false) {
  681. if (empty($this->image_objects[$file_path]) || $no_cache) {
  682. $this->imagick_destroy_image_object($file_path);
  683. $image = new Imagick();
  684. if (!empty($this->options['imagick_resource_limits'])) {
  685. foreach ($this->options['imagick_resource_limits'] as $type => $limit) {
  686. $image->setResourceLimit($type, $limit);
  687. }
  688. }
  689. $image->readImage($file_path);
  690. $this->image_objects[$file_path] = $image;
  691. }
  692. return $this->image_objects[$file_path];
  693. }
  694. protected function imagick_set_image_object($file_path, $image) {
  695. $this->imagick_destroy_image_object($file_path);
  696. $this->image_objects[$file_path] = $image;
  697. }
  698. protected function imagick_destroy_image_object($file_path) {
  699. $image = @$this->image_objects[$file_path];
  700. return $image && $image->destroy();
  701. }
  702. protected function imagick_orient_image($image) {
  703. $orientation = $image->getImageOrientation();
  704. $background = new ImagickPixel('none');
  705. switch ($orientation) {
  706. case imagick::ORIENTATION_TOPRIGHT: // 2
  707. $image->flopImage(); // horizontal flop around y-axis
  708. break;
  709. case imagick::ORIENTATION_BOTTOMRIGHT: // 3
  710. $image->rotateImage($background, 180);
  711. break;
  712. case imagick::ORIENTATION_BOTTOMLEFT: // 4
  713. $image->flipImage(); // vertical flip around x-axis
  714. break;
  715. case imagick::ORIENTATION_LEFTTOP: // 5
  716. $image->flopImage(); // horizontal flop around y-axis
  717. $image->rotateImage($background, 270);
  718. break;
  719. case imagick::ORIENTATION_RIGHTTOP: // 6
  720. $image->rotateImage($background, 90);
  721. break;
  722. case imagick::ORIENTATION_RIGHTBOTTOM: // 7
  723. $image->flipImage(); // vertical flip around x-axis
  724. $image->rotateImage($background, 270);
  725. break;
  726. case imagick::ORIENTATION_LEFTBOTTOM: // 8
  727. $image->rotateImage($background, 270);
  728. break;
  729. default:
  730. return false;
  731. }
  732. $image->setImageOrientation(imagick::ORIENTATION_TOPLEFT); // 1
  733. return true;
  734. }
  735. protected function imagick_create_scaled_image($file_name, $version, $options) {
  736. list($file_path, $new_file_path) =
  737. $this->get_scaled_image_file_paths($file_name, $version);
  738. $image = $this->imagick_get_image_object(
  739. $file_path,
  740. !empty($options['no_cache'])
  741. );
  742. if ($image->getImageFormat() === 'GIF') {
  743. // Handle animated GIFs:
  744. $images = $image->coalesceImages();
  745. foreach ($images as $frame) {
  746. $image = $frame;
  747. $this->imagick_set_image_object($file_name, $image);
  748. break;
  749. }
  750. }
  751. $image_oriented = false;
  752. if (!empty($options['auto_orient'])) {
  753. $image_oriented = $this->imagick_orient_image($image);
  754. }
  755. $new_width = $max_width = $img_width = $image->getImageWidth();
  756. $new_height = $max_height = $img_height = $image->getImageHeight();
  757. if (!empty($options['max_width'])) {
  758. $new_width = $max_width = $options['max_width'];
  759. }
  760. if (!empty($options['max_height'])) {
  761. $new_height = $max_height = $options['max_height'];
  762. }
  763. if (!($image_oriented || $max_width < $img_width || $max_height < $img_height)) {
  764. if ($file_path !== $new_file_path) {
  765. return copy($file_path, $new_file_path);
  766. }
  767. return true;
  768. }
  769. $crop = !empty($options['crop']);
  770. if ($crop) {
  771. $x = 0;
  772. $y = 0;
  773. if (($img_width / $img_height) >= ($max_width / $max_height)) {
  774. $new_width = 0; // Enables proportional scaling based on max_height
  775. $x = ($img_width / ($img_height / $max_height) - $max_width) / 2;
  776. } else {
  777. $new_height = 0; // Enables proportional scaling based on max_width
  778. $y = ($img_height / ($img_width / $max_width) - $max_height) / 2;
  779. }
  780. }
  781. $success = $image->resizeImage(
  782. $new_width,
  783. $new_height,
  784. isset($options['filter']) ? $options['filter'] : imagick::FILTER_LANCZOS,
  785. isset($options['blur']) ? $options['blur'] : 1,
  786. $new_width && $new_height // fit image into constraints if not to be cropped
  787. );
  788. if ($success && $crop) {
  789. $success = $image->cropImage(
  790. $max_width,
  791. $max_height,
  792. $x,
  793. $y
  794. );
  795. if ($success) {
  796. $success = $image->setImagePage($max_width, $max_height, 0, 0);
  797. }
  798. }
  799. $type = strtolower(substr(strrchr($file_name, '.'), 1));
  800. switch ($type) {
  801. case 'jpg':
  802. case 'jpeg':
  803. if (!empty($options['jpeg_quality'])) {
  804. $image->setImageCompression(Imagick::COMPRESSION_JPEG);
  805. $image->setImageCompressionQuality($options['jpeg_quality']);
  806. }
  807. break;
  808. }
  809. if (!empty($options['strip'])) {
  810. $image->stripImage();
  811. }
  812. return $success && $image->writeImage($new_file_path);
  813. }
  814. protected function imagemagick_create_scaled_image($file_name, $version, $options) {
  815. list($file_path, $new_file_path) =
  816. $this->get_scaled_image_file_paths($file_name, $version);
  817. $resize = @$options['max_width']
  818. .(empty($options['max_height']) ? '' : 'x'.$options['max_height']);
  819. if (!$resize && empty($options['auto_orient'])) {
  820. if ($file_path !== $new_file_path) {
  821. return copy($file_path, $new_file_path);
  822. }
  823. return true;
  824. }
  825. $cmd = $this->options['convert_bin'];
  826. if (!empty($this->options['convert_params'])) {
  827. $cmd .= ' '.$this->options['convert_params'];
  828. }
  829. $cmd .= ' '.escapeshellarg($file_path);
  830. if (!empty($options['auto_orient'])) {
  831. $cmd .= ' -auto-orient';
  832. }
  833. if ($resize) {
  834. // Handle animated GIFs:
  835. $cmd .= ' -coalesce';
  836. if (empty($options['crop'])) {
  837. $cmd .= ' -resize '.escapeshellarg($resize.'>');
  838. } else {
  839. $cmd .= ' -resize '.escapeshellarg($resize.'^');
  840. $cmd .= ' -gravity center';
  841. $cmd .= ' -crop '.escapeshellarg($resize.'+0+0');
  842. }
  843. // Make sure the page dimensions are correct (fixes offsets of animated GIFs):
  844. $cmd .= ' +repage';
  845. }
  846. if (!empty($options['convert_params'])) {
  847. $cmd .= ' '.$options['convert_params'];
  848. }
  849. $cmd .= ' '.escapeshellarg($new_file_path);
  850. exec($cmd, $output, $error);
  851. if ($error) {
  852. error_log(implode('\n', $output));
  853. return false;
  854. }
  855. return true;
  856. }
  857. protected function get_image_size($file_path) {
  858. if ($this->options['image_library']) {
  859. if (extension_loaded('imagick')) {
  860. $image = new Imagick();
  861. try {
  862. if (@$image->pingImage($file_path)) {
  863. $dimensions = array($image->getImageWidth(), $image->getImageHeight());
  864. $image->destroy();
  865. return $dimensions;
  866. }
  867. return false;
  868. } catch (Exception $e) {
  869. error_log($e->getMessage());
  870. }
  871. }
  872. if ($this->options['image_library'] === 2) {
  873. $cmd = $this->options['identify_bin'];
  874. $cmd .= ' -ping '.escapeshellarg($file_path);
  875. exec($cmd, $output, $error);
  876. if (!$error && !empty($output)) {
  877. // image.jpg JPEG 1920x1080 1920x1080+0+0 8-bit sRGB 465KB 0.000u 0:00.000
  878. $infos = preg_split('/\s+/', $output[0]);
  879. $dimensions = preg_split('/x/', $infos[2]);
  880. return $dimensions;
  881. }
  882. return false;
  883. }
  884. }
  885. if (!function_exists('getimagesize')) {
  886. error_log('Function not found: getimagesize');
  887. return false;
  888. }
  889. return @getimagesize($file_path);
  890. }
  891. protected function create_scaled_image($file_name, $version, $options) {
  892. if ($this->options['image_library'] === 2) {
  893. return $this->imagemagick_create_scaled_image($file_name, $version, $options);
  894. }
  895. if ($this->options['image_library'] && extension_loaded('imagick')) {
  896. return $this->imagick_create_scaled_image($file_name, $version, $options);
  897. }
  898. return $this->gd_create_scaled_image($file_name, $version, $options);
  899. }
  900. protected function destroy_image_object($file_path) {
  901. if ($this->options['image_library'] && extension_loaded('imagick')) {
  902. return $this->imagick_destroy_image_object($file_path);
  903. }
  904. }
  905. protected function is_valid_image_file($file_path) {
  906. if (!preg_match($this->options['image_file_types'], $file_path)) {
  907. return false;
  908. }
  909. if (function_exists('exif_imagetype')) {
  910. return @exif_imagetype($file_path);
  911. }
  912. $image_info = $this->get_image_size($file_path);
  913. return $image_info && $image_info[0] && $image_info[1];
  914. }
  915. protected function handle_image_file($file_path, $file) {
  916. $failed_versions = array();
  917. foreach($this->options['image_versions'] as $version => $options) {
  918. if ($this->create_scaled_image($file->name, $version, $options)) {
  919. if (!empty($version)) {
  920. $file->{$version.'Url'} = $this->get_download_url(
  921. $file->name,
  922. $version
  923. );
  924. } else {
  925. $file->size = $this->get_file_size($file_path, true);
  926. }
  927. } else {
  928. $failed_versions[] = $version;
  929. }
  930. }
  931. switch (count($failed_versions)) {
  932. case 0:
  933. break;
  934. case 1:
  935. $file->error = 'Failed to create scaled version: '
  936. .$failed_versions[0];
  937. break;
  938. default:
  939. $file->error = 'Failed to create scaled versions: '
  940. .implode($failed_versions,', ');
  941. }
  942. // Free memory:
  943. $this->destroy_image_object($file_path);
  944. }
  945. protected function handle_file_upload($uploaded_file, $name, $size, $type, $error,
  946. $index = null, $content_range = null) {
  947. $file = new stdClass();
  948. $file->name = $this->get_file_name($name, $type, $index, $content_range);
  949. $file->size = $this->fix_integer_overflow(intval($size));
  950. $file->type = $type;
  951. if ($this->validate($uploaded_file, $file, $error, $index)) {
  952. $this->handle_form_data($file, $index);
  953. $upload_dir = $this->get_upload_path();
  954. if (!is_dir($upload_dir)) {
  955. mkdir($upload_dir, $this->options['mkdir_mode'], true);
  956. }
  957. $file_path = $this->get_upload_path($file->name);
  958. $append_file = $content_range && is_file($file_path) &&
  959. $file->size > $this->get_file_size($file_path);
  960. if ($uploaded_file && is_uploaded_file($uploaded_file)) {
  961. // multipart/formdata uploads (POST method uploads)
  962. if ($append_file) {
  963. file_put_contents(
  964. $file_path,
  965. fopen($uploaded_file, 'r'),
  966. FILE_APPEND
  967. );
  968. } else {
  969. move_uploaded_file($uploaded_file, $file_path);
  970. }
  971. } else {
  972. // Non-multipart uploads (PUT method support)
  973. file_put_contents(
  974. $file_path,
  975. fopen('php://input', 'r'),
  976. $append_file ? FILE_APPEND : 0
  977. );
  978. }
  979. $file_size = $this->get_file_size($file_path, $append_file);
  980. if ($file_size === $file->size) {
  981. $file->url = $this->get_download_url($file->name);
  982. if ($this->is_valid_image_file($file_path)) {
  983. $this->handle_image_file($file_path, $file);
  984. }
  985. } else {
  986. $file->size = $file_size;
  987. if (!$content_range && $this->options['discard_aborted_uploads']) {
  988. unlink($file_path);
  989. $file->error = 'abort';
  990. }
  991. }
  992. $this->set_additional_file_properties($file);
  993. }
  994. return $file;
  995. }
  996. protected function readfile($file_path) {
  997. $file_size = $this->get_file_size($file_path);
  998. $chunk_size = $this->options['readfile_chunk_size'];
  999. if ($chunk_size && $file_size > $chunk_size) {
  1000. $handle = fopen($file_path, 'rb');
  1001. while (!feof($handle)) {
  1002. echo fread($handle, $chunk_size);
  1003. ob_flush();
  1004. flush();
  1005. }
  1006. fclose($handle);
  1007. return $file_size;
  1008. }
  1009. return readfile($file_path);
  1010. }
  1011. protected function body($str) {
  1012. echo $str;
  1013. }
  1014. protected function header($str) {
  1015. header($str);
  1016. }
  1017. protected function get_server_var($id) {
  1018. return isset($_SERVER[$id]) ? $_SERVER[$id] : '';
  1019. }
  1020. protected function generate_response($content, $print_response = true) {
  1021. if ($print_response) {
  1022. $json = json_encode($content);
  1023. $redirect = isset($_REQUEST['redirect']) ?
  1024. stripslashes($_REQUEST['redirect']) : null;
  1025. if ($redirect) {
  1026. $this->header('Location: '.sprintf($redirect, rawurlencode($json)));
  1027. return;
  1028. }
  1029. $this->head();
  1030. if ($this->get_server_var('HTTP_CONTENT_RANGE')) {
  1031. $files = isset($content[$this->options['param_name']]) ?
  1032. $content[$this->options['param_name']] : null;
  1033. if ($files && is_array($files) && is_object($files[0]) && $files[0]->size) {
  1034. $this->header('Range: 0-'.(
  1035. $this->fix_integer_overflow(intval($files[0]->size)) - 1
  1036. ));
  1037. }
  1038. }
  1039. $this->body($json);
  1040. }
  1041. return $content;
  1042. }
  1043. protected function get_version_param() {
  1044. return isset($_GET['version']) ? basename(stripslashes($_GET['version'])) : null;
  1045. }
  1046. protected function get_singular_param_name() {
  1047. return substr($this->options['param_name'], 0, -1);
  1048. }
  1049. protected function get_file_name_param() {
  1050. $name = $this->get_singular_param_name();
  1051. return isset($_GET[$name]) ? basename(stripslashes($_GET[$name])) : null;
  1052. }
  1053. protected function get_file_names_params() {
  1054. $params = isset($_GET[$this->options['param_name']]) ?
  1055. $_GET[$this->options['param_name']] : array();
  1056. foreach ($params as $key => $value) {
  1057. $params[$key] = basename(stripslashes($value));
  1058. }
  1059. return $params;
  1060. }
  1061. protected function get_file_type($file_path) {
  1062. switch (strtolower(pathinfo($file_path, PATHINFO_EXTENSION))) {
  1063. case 'jpeg':
  1064. case 'jpg':
  1065. return 'image/jpeg';
  1066. case 'png':
  1067. return 'image/png';
  1068. case 'gif':
  1069. return 'image/gif';
  1070. default:
  1071. return '';
  1072. }
  1073. }
  1074. protected function download() {
  1075. switch ($this->options['download_via_php']) {
  1076. case 1:
  1077. $redirect_header = null;
  1078. break;
  1079. case 2:
  1080. $redirect_header = 'X-Sendfile';
  1081. break;
  1082. case 3:
  1083. $redirect_header = 'X-Accel-Redirect';
  1084. break;
  1085. default:
  1086. return $this->header('HTTP/1.1 403 Forbidden');
  1087. }
  1088. $file_name = $this->get_file_name_param();
  1089. if (!$this->is_valid_file_object($file_name)) {
  1090. return $this->header('HTTP/1.1 404 Not Found');
  1091. }
  1092. if ($redirect_header) {
  1093. return $this->header(
  1094. $redirect_header.': '.$this->get_download_url(
  1095. $file_name,
  1096. $this->get_version_param(),
  1097. true
  1098. )
  1099. );
  1100. }
  1101. $file_path = $this->get_upload_path($file_name, $this->get_version_param());
  1102. // Prevent browsers from MIME-sniffing the content-type:
  1103. $this->header('X-Content-Type-Options: nosniff');
  1104. if (!preg_match($this->options['inline_file_types'], $file_name)) {
  1105. $this->header('Content-Type: application/octet-stream');
  1106. $this->header('Content-Disposition: attachment; filename="'.$file_name.'"');
  1107. } else {
  1108. $this->header('Content-Type: '.$this->get_file_type($file_path));
  1109. $this->header('Content-Disposition: inline; filename="'.$file_name.'"');
  1110. }
  1111. $this->header('Content-Length: '.$this->get_file_size($file_path));
  1112. $this->header('Last-Modified: '.gmdate('D, d M Y H:i:s T', filemtime($file_path)));
  1113. $this->readfile($file_path);
  1114. }
  1115. protected function send_content_type_header() {
  1116. $this->header('Vary: Accept');
  1117. if (strpos($this->get_server_var('HTTP_ACCEPT'), 'application/json') !== false) {
  1118. $this->header('Content-type: application/json');
  1119. } else {
  1120. $this->header('Content-type: text/plain');
  1121. }
  1122. }
  1123. protected function send_access_control_headers() {
  1124. $this->header('Access-Control-Allow-Origin: '.$this->options['access_control_allow_origin']);
  1125. $this->header('Access-Control-Allow-Credentials: '
  1126. .($this->options['access_control_allow_credentials'] ? 'true' : 'false'));
  1127. $this->header('Access-Control-Allow-Methods: '
  1128. .implode(', ', $this->options['access_control_allow_methods']));
  1129. $this->header('Access-Control-Allow-Headers: '
  1130. .implode(', ', $this->options['access_control_allow_headers']));
  1131. }
  1132. public function head() {
  1133. $this->header('Pragma: no-cache');
  1134. $this->header('Cache-Control: no-store, no-cache, must-revalidate');
  1135. $this->header('Content-Disposition: inline; filename="files.json"');
  1136. // Prevent Internet Explorer from MIME-sniffing the content-type:
  1137. $this->header('X-Content-Type-Options: nosniff');
  1138. if ($this->options['access_control_allow_origin']) {
  1139. $this->send_access_control_headers();
  1140. }
  1141. $this->send_content_type_header();
  1142. }
  1143. public function get($print_response = true) {
  1144. if ($print_response && isset($_GET['download'])) {
  1145. return $this->download();
  1146. }
  1147. $file_name = $this->get_file_name_param();
  1148. if ($file_name) {
  1149. $response = array(
  1150. $this->get_singular_param_name() => $this->get_file_object($file_name)
  1151. );
  1152. } else {
  1153. $response = array(
  1154. $this->options['param_name'] => $this->get_file_objects()
  1155. );
  1156. }
  1157. return $this->generate_response($response, $print_response);
  1158. }
  1159. public function post($print_response = true) {
  1160. if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') {
  1161. return $this->delete($print_response);
  1162. }
  1163. $upload = isset($_FILES[$this->options['param_name']]) ?
  1164. $_FILES[$this->options['param_name']] : null;
  1165. // Parse the Content-Disposition header, if available:
  1166. $file_name = $this->get_server_var('HTTP_CONTENT_DISPOSITION') ?
  1167. rawurldecode(preg_replace(
  1168. '/(^[^"]+")|("$)/',
  1169. '',
  1170. $this->get_server_var('HTTP_CONTENT_DISPOSITION')
  1171. )) : null;
  1172. // Parse the Content-Range header, which has the following form:
  1173. // Content-Range: bytes 0-524287/2000000
  1174. $content_range = $this->get_server_var('HTTP_CONTENT_RANGE') ?
  1175. preg_split('/[^0-9]+/', $this->get_server_var('HTTP_CONTENT_RANGE')) : null;
  1176. $size = $content_range ? $content_range[3] : null;
  1177. $files = array();
  1178. if ($upload && is_array($upload['tmp_name'])) {
  1179. // param_name is an array identifier like "files[]",
  1180. // $_FILES is a multi-dimensional array:
  1181. foreach ($upload['tmp_name'] as $index => $value) {
  1182. $files[] = $this->handle_file_upload(
  1183. $upload['tmp_name'][$index],
  1184. $file_name ? $file_name : $upload['name'][$index],
  1185. $size ? $size : $upload['size'][$index],
  1186. $upload['type'][$index],
  1187. $upload['error'][$index],
  1188. $index,
  1189. $content_range
  1190. );
  1191. }
  1192. } else {
  1193. // param_name is a single object identifier like "file",
  1194. // $_FILES is a one-dimensional array:
  1195. $files[] = $this->handle_file_upload(
  1196. isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
  1197. $file_name ? $file_name : (isset($upload['name']) ?
  1198. $upload['name'] : null),
  1199. $size ? $size : (isset($upload['size']) ?
  1200. $upload['size'] : $this->get_server_var('CONTENT_LENGTH')),
  1201. isset($upload['type']) ?
  1202. $upload['type'] : $this->get_server_var('CONTENT_TYPE'),
  1203. isset($upload['error']) ? $upload['error'] : null,
  1204. null,
  1205. $content_range
  1206. );
  1207. }
  1208. return $this->generate_response(
  1209. array($this->options['param_name'] => $files),
  1210. $print_response
  1211. );
  1212. }
  1213. public function delete($print_response = true) {

Large files files are truncated, but you can click here to view the full file