PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/fuel/core/classes/upload.php

https://bitbucket.org/codeyash/bootstrap
PHP | 731 lines | 472 code | 87 blank | 172 comment | 62 complexity | 16388443e18b065b237697b535a92071 MD5 | raw file
Possible License(s): MIT, Apache-2.0
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.5
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2013 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. /**
  14. * Upload Class
  15. *
  16. * @package Fuel
  17. * @category Core
  18. * @author Harro "WanWizard" Verton
  19. * @link http://docs.fuelphp.com/classes/upload.html
  20. */
  21. class Upload
  22. {
  23. /* ---------------------------------------------------------------------------
  24. * ERROR CODE CONSTANTS
  25. * --------------------------------------------------------------------------- */
  26. // duplicate the PHP standard error codes for consistency
  27. const UPLOAD_ERR_OK = UPLOAD_ERR_OK;
  28. const UPLOAD_ERR_INI_SIZE = UPLOAD_ERR_INI_SIZE;
  29. const UPLOAD_ERR_FORM_SIZE = UPLOAD_ERR_FORM_SIZE;
  30. const UPLOAD_ERR_PARTIAL = UPLOAD_ERR_PARTIAL;
  31. const UPLOAD_ERR_NO_FILE = UPLOAD_ERR_NO_FILE;
  32. const UPLOAD_ERR_NO_TMP_DIR = UPLOAD_ERR_NO_TMP_DIR;
  33. const UPLOAD_ERR_CANT_WRITE = UPLOAD_ERR_CANT_WRITE;
  34. const UPLOAD_ERR_EXTENSION = UPLOAD_ERR_EXTENSION;
  35. // and add our own error codes
  36. const UPLOAD_ERR_MAX_SIZE = 101;
  37. const UPLOAD_ERR_EXT_BLACKLISTED = 102;
  38. const UPLOAD_ERR_EXT_NOT_WHITELISTED = 103;
  39. const UPLOAD_ERR_TYPE_BLACKLISTED = 104;
  40. const UPLOAD_ERR_TYPE_NOT_WHITELISTED = 105;
  41. const UPLOAD_ERR_MIME_BLACKLISTED = 106;
  42. const UPLOAD_ERR_MIME_NOT_WHITELISTED = 107;
  43. const UPLOAD_ERR_MAX_FILENAME_LENGTH = 108;
  44. const UPLOAD_ERR_MOVE_FAILED = 109;
  45. const UPLOAD_ERR_DUPLICATE_FILE = 110;
  46. const UPLOAD_ERR_MKDIR_FAILED = 111;
  47. const UPLOAD_ERR_FTP_FAILED = 112;
  48. /* ---------------------------------------------------------------------------
  49. * STATIC PROPERTIES
  50. * --------------------------------------------------------------------------- */
  51. /**
  52. * @var array default configuration values
  53. */
  54. protected static $_defaults = array(
  55. 'auto_process' => false,
  56. // validation settings
  57. 'max_size' => 0,
  58. 'max_length' => 0,
  59. 'ext_whitelist' => array(),
  60. 'ext_blacklist' => array(),
  61. 'type_whitelist' => array(),
  62. 'type_blacklist' => array(),
  63. 'mime_whitelist' => array(),
  64. 'mime_blacklist' => array(),
  65. // save settings
  66. 'path' => '',
  67. 'prefix' => '',
  68. 'suffix' => '',
  69. 'extension' => '',
  70. 'create_path' => true,
  71. 'path_chmod' => 0777,
  72. 'file_chmod' => 0666,
  73. 'auto_rename' => true,
  74. 'overwrite' => false,
  75. 'randomize' => false,
  76. 'normalize' => false,
  77. 'normalize_separator' => '_',
  78. 'change_case' => false,
  79. 'ftp_mode' => 'auto',
  80. 'ftp_permissions' => null
  81. );
  82. /**
  83. * @var array defined callbacks
  84. */
  85. protected static $callbacks = array(
  86. 'validate' => null,
  87. 'before' => null,
  88. 'after' => null
  89. );
  90. /**
  91. * @var array configuration of this instance
  92. */
  93. protected static $config = array();
  94. /**
  95. * @var array normalized $_FILES array
  96. */
  97. protected static $files = array();
  98. /**
  99. * @var bool indicator of valid uploads
  100. */
  101. protected static $valid = false;
  102. /**
  103. * @var object Ftp object
  104. */
  105. protected static $with_ftp = false;
  106. /* ---------------------------------------------------------------------------
  107. * STATIC METHODS
  108. * --------------------------------------------------------------------------- */
  109. /**
  110. * class initialisation, load the config and process $_FILES if needed
  111. *
  112. * @return void
  113. */
  114. public static function _init()
  115. {
  116. // get the config for this upload
  117. \Config::load('upload', true);
  118. // get the language file for this upload
  119. \Lang::load('upload', true);
  120. // make sure we have defaults for those not defined
  121. static::$config = array_merge(static::$_defaults, \Config::get('upload', array()));
  122. static::$config['auto_process'] and static::process();
  123. }
  124. // ---------------------------------------------------------------------------
  125. /**
  126. * Check if we have valid files
  127. *
  128. * @return bool true if static:$files contains uploaded files that are valid
  129. */
  130. public static function is_valid()
  131. {
  132. return static::$valid;
  133. }
  134. // ---------------------------------------------------------------------------
  135. /**
  136. * Get the list of validated files
  137. *
  138. * @return array list of uploaded files that are validated
  139. */
  140. public static function get_files($index = null)
  141. {
  142. // if no parameters were passed, return all files successfully uploaded
  143. if (func_num_args() == 0)
  144. {
  145. return array_filter(static::$files, function($file) { return $file['error'] === false; } );
  146. }
  147. // if an index number was given, return that file only (if that was successful)
  148. elseif (isset(static::$files[$index]) and static::$files[$index]['error'] === false)
  149. {
  150. return static::$files[$index];
  151. }
  152. // if an field name was given, return that file only (if that was successful)
  153. else
  154. {
  155. foreach (static::$files as $file)
  156. {
  157. if ($file['field'] == $index and $file['error'] === false)
  158. {
  159. return $file;
  160. }
  161. }
  162. throw new \FuelException('No valid uploaded file exists with index "'.$index.'"');
  163. }
  164. }
  165. // ---------------------------------------------------------------------------
  166. /**
  167. * Get the list of non-validated files
  168. *
  169. * @return array list of uploaded files that failed to validate
  170. */
  171. public static function get_errors($index = null)
  172. {
  173. // if no parameters were passed, return all files in error
  174. if (func_num_args() == 0)
  175. {
  176. return array_filter(static::$files, function($file) { return $file['error'] === true; } );
  177. }
  178. // if an index number was given, return that file only (if it is in error)
  179. elseif (isset(static::$files[$index]) and static::$files[$index]['error'] === true)
  180. {
  181. return static::$files[$index];
  182. }
  183. // if an field name was given, return that file only (if it is in error)
  184. else
  185. {
  186. foreach (static::$files as $file)
  187. {
  188. if ($file['field'] == $index and $file['error'] === true)
  189. {
  190. return $file;
  191. }
  192. }
  193. throw new \FuelException('No invalid uploaded file exists with index "'.$index.'"');
  194. }
  195. }
  196. // --------------------------------------------------------------------
  197. /**
  198. * Register
  199. *
  200. * Registers a Callback for a given event
  201. *
  202. * @access public
  203. * @param string The name of the event
  204. * @param mixed callback information
  205. * @return void
  206. */
  207. public static function register()
  208. {
  209. // get any arguments passed
  210. $callback = func_get_args();
  211. // if the arguments are valid, register the callback
  212. if (isset($callback[0]) and is_string($callback[0]) and isset($callback[1]) and is_callable($callback[1]))
  213. {
  214. // make sure we have an entry for this callback
  215. if (array_key_exists($callback[0], static::$callbacks))
  216. {
  217. static::$callbacks[array_shift($callback)] = $callback;
  218. // report success
  219. return true;
  220. }
  221. }
  222. // can't register the callback
  223. return false;
  224. }
  225. // ---------------------------------------------------------------------------
  226. /**
  227. * Normalize the $_FILES array and store the result in $files
  228. *
  229. * @return void
  230. */
  231. public static function process($config = array())
  232. {
  233. // process runtime config
  234. if (is_array($config))
  235. {
  236. static::$config = array_merge(static::$config, $config);
  237. }
  238. // processed files array's
  239. static::$files = array();
  240. $files = array();
  241. // any files uploaded?
  242. if ( empty($_FILES))
  243. {
  244. throw new \FuelException('No file upload was initiated. Did you specify "enctype" in your &lt;form&gt; tag?');
  245. }
  246. // normalize the multidimensional fields in the $_FILES array
  247. foreach($_FILES as $name => $value)
  248. {
  249. if (is_array($value['name']))
  250. {
  251. foreach($value as $field => $content)
  252. {
  253. foreach(\Arr::flatten($content) as $element => $data)
  254. {
  255. $_FILES[$name.':'.$element][$field] = $data;
  256. }
  257. }
  258. unset($_FILES[$name]);
  259. }
  260. }
  261. // normalize the $_FILES array
  262. foreach($_FILES as $name => $value)
  263. {
  264. // store the file data
  265. $file = array('field' => $name, 'file' => $value['tmp_name']);
  266. if ($value['error'])
  267. {
  268. $file['error'] = true;
  269. $file['errors'][] = array('error' => $value['error']);
  270. }
  271. else
  272. {
  273. $file['error'] = false;
  274. $file['errors'] = array();
  275. }
  276. unset($value['tmp_name']);
  277. $files[] = array_merge($value, $file);
  278. }
  279. // verify and augment the files data
  280. foreach($files as $key => $value)
  281. {
  282. // add some filename details (pathinfo can't be trusted with utf-8 filenames!)
  283. $files[$key]['extension'] = ltrim(strrchr(ltrim($files[$key]['name'], '.'), '.'),'.');
  284. if (empty($files[$key]['extension']))
  285. {
  286. $files[$key]['filename'] = $files[$key]['name'];
  287. }
  288. else
  289. {
  290. $files[$key]['filename'] = substr($files[$key]['name'], 0, strlen($files[$key]['name'])-(strlen($files[$key]['extension'])+1));
  291. }
  292. // does this upload exceed the maximum size?
  293. if (! empty(static::$config['max_size']) and $files[$key]['size'] > static::$config['max_size'])
  294. {
  295. $files[$key]['error'] = true;
  296. $files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_MAX_SIZE);
  297. }
  298. // add mimetype information
  299. if ( ! $files[$key]['error'])
  300. {
  301. $handle = finfo_open(FILEINFO_MIME_TYPE);
  302. $files[$key]['mimetype'] = finfo_file($handle, $value['file']);
  303. finfo_close($handle);
  304. if ($files[$key]['mimetype'] == 'application/octet-stream' and $files[$key]['type'] != $files[$key]['mimetype'])
  305. {
  306. $files[$key]['mimetype'] = $files[$key]['type'];
  307. }
  308. // make sure it contains something valid
  309. if (empty($files[$key]['mimetype']))
  310. {
  311. $files[$key]['mimetype'] = 'application/octet-stream';
  312. }
  313. }
  314. // check the file extension black- and whitelists
  315. if ( ! $files[$key]['error'])
  316. {
  317. if (in_array(strtolower($files[$key]['extension']), (array) static::$config['ext_blacklist']))
  318. {
  319. $files[$key]['error'] = true;
  320. $files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_EXT_BLACKLISTED);
  321. }
  322. elseif ( ! empty(static::$config['ext_whitelist']) and ! in_array(strtolower($files[$key]['extension']), (array) static::$config['ext_whitelist']))
  323. {
  324. $files[$key]['error'] = true;
  325. $files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_EXT_NOT_WHITELISTED);
  326. }
  327. }
  328. // check the file type black- and whitelists
  329. if ( ! $files[$key]['error'])
  330. {
  331. // split the mimetype info so we can run some tests
  332. preg_match('|^(.*)/(.*)|', $files[$key]['mimetype'], $mimeinfo);
  333. if (in_array($mimeinfo[1], (array) static::$config['type_blacklist']))
  334. {
  335. $files[$key]['error'] = true;
  336. $files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_TYPE_BLACKLISTED);
  337. }
  338. if ( ! empty(static::$config['type_whitelist']) and ! in_array($mimeinfo[1], (array) static::$config['type_whitelist']))
  339. {
  340. $files[$key]['error'] = true;
  341. $files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_TYPE_NOT_WHITELISTED);
  342. }
  343. }
  344. // check the file mimetype black- and whitelists
  345. if ( ! $files[$key]['error'])
  346. {
  347. if (in_array($files[$key]['mimetype'], (array) static::$config['mime_blacklist']))
  348. {
  349. $files[$key]['error'] = true;
  350. $files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_MIME_BLACKLISTED);
  351. }
  352. elseif ( ! empty(static::$config['mime_whitelist']) and ! in_array($files[$key]['mimetype'], (array) static::$config['mime_whitelist']))
  353. {
  354. $files[$key]['error'] = true;
  355. $files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_MIME_NOT_WHITELISTED);
  356. }
  357. }
  358. // store the normalized and validated result
  359. static::$files[$key] = $files[$key];
  360. // validation callback defined?
  361. if (array_key_exists('validate', static::$callbacks) and ! is_null(static::$callbacks['validate']))
  362. {
  363. // get the callback method
  364. $callback = static::$callbacks['validate'][0];
  365. // call the callback
  366. if (is_callable($callback))
  367. {
  368. $result = call_user_func_array($callback, array(&static::$files[$key]));
  369. if (is_numeric($result) and $result)
  370. {
  371. static::$files[$key]['error'] = true;
  372. static::$files[$key]['errors'][] = array('error' => $result);
  373. }
  374. }
  375. }
  376. // and add the message texts
  377. foreach (static::$files[$key]['errors'] as $e => $error)
  378. {
  379. empty(static::$files[$key]['errors'][$e]['message']) and static::$files[$key]['errors'][$e]['message'] = \Lang::get('upload.error_'.$error['error']);
  380. }
  381. }
  382. // determine the validate status of at least one uploaded file
  383. $valid = false;
  384. foreach(static::$files as $key => $value)
  385. {
  386. if ($value['error'] === false)
  387. {
  388. $valid = true;
  389. break;
  390. }
  391. }
  392. static::$valid = $valid;
  393. }
  394. // ---------------------------------------------------------------------------
  395. /**
  396. * Upload files with Ftp
  397. *
  398. * @param string|array The name of the config group to use, or a configuration array.
  399. * @param bool Automatically connect to this server.
  400. */
  401. public static function with_ftp($config = 'default', $connect = true)
  402. {
  403. static::$with_ftp = \Ftp::forge($config, $connect);
  404. }
  405. // ---------------------------------------------------------------------------
  406. /**
  407. * save uploaded file(s)
  408. *
  409. * @param mixed if int, $files element to move. if array, list of elements to move, if none, move all elements
  410. * @param string path to move to
  411. * @return void
  412. */
  413. public static function save()
  414. {
  415. // path to save the files to
  416. $path = static::$config['path'];
  417. // files to save
  418. $files = array();
  419. // check for parameters
  420. if (func_num_args())
  421. {
  422. foreach(func_get_args() as $param)
  423. {
  424. // string => new path to save to
  425. if (is_string($param))
  426. {
  427. $path = $param;
  428. }
  429. // array => list of $files indexes to save
  430. elseif(is_array($param))
  431. {
  432. $files = array();
  433. foreach($param as $key)
  434. {
  435. if (isset(static::$files[(int) $key]))
  436. {
  437. $files[(int) $key] = static::$files[(int) $key];
  438. }
  439. }
  440. }
  441. // integer => files index to save
  442. elseif(is_numeric($param))
  443. {
  444. if (isset(static::$files[$param]))
  445. {
  446. $files[$param] = static::$files[$param];
  447. }
  448. }
  449. }
  450. }
  451. else
  452. {
  453. // save all files
  454. $files = static::$files;
  455. }
  456. // anything to save?
  457. if (empty($files))
  458. {
  459. throw new \FuelException('No uploaded files are selected.');
  460. }
  461. // supplied new name and not auto renaming?
  462. if (array_key_exists('new_name', static::$config) and ! static::$config['auto_rename'] and count($files) > 1)
  463. {
  464. throw new \FuelException('Can\'t rename multiple files without auto renaming.');
  465. }
  466. // make sure we have a valid path
  467. $path = rtrim($path, DS).DS;
  468. if ( ! is_dir($path) and (bool) static::$config['create_path'])
  469. {
  470. $oldumask = umask(0);
  471. @mkdir($path, static::$config['path_chmod'], true);
  472. umask($oldumask);
  473. }
  474. if ( ! is_dir($path))
  475. {
  476. throw new \FuelException('Can\'t move the uploaded file. Destination path specified does not exist.');
  477. }
  478. // save the old umask
  479. $oldumask = umask(0);
  480. // now that we have a path, let's save the files
  481. foreach($files as $key => $file)
  482. {
  483. // skip all files in error
  484. if ($file['error'] === true)
  485. {
  486. continue;
  487. }
  488. // do we need to generate a random filename?
  489. if ( (bool) static::$config['randomize'])
  490. {
  491. $filename = md5(serialize($file));
  492. }
  493. else
  494. {
  495. $filename = $file['filename'];
  496. if ( (bool) static::$config['normalize'])
  497. {
  498. $filename = \Inflector::friendly_title($filename, static::$config['normalize_separator']);
  499. }
  500. }
  501. array_key_exists('new_name', static::$config) and $filename = (string) static::$config['new_name'];
  502. // array with the final filename
  503. $save_as = array(
  504. static::$config['prefix'],
  505. $filename,
  506. static::$config['suffix'],
  507. '',
  508. '.',
  509. empty(static::$config['extension']) ? $file['extension'] : static::$config['extension']
  510. );
  511. // remove the dot if no extension is present
  512. if (empty($save_as[5]))
  513. {
  514. $save_as[4] = '';
  515. }
  516. // need to modify case?
  517. switch(static::$config['change_case'])
  518. {
  519. case 'upper':
  520. $save_as = array_map(function($var) { return strtoupper($var); }, $save_as);
  521. break;
  522. case 'lower':
  523. $save_as = array_map(function($var) { return strtolower($var); }, $save_as);
  524. break;
  525. default:
  526. break;
  527. }
  528. // check if the file already exists
  529. if (file_exists($path.implode('', $save_as)))
  530. {
  531. if ( (bool) static::$config['auto_rename'])
  532. {
  533. $counter = 0;
  534. do
  535. {
  536. $save_as[3] = '_'.++$counter;
  537. }
  538. while (file_exists($path.implode('', $save_as)));
  539. }
  540. else
  541. {
  542. if ( ! (bool) static::$config['overwrite'])
  543. {
  544. static::$files[$key]['error'] = true;
  545. static::$files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_DUPLICATE_FILE);
  546. continue;
  547. }
  548. }
  549. }
  550. // no need to store it as an array anymore
  551. $save_as = implode('', $save_as);
  552. // does the filename exceed the maximum length?
  553. if ( ! empty(static::$config['max_length']) and strlen($save_as) > static::$config['max_length'])
  554. {
  555. static::$files[$key]['error'] = true;
  556. static::$files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_MAX_FILENAME_LENGTH);
  557. continue;
  558. }
  559. // if no error was detected, move the file
  560. if ( ! static::$files[$key]['error'])
  561. {
  562. // save the additional information
  563. static::$files[$key]['saved_to'] = $path;
  564. static::$files[$key]['saved_as'] = $save_as;
  565. // before callback defined?
  566. if (array_key_exists('before', static::$callbacks) and ! is_null(static::$callbacks['before']))
  567. {
  568. // get the callback method
  569. $callback = static::$callbacks['before'][0];
  570. // call the callback
  571. if (is_callable($callback))
  572. {
  573. $result = call_user_func_array($callback, array(&static::$files[$key]));
  574. if (is_numeric($result))
  575. {
  576. static::$files[$key]['error'] = true;
  577. static::$files[$key]['errors'][] = array('error' => $result);
  578. }
  579. }
  580. // recheck the saved_to path, it might have been altered
  581. if ( ! is_dir(static::$files[$key]['saved_to']) and (bool) static::$config['create_path'])
  582. {
  583. @mkdir(static::$files[$key]['saved_to'], static::$config['path_chmod'], true);
  584. }
  585. if ( ! is_dir(static::$files[$key]['saved_to']))
  586. {
  587. static::$files[$key]['error'] = true;
  588. static::$files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_MKDIR_FAILED);
  589. continue;
  590. }
  591. }
  592. // move the uploaded file
  593. if ( ! static::$files[$key]['error'])
  594. {
  595. // check if file should be uploaded with ftp
  596. if (static::$with_ftp)
  597. {
  598. $uploaded = static::$with_ftp->upload($file['file'], static::$config['path'].static::$files[$key]['saved_as'], static::$config['ftp_mode'], static::$config['ftp_permissions']);
  599. if ( ! $uploaded)
  600. {
  601. static::$files[$key]['error'] = true;
  602. static::$files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_FTP_FAILED);
  603. }
  604. }
  605. else
  606. {
  607. if( ! @move_uploaded_file($file['file'], static::$files[$key]['saved_to'].static::$files[$key]['saved_as']) )
  608. {
  609. static::$files[$key]['error'] = true;
  610. static::$files[$key]['errors'][] = array('error' => static::UPLOAD_ERR_MOVE_FAILED);
  611. }
  612. else
  613. {
  614. @chmod(static::$files[$key]['saved_to'].static::$files[$key]['saved_as'], static::$config['file_chmod']);
  615. }
  616. // after callback defined?
  617. if (array_key_exists('after', static::$callbacks) and ! is_null(static::$callbacks['after']))
  618. {
  619. // get the callback method
  620. $callback = static::$callbacks['after'][0];
  621. // call the callback
  622. if (is_callable($callback))
  623. {
  624. $result = call_user_func_array($callback, array(&static::$files[$key]));
  625. if (is_numeric($result))
  626. {
  627. static::$files[$key]['error'] = true;
  628. static::$files[$key]['errors'][] = array('error' => $result);
  629. }
  630. }
  631. }
  632. }
  633. }
  634. }
  635. }
  636. // and add the message texts
  637. foreach (static::$files[$key]['errors'] as $e => $error)
  638. {
  639. empty(static::$files[$key]['errors'][$e]['message']) and static::$files[$key]['errors'][$e]['message'] = \Lang::get('upload.error_'.$error['error']);
  640. }
  641. // reset the umask
  642. umask($oldumask);
  643. }
  644. }