4
0

class.uploader.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. <?php
  2. # ======================================================================== #
  3. #
  4. # Title [PHP] Uploader
  5. # Author: CreativeDream
  6. # Website: https://github.com/CreativeDream/php-uploader
  7. # Version: 0.4
  8. # Date: 14-Sep-2016
  9. # Purpose: Validate, Remove, Upload, Download files to server.
  10. # Information: Please don't forget to check your php.ini file for "upload_max_filesize", "post_max_size", "max_file_uploads"
  11. #
  12. # ======================================================================== #
  13. class Uploader {
  14. protected $options = array(
  15. 'limit' => null,
  16. 'maxSize' => null,
  17. 'extensions' => null,
  18. 'required' => false,
  19. 'uploadDir' => 'uploads/',
  20. 'title' => array('auto', 10),
  21. 'removeFiles' => true,
  22. 'perms' => null,
  23. 'replace' => true,
  24. 'onCheck' => null,
  25. 'onError' => null,
  26. 'onSuccess' => null,
  27. 'onUpload' => null,
  28. 'onComplete' => null,
  29. 'onRemove' => null
  30. );
  31. public $error_messages = array(
  32. 1 => "The uploaded file exceeds the upload_max_filesize directive in php.ini.",
  33. 2 => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.",
  34. 3 => "The uploaded file was only partially uploaded.",
  35. 4 => "No file was uploaded.",
  36. 6 => "Missing a temporary folder.",
  37. 7 => "Failed to write file to disk.",
  38. 8 => "A PHP extension stopped the file upload.",
  39. 'accept_file_types' => "Filetype not allowed",
  40. 'file_uploads' => "File uploading option in disabled in php.ini",
  41. 'post_max_size' => "The uploaded file exceeds the post_max_size directive in php.ini",
  42. 'max_file_size' => "File is too big",
  43. 'max_number_of_files' => "Maximum number of files exceeded",
  44. 'required_and_no_file' => "No file was choosed. Please select one.",
  45. 'no_download_content' => "File could't be download."
  46. );
  47. private $field = null;
  48. private $data = array(
  49. "hasErrors" => false,
  50. "hasWarnings" => false,
  51. "isSuccess" => false,
  52. "isComplete" => false,
  53. "data" => array(
  54. "files" => array(),
  55. "metas" => array()
  56. )
  57. );
  58. public function __construct(){
  59. // __construct function
  60. $this->cache_data = $this->data;
  61. }
  62. /**
  63. * upload method
  64. *
  65. * Return the initialize method
  66. * @param $field {Array, String}
  67. * @param $options {Array, null}
  68. * @return array
  69. */
  70. public function upload($field, $options = null){
  71. $this->data = $this->cache_data;
  72. return $this->initialize($field, $options);
  73. }
  74. /**
  75. * initialize method
  76. *
  77. * Initialize field values and properties.
  78. * Merge options
  79. * Prepare files
  80. * @param $field {Array, String}
  81. * @param $options {Array, null}
  82. * @return array
  83. */
  84. private function initialize($field, $options){
  85. if(is_array($field) && in_array($field, $_FILES)){
  86. $this->field = $field;
  87. $this->field['Field_Name'] = array_search($field, $_FILES);
  88. $this->field['Field_Type'] = 'input';
  89. if(!is_array($this->field['name'])){
  90. $this->field = array_merge($this->field, array("name" => array($this->field['name']), "tmp_name"=>array($this->field['tmp_name']), "type"=>array($this->field['type']), "error"=>array($this->field['error']), "size"=>array($this->field['size'])));
  91. }
  92. foreach($this->field['name'] as $key=>$value){ if(empty($value)){ unset($this->field['name'][$key]); unset($this->field['type'][$key]); unset($this->field['tmp_name'][$key]); unset($this->field['error'][$key]); unset($this->field['size'][$key]); } }
  93. $this->field['length'] = count($this->field['name']);
  94. }elseif(is_string($field) && $this->isURL($field)){
  95. $this->field = array("name" => array($field), "size"=>array(), "type"=>array(), "error"=>array());
  96. $this->field['Field_Type'] = 'link';
  97. $this->field['length'] = 1;
  98. }else{
  99. return false;
  100. }
  101. if($options != null){
  102. $this->setOptions($options);
  103. }
  104. return $this->prepareFiles();
  105. }
  106. /**
  107. * setOptions method
  108. *
  109. * Merge options
  110. * @param $options {Array}
  111. */
  112. private function setOptions($options){
  113. if(!is_array($options)){return false;}
  114. $this->options = array_merge($this->options, $options);
  115. }
  116. /**
  117. * validation method
  118. *
  119. * Check the field and files
  120. * @return boolean
  121. */
  122. private function validate($file = null){
  123. $field = $this->field;
  124. $errors = array();
  125. $options = $this->options;
  126. if($file == null){
  127. $ini = array(ini_get('file_uploads'),((int) ini_get('upload_max_filesize')),((int) ini_get('post_max_size')), ((int) ini_get('max_file_uploads')));
  128. if(!isset($field) || empty($field)) return false;
  129. if(!$ini[0]) $errors[] = $this->error_messages['file_uploads'];
  130. if($options['required'] && $field['length'] == 0) $errors[] = $this->error_messages['required_and_no_file'];
  131. if(($options['limit'] && $field['length'] > $options['limit']) || ($field['length']) > $ini[3]) $errors[] = $this->error_messages['max_number_of_files'];
  132. if(!file_exists($options['uploadDir']) && !is_dir($options['uploadDir']) && mkdir($options['uploadDir'], 750, true)){
  133. $this->data['hasWarnings'] = true;
  134. $this->data['warnings'] = "A new directory was created in " . realpath($options['uploadDir']);
  135. }
  136. if(!is_writable($options['uploadDir'])) @chmod($options['uploadDir'], 750);
  137. if($field['Field_Type'] == "input"){
  138. $total_size = 0; foreach($this->field['size'] as $key=>$value){ $total_size += $value; } $total_size = $total_size/1048576;
  139. if($options['maxSize'] && $total_size > $options['maxSize']) $errors[] = $this->error_messages['max_file_size'];
  140. if($ini[1] != 0 && $total_size > $ini[1]) $errors[] = $this->error_messages[1];
  141. if($ini[2] != 0 && $total_size > $ini[2]) $errors[] = $this->error_messages['post_max_size'];
  142. }
  143. }else{
  144. if(@$field['error'][$file['index']] > 0 && array_key_exists($field['error'][$file['index']], $this->error_messages)) $errors[] = $this->error_messages[$field['error'][$file['index']]];
  145. if($options['extensions'] && !in_array($file['extension'], $options['extensions'])) $errors[] = $this->error_messages['accept_file_types'];
  146. if($file['type'][0] == "image" && @!is_array(getimagesize($file['tmp']))) $errors[] = $this->error_messages['accept_file_types'];
  147. if($options['maxSize'] && $file['size'][0] > $options['maxSize']) $errors[] = $this->error_messages['max_file_size'];
  148. if($field['Field_Type'] == 'link' && empty($this->cache_download_content)) $errors[] = "";
  149. }
  150. $custom = $this->_onCheck($file); if($custom) $errors = array_merge($errors, $custom);
  151. if(!empty($errors)){
  152. $this->data['hasErrors'] = true;
  153. if(!isset($this->data['errors'])) $this->data['errors'] = array();
  154. $this->data['errors'][] = $errors;
  155. $custom = $this->_onError($errors, $file);
  156. return false;
  157. }else{
  158. return true;
  159. }
  160. }
  161. /**
  162. * prepareFiles method
  163. *
  164. * Prepare files for upload/download and generate meta infos
  165. * @return $this->data
  166. */
  167. private function prepareFiles(){
  168. $field = $this->field;
  169. $validate = $this->validate();
  170. if($validate){
  171. $files = array();
  172. $removed_files = $this->removeFiles();
  173. $isAddMoreMode = count(preg_grep('/^(\d+)\:\/\/(.*)/i', $removed_files)) > 0;
  174. $addMoreMatches = array();
  175. for($i = 0; $i < count($field['name']); $i++){
  176. $metas = array();
  177. if($field['Field_Type'] == 'input'){
  178. $tmp_name = $field['tmp_name'][$i];
  179. }elseif($field['Field_Type'] == 'link'){
  180. $link = $this->downloadFile($field['name'][0], false, true);
  181. $tmp_name = $field['name'][0];
  182. $field['name'][0] = pathinfo($field['name'][0], PATHINFO_BASENAME);
  183. $field['type'][0] = $link['type'];
  184. $field['size'][0] = $link['size'];
  185. $field['error'][0] = 0;
  186. }
  187. $metas['extension'] = substr(strrchr($field['name'][$i], "."),1);
  188. $metas['type'] = preg_split('[/]', $field['type'][$i]);
  189. $metas['extension'] = $field['Field_Type'] == 'link' && empty($metas['extension']) ? $metas['type'][1] : $metas['extension'];
  190. $metas['old_name'] = $field['name'][$i];
  191. $metas['size'] = $field['size'][$i];
  192. $metas['size2'] = $this->formatSize($metas['size']);
  193. $metas['name'] = $this->generateFileName($this->options['title'], array('name'=>substr($metas['old_name'], 0, (!empty($metas['extension']) ? -(strlen($metas['extension'])+1) : strlen($metas['old_name']))), 'size'=>$metas['size'], 'extension'=> $metas['extension']));
  194. $metas['file'] = $this->options['uploadDir'] . $metas['name'];
  195. $metas['replaced'] = file_exists($metas['file']);
  196. $metas['date'] = date('r');
  197. $is_file_removed = in_array($field['name'][$i], $removed_files);
  198. if($isAddMoreMode){
  199. $addMoreMatches[$field['name'][$i]][] = $i;
  200. $matches = preg_grep('/^'.(count($addMoreMatches[$field['name'][$i]])-1).'\:\/\/'.$field['name'][$i].'/i', $removed_files);
  201. if(count($matches) == 1) {
  202. $is_file_removed = true;
  203. }
  204. }
  205. if(!$is_file_removed && $this->validate(array_merge($metas, array('index'=>$i, 'tmp'=>$tmp_name))) && $this->uploadFile($tmp_name, $metas['file'])){
  206. if($this->options['perms']) @chmod($metas['file'], $this->options['perms']);
  207. $custom = $this->_onUpload($metas, $this->field); if($custom && is_array($custom)) $metas = array_merge($custom, $metas);
  208. ksort($metas);
  209. $files[] = $metas['file'];
  210. $this->data['data']['metas'][] = $metas;
  211. }
  212. }
  213. $this->data['isSuccess'] = count($field['name']) - count($removed_files) == count($files);
  214. $this->data['data']['files'] = $files;
  215. if($this->data['isSuccess']) $custom = $this->_onSuccess($this->data['data']['files'], $this->data['data']['metas']);
  216. $this->data['isComplete'] = true;
  217. $custom = $this->_onComplete($this->data['data']['files'], $this->data['data']['metas']);
  218. }
  219. return $this->data;
  220. }
  221. /**
  222. * uploadFile method
  223. *
  224. * Upload/Download files to server
  225. * @return boolean
  226. */
  227. private function uploadFile($source, $destination){
  228. if($this->field['Field_Type'] == 'input'){
  229. return @move_uploaded_file($source, $destination);
  230. }elseif($this->field['Field_Type'] == 'link'){
  231. return $this->downloadFile($source, $destination);
  232. }
  233. }
  234. /**
  235. * removeFiles method
  236. *
  237. * Remove files or cancel upload for them
  238. * @return array
  239. */
  240. private function removeFiles(){
  241. $removed_files = array();
  242. if($this->options['removeFiles'] !== false){
  243. foreach($_POST as $key=>$value){
  244. preg_match((is_string($this->options['removeFiles']) ? $this->options['removeFiles'] : '/jfiler-items-exclude-'.$this->field['Field_Name'].'-(\d+)/'), $key, $matches);
  245. if(isset($matches) && !empty($matches)){
  246. $input = $_POST[$matches[0]];
  247. if($this->isJson($input)) $removed_files = json_decode($input, true);
  248. $custom = $this->_onRemove($removed_files, $this->field); if($custom && is_array($custom)) $removed_files = $custom;
  249. }
  250. }
  251. }
  252. return $removed_files;
  253. }
  254. /**
  255. * downloadFile method
  256. *
  257. * Download file to server
  258. * @return boolean
  259. */
  260. private function downloadFile($source, $destination, $info = false){
  261. set_time_limit(80);
  262. $forInfo = array(
  263. "size" => 1,
  264. "type" => "text/plain"
  265. );
  266. if(!isset($this->cache_download_content)){
  267. $file_content = file_get_contents($source);
  268. if($info){
  269. $headers = implode(" ", $http_response_header);
  270. if(preg_match('/Content-Length: (\d+)/', $headers, $matches)) $forInfo['size'] = $matches[1];
  271. if(preg_match('/Content-Type: (\w+\/\w+)/', $headers, $matches)) $forInfo['type'] = $matches[1];
  272. $this->cache_download_content = $file_content;
  273. return $forInfo;
  274. }
  275. }else{
  276. $file_content = $this->cache_download_content;
  277. }
  278. $downloaded_file = @fopen($destination, 'w');
  279. $written = @fwrite($downloaded_file, $file_content);
  280. @fclose($downloaded_file);
  281. return $written;
  282. }
  283. /**
  284. * generateFileName method
  285. *
  286. * Generated a file name by uploading
  287. * @return boolean
  288. */
  289. private function generateFileName($conf, $file, $skip_replace_check = false){
  290. $file['name'] = preg_replace("[^\w\s\d\.\-_~,;:\[\]\(\]]", '', $file['name']);
  291. $type = is_array($conf) && isset($conf[0]) ? $conf[0] : $conf;
  292. $type = $type ? $type : 'name';
  293. $length = is_array($conf) && isset($conf[1]) ? $conf[1] : null;
  294. $random_string = substr(str_shuffle("_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, $length ? $length : 10);
  295. $extension = !empty($file['extension']) ? "." . $file['extension'] : "";
  296. $string = "";
  297. $is_extension_used = false;
  298. switch($type){
  299. case "auto":
  300. $string = $random_string;
  301. break;
  302. case "name":
  303. $string = $file['name'];
  304. break;
  305. default:
  306. $string = $type;
  307. if(strpos($string, "{{random}}") !== false){
  308. $string = str_replace("{{random}}", $random_string, $string);
  309. }
  310. if(strpos($string, "{{file_name}}") !== false){
  311. $string = str_replace("{{file_name}}", $file['name'], $string);
  312. }
  313. if(strpos($string, "{{file_size}}") !== false){
  314. $string = str_replace("{{file_size}}", $file['size'], $string);
  315. }
  316. if(strpos($string, "{{timestamp}}") !== false){
  317. $string = str_replace("{{timestamp}}", time(), $string);
  318. }
  319. if(strpos($string, "{{date}}") !== false){
  320. $string = str_replace("{{date}}", date('Y-n-d_H:i:s'), $string);
  321. }
  322. if(strpos($string, "{{extension}}") !== false){
  323. $is_extension_used = true;
  324. $string = str_replace("{{extension}}", $file['extension'], $string);
  325. }
  326. if(strpos($string, "{{.extension}}") !== false){
  327. $is_extension_used = true;
  328. $string = str_replace("{{.extension}}", $extension, $string);
  329. }
  330. }
  331. if(!$is_extension_used)
  332. $string .= $extension;
  333. if(!$this->options['replace'] && !$skip_replace_check){
  334. $name = $file['name'];
  335. $i = 1;
  336. while (file_exists($this->options['uploadDir'].$string)) {
  337. $file['name'] = $name . " ({$i})";
  338. $string = $this->generateFileName($conf, $file, true);
  339. $i++;
  340. }
  341. }
  342. return $string;
  343. }
  344. /**
  345. * isJson method
  346. *
  347. * Check if string is a valid json
  348. * @return boolean
  349. */
  350. private function isJson($string) {
  351. json_decode($string);
  352. return (json_last_error() == JSON_ERROR_NONE);
  353. }
  354. /**
  355. * isURL method
  356. *
  357. * Check if string $url is a link
  358. * @return boolean
  359. */
  360. private function isURL($url){
  361. return preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $url);
  362. }
  363. /**
  364. * formatSize method
  365. *
  366. * Convert file size
  367. * @return float
  368. */
  369. private function formatSize($bytes){
  370. if ($bytes >= 1073741824){
  371. $bytes = number_format($bytes / 1073741824, 2) . ' GB';
  372. }elseif ($bytes >= 1048576){
  373. $bytes = number_format($bytes / 1048576, 2) . ' MB';
  374. }elseif ($bytes > 0){
  375. $bytes = number_format($bytes / 1024, 2) . ' KB';
  376. }else{
  377. $bytes = '0 bytes';
  378. }
  379. return $bytes;
  380. }
  381. private function _onCheck(){
  382. $arguments = func_get_args();
  383. return $this->options['onCheck'] != null && function_exists($this->options['onCheck']) ? $this->options['onCheck'](@$arguments[0]) : null;
  384. }
  385. private function _onSuccess(){
  386. $arguments = func_get_args();
  387. return $this->options['onSuccess'] != null && function_exists($this->options['onSuccess']) ? $this->options['onSuccess'](@$arguments[0], @$arguments[1]) : null;
  388. }
  389. private function _onError(){
  390. $arguments = func_get_args();
  391. return $this->options['onError'] && function_exists($this->options['onError']) ? $this->options['onError'](@$arguments[0], @$arguments[1]) : null;
  392. }
  393. private function _onUpload(){
  394. $arguments = func_get_args();
  395. return $this->options['onUpload'] && function_exists($this->options['onUpload']) ? $this->options['onUpload'](@$arguments[0], @$arguments[1]) : null;
  396. }
  397. private function _onComplete(){
  398. $arguments = func_get_args();
  399. return $this->options['onComplete'] != null && function_exists($this->options['onComplete']) ? $this->options['onComplete'](@$arguments[0], @$arguments[1]) : null;
  400. }
  401. private function _onRemove(){
  402. $arguments = func_get_args();
  403. return $this->options['onRemove'] && function_exists($this->options['onRemove']) ? $this->options['onRemove'](@$arguments[0], @$arguments[1]) : null;
  404. }
  405. }
  406. ?>