PageRenderTime 51ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/classes/file.php

https://github.com/KenjiOhtsuka/core
PHP | 855 lines | 516 code | 114 blank | 225 comment | 56 complexity | abc24947327e1ff2909f33db37efb5c4 MD5 | raw file
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.7
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2014 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. class FileAccessException extends \FuelException {}
  14. class OutsideAreaException extends \OutOfBoundsException {}
  15. class InvalidPathException extends \FileAccessException {}
  16. // ------------------------------------------------------------------------
  17. /**
  18. * File Class
  19. *
  20. * @package Fuel
  21. * @subpackage Core
  22. * @category Core
  23. */
  24. class File
  25. {
  26. /**
  27. * @var array loaded area's
  28. */
  29. protected static $areas = array();
  30. public static function _init()
  31. {
  32. \Config::load('file', true);
  33. // make sure the configured chmod values are octal
  34. $chmod = \Config::get('file.chmod.folders', 0777);
  35. is_string($chmod) and \Config::set('file.chmod.folders', octdec($chmod));
  36. $chmod = \Config::get('file.chmod.files', 0666);
  37. is_string($chmod) and \Config::set('file.chmod.files', octdec($chmod));
  38. static::$areas[null] = \File_Area::forge(\Config::get('file.base_config', array()));
  39. foreach (\Config::get('file.areas', array()) as $name => $config)
  40. {
  41. static::$areas[$name] = \File_Area::forge($config);
  42. }
  43. }
  44. public static function forge(array $config = array())
  45. {
  46. return \File_Area::forge($config);
  47. }
  48. /**
  49. * Instance
  50. *
  51. * @param string|File_Area|null file area name, object or null for base area
  52. * @return File_Area
  53. */
  54. public static function instance($area = null)
  55. {
  56. if ($area instanceof File_Area)
  57. {
  58. return $area;
  59. }
  60. $instance = array_key_exists($area, static::$areas) ? static::$areas[$area] : false;
  61. if ($instance === false)
  62. {
  63. throw new \InvalidArgumentException('There is no file instance named "'.$area.'".');
  64. }
  65. return $instance;
  66. }
  67. /**
  68. * File & directory objects factory
  69. *
  70. * @param string path to the file or directory
  71. * @param array configuration items
  72. * @param string|File_Area|null file area name, object or null for base area
  73. * @return File_Handler_File
  74. */
  75. public static function get($path, array $config = array(), $area = null)
  76. {
  77. return static::instance($area)->get_handler($path, $config);
  78. }
  79. /**
  80. * Get the url.
  81. *
  82. * @return bool
  83. */
  84. public static function get_url($path, array $config = array(), $area = null)
  85. {
  86. return static::get($path, $config, $area)->get_url();
  87. }
  88. /**
  89. * Check for file existence
  90. *
  91. * @param string path to file to check
  92. * @param string|File_Area|null file area name, object or null for base area
  93. * @return bool
  94. */
  95. public static function exists($path, $area = null)
  96. {
  97. $path = rtrim(static::instance($area)->get_path($path), '\\/');
  98. // resolve symlinks
  99. while ($path and is_link($path))
  100. {
  101. $path = readlink($path);
  102. }
  103. return is_file($path);
  104. }
  105. /**
  106. * Create an empty file
  107. *
  108. * @param string directory where to create file
  109. * @param string filename
  110. * @param string|File_Area|null file area name, object or null for base area
  111. * @return bool
  112. */
  113. public static function create($basepath, $name, $contents = null, $area = null)
  114. {
  115. $basepath = rtrim(static::instance($area)->get_path($basepath), '\\/').DS;
  116. $new_file = static::instance($area)->get_path($basepath.$name);
  117. if ( ! is_dir($basepath) or ! is_writable($basepath))
  118. {
  119. throw new \InvalidPathException('Invalid basepath: "'.$basepath.'", cannot create file at this location.');
  120. }
  121. elseif (is_file($new_file))
  122. {
  123. throw new \FileAccessException('File: "'.$new_file.'" already exists, cannot be created.');
  124. }
  125. $file = static::open_file(@fopen($new_file, 'c'), true, $area);
  126. fwrite($file, $contents);
  127. static::close_file($file, $area);
  128. return true;
  129. }
  130. /**
  131. * Create an empty directory
  132. *
  133. * @param string directory where to create new dir
  134. * @param string dirname
  135. * @param int (octal) file permissions
  136. * @param string|File_Area|null file area name, object or null for non-specific
  137. * @return bool
  138. */
  139. public static function create_dir($basepath, $name, $chmod = null, $area = null)
  140. {
  141. $basepath = rtrim(static::instance($area)->get_path($basepath), '\\/').DS;
  142. $new_dir = static::instance($area)->get_path($basepath.trim($name,'\\/'));
  143. is_null($chmod) and $chmod = \Config::get('file.chmod.folders', 0777);
  144. if ( ! is_dir($basepath) or ! is_writable($basepath))
  145. {
  146. throw new \InvalidPathException('Invalid basepath: "'.$basepath.'", cannot create directory at this location.');
  147. }
  148. elseif (is_dir($new_dir))
  149. {
  150. throw new \FileAccessException('Directory: "'.$new_dir.'" exists already, cannot be created.');
  151. }
  152. // unify the path separators, and get the part we need to add to the basepath
  153. $new_dir = substr(str_replace(array('\\', '/'), DS, $new_dir), strlen($basepath));
  154. // recursively create the directory. we can't use mkdir permissions or recursive
  155. // due to the fact that mkdir is restricted by the current users umask
  156. $basepath = rtrim($basepath, DS);
  157. foreach (explode(DS, $new_dir) as $dir)
  158. {
  159. $basepath .= DS.$dir;
  160. if ( ! is_dir($basepath))
  161. {
  162. try
  163. {
  164. if ( ! mkdir($basepath))
  165. {
  166. return false;
  167. }
  168. chmod($basepath, $chmod);
  169. }
  170. catch (\PHPErrorException $e)
  171. {
  172. return false;
  173. }
  174. }
  175. }
  176. return true;
  177. }
  178. /**
  179. * Read file
  180. *
  181. * @param string file to read
  182. * @param bool whether to use readfile() or file_get_contents()
  183. * @param string|File_Area|null file area name, object or null for base area
  184. * @return IO|string file contents
  185. */
  186. public static function read($path, $as_string = false, $area = null)
  187. {
  188. $path = static::instance($area)->get_path($path);
  189. if ( ! is_file($path))
  190. {
  191. throw new \InvalidPathException('Cannot read file: "'.$path.'", file does not exists.');
  192. }
  193. $file = static::open_file(@fopen($path, 'r'), LOCK_SH, $area);
  194. $return = $as_string ? file_get_contents($path) : readfile($path);
  195. static::close_file($file, $area);
  196. return $return;
  197. }
  198. /**
  199. * Read directory
  200. *
  201. * @param string directory to read
  202. * @param int depth to recurse directory, 1 is only current and 0 or smaller is unlimited
  203. * @param Array|null array of partial regexes or non-array for default
  204. * @param string|File_Area|null file area name, object or null for base area
  205. * @return array directory contents in an array
  206. */
  207. public static function read_dir($path, $depth = 0, $filter = null, $area = null)
  208. {
  209. $path = rtrim(static::instance($area)->get_path($path), '\\/').DS;
  210. if ( ! is_dir($path))
  211. {
  212. throw new \InvalidPathException('Invalid path: "'.$path.'", directory cannot be read.');
  213. }
  214. if ( ! $fp = @opendir($path))
  215. {
  216. throw new \FileAccessException('Could not open directory: "'.$path.'" for reading.');
  217. }
  218. // Use default when not set
  219. if ( ! is_array($filter))
  220. {
  221. $filter = array('!^\.');
  222. if ($extensions = static::instance($area)->extensions())
  223. {
  224. foreach($extensions as $ext)
  225. {
  226. $filter[] = '\.'.$ext.'$';
  227. }
  228. }
  229. }
  230. $files = array();
  231. $dirs = array();
  232. $new_depth = $depth - 1;
  233. while (false !== ($file = readdir($fp)))
  234. {
  235. // Remove '.', '..'
  236. if (in_array($file, array('.', '..')))
  237. {
  238. continue;
  239. }
  240. // use filters when given
  241. elseif ( ! empty($filter))
  242. {
  243. $continue = false; // whether or not to continue
  244. $matched = false; // whether any positive pattern matched
  245. $positive = false; // whether positive filters are present
  246. foreach($filter as $f => $type)
  247. {
  248. if (is_numeric($f))
  249. {
  250. // generic rule
  251. $f = $type;
  252. }
  253. else
  254. {
  255. // type specific rule
  256. $is_file = is_file($path.$file);
  257. if (($type === 'file' and ! $is_file) or ($type !== 'file' and $is_file))
  258. {
  259. continue;
  260. }
  261. }
  262. $not = substr($f, 0, 1) == '!'; // whether it's a negative condition
  263. $f = $not ? substr($f, 1) : $f;
  264. // on negative condition a match leads to a continue
  265. if (($match = preg_match('/'.$f.'/uiD', $file) > 0) and $not)
  266. {
  267. $continue = true;
  268. }
  269. $positive = $positive ?: ! $not; // whether a positive condition was encountered
  270. $matched = $matched ?: ($match and ! $not); // whether one of the filters has matched
  271. }
  272. // continue when negative matched or when positive filters and nothing matched
  273. if ($continue or $positive and ! $matched)
  274. {
  275. continue;
  276. }
  277. }
  278. if (@is_dir($path.$file))
  279. {
  280. // Use recursion when depth not depleted or not limited...
  281. if ($depth < 1 or $new_depth > 0)
  282. {
  283. $dirs[$file.DS] = static::read_dir($path.$file.DS, $new_depth, $filter, $area);
  284. }
  285. // ... or set dir to false when not read
  286. else
  287. {
  288. $dirs[$file.DS] = false;
  289. }
  290. }
  291. else
  292. {
  293. $files[] = $file;
  294. }
  295. }
  296. closedir($fp);
  297. // sort dirs & files naturally and return array with dirs on top and files
  298. uksort($dirs, 'strnatcasecmp');
  299. natcasesort($files);
  300. return array_merge($dirs, $files);
  301. }
  302. /**
  303. * Update a file
  304. *
  305. * @param string directory where to write the file
  306. * @param string filename
  307. * @param string|File_Area|null file area name, object or null for base area
  308. * @return bool
  309. */
  310. public static function update($basepath, $name, $contents = null, $area = null)
  311. {
  312. $basepath = rtrim(static::instance($area)->get_path($basepath), '\\/').DS;
  313. $new_file = static::instance($area)->get_path($basepath.$name);
  314. if ( ! $file = static::open_file(@fopen($new_file, 'w'), true, $area))
  315. {
  316. if ( ! is_dir($basepath) or ! is_writable($basepath))
  317. {
  318. throw new \InvalidPathException('Invalid basepath: "'.$basepath.'", cannot update a file at this location.');
  319. }
  320. throw new \FileAccessException('No write access to: "'.$basepath.'", cannot update a file.');
  321. }
  322. fwrite($file, $contents);
  323. static::close_file($file, $area);
  324. return true;
  325. }
  326. /**
  327. * Append to a file
  328. *
  329. * @param string directory where to write the file
  330. * @param string filename
  331. * @param string|File_Area|null file area name, object or null for base area
  332. * @return bool
  333. */
  334. public static function append($basepath, $name, $contents = null, $area = null)
  335. {
  336. $basepath = rtrim(static::instance($area)->get_path($basepath), '\\/').DS;
  337. $new_file = static::instance($area)->get_path($basepath.$name);
  338. if ( ! is_file($new_file))
  339. {
  340. throw new \FileAccessException('File: "'.$new_file.'" does not exist, cannot be appended.');
  341. }
  342. if ( ! $file = static::open_file(@fopen($new_file, 'a'), true, $area))
  343. {
  344. if ( ! is_dir($basepath) or ! is_writable($basepath))
  345. {
  346. throw new \InvalidPathException('Invalid basepath: "'.$basepath.'", cannot append to a file at this location.');
  347. }
  348. throw new \FileAccessException('No write access, cannot append to the file: "'.$file.'".');
  349. }
  350. fwrite($file, $contents);
  351. static::close_file($file, $area);
  352. return true;
  353. }
  354. /**
  355. * Get the octal permissions for a file or directory
  356. *
  357. * @param string path to the file or directory
  358. * @param mixed file area name, object or null for base area
  359. * $return string octal file permissions
  360. */
  361. public static function get_permissions($path, $area = null)
  362. {
  363. $path = static::instance($area)->get_path($path);
  364. if ( ! file_exists($path))
  365. {
  366. throw new \InvalidPathException('Path: "'.$path.'" is not a directory or a file, cannot get permissions.');
  367. }
  368. return substr(sprintf('%o', fileperms($path)), -4);
  369. }
  370. /**
  371. * Get a file's or directory's created or modified timestamp.
  372. *
  373. * @param string path to the file or directory
  374. * @param string modified or created
  375. * @param mixed file area name, object or null for base area
  376. * @return int Unix Timestamp
  377. */
  378. public static function get_time($path, $type = 'modified', $area = null)
  379. {
  380. $path = static::instance($area)->get_path($path);
  381. if ( ! file_exists($path))
  382. {
  383. throw new \InvalidPathException('Path: "'.$path.'" is not a directory or a file, cannot get creation timestamp.');
  384. }
  385. if ($type === 'modified')
  386. {
  387. return filemtime($path);
  388. }
  389. elseif ($type === 'created')
  390. {
  391. return filectime($path);
  392. }
  393. else
  394. {
  395. throw new \UnexpectedValueException('File::time $type must be "modified" or "created".');
  396. }
  397. }
  398. /**
  399. * Get a file's size.
  400. *
  401. * @param string path to the file or directory
  402. * @param mixed file area name, object or null for base area
  403. * @return int the file's size in bytes
  404. */
  405. public static function get_size($path, $area = null)
  406. {
  407. $path = static::instance($area)->get_path($path);
  408. if ( ! file_exists($path))
  409. {
  410. throw new \InvalidPathException('Path: "'.$path.'" is not a directory or a file, cannot get size.');
  411. }
  412. return filesize($path);
  413. }
  414. /**
  415. * Rename directory or file
  416. *
  417. * @param string path to file or directory to rename
  418. * @param string new path (full path, can also cause move)
  419. * @param string|File_Area|null source path file area name, object or null for non-specific
  420. * @param string|File_Area|null target path file area name, object or null for non-specific. Defaults to source_area if not set.
  421. * @return bool
  422. */
  423. public static function rename($path, $new_path, $source_area = null, $target_area = null)
  424. {
  425. $path = static::instance($source_area)->get_path($path);
  426. $new_path = static::instance($target_area ?: $source_area)->get_path($new_path);
  427. return rename($path, $new_path);
  428. }
  429. /**
  430. * Alias for rename(), not needed but consistent with other methods
  431. */
  432. public static function rename_dir($path, $new_path, $source_area = null, $target_area = null)
  433. {
  434. return static::rename($path, $new_path, $source_area, $target_area);
  435. }
  436. /**
  437. * Copy file
  438. *
  439. * @param string path to file to copy
  440. * @param string new base directory (full path)
  441. * @param string|File_Area|null source path file area name, object or null for non-specific
  442. * @param string|File_Area|null target path file area name, object or null for non-specific. Defaults to source_area if not set.
  443. * @return bool
  444. */
  445. public static function copy($path, $new_path, $source_area = null, $target_area = null)
  446. {
  447. $path = static::instance($source_area)->get_path($path);
  448. $new_path = static::instance($target_area ?: $source_area)->get_path($new_path);
  449. if ( ! is_file($path))
  450. {
  451. throw new \InvalidPathException('Cannot copy file: given path: "'.$path.'" is not a file.');
  452. }
  453. elseif (file_exists($new_path))
  454. {
  455. throw new \FileAccessException('Cannot copy file: new path: "'.$new_path.'" already exists.');
  456. }
  457. return copy($path, $new_path);
  458. }
  459. /**
  460. * Copy directory
  461. *
  462. * @param string path to directory which contents will be copied
  463. * @param string new base directory (full path)
  464. * @param string|File_Area|null source path file area name, object or null for non-specific
  465. * @param string|File_Area|null target path file area name, object or null for non-specific. Defaults to source_area if not set.
  466. * @return bool
  467. * @throws FileAccessException when something went wrong
  468. */
  469. public static function copy_dir($path, $new_path, $source_area = null, $target_area = null)
  470. {
  471. $target_area = $target_area ?: $source_area;
  472. $path = rtrim(static::instance($source_area)->get_path($path), '\\/').DS;
  473. $new_path = rtrim(static::instance($target_area)->get_path($new_path), '\\/').DS;
  474. if ( ! is_dir($path))
  475. {
  476. throw new \InvalidPathException('Cannot copy directory: given path: "'.$path.'" is not a directory: '.$path);
  477. }
  478. elseif ( ! file_exists($new_path))
  479. {
  480. $newpath_dirname = pathinfo($new_path, PATHINFO_DIRNAME);
  481. static::create_dir($newpath_dirname, pathinfo($new_path, PATHINFO_BASENAME), fileperms($newpath_dirname) ?: 0777, $target_area);
  482. }
  483. $files = static::read_dir($path, -1, array(), $source_area);
  484. foreach ($files as $dir => $file)
  485. {
  486. if (is_array($file))
  487. {
  488. $check = static::create_dir($new_path.DS, substr($dir, 0, -1), fileperms($path.$dir) ?: 0777, $target_area);
  489. $check and static::copy_dir($path.$dir.DS, $new_path.$dir, $source_area, $target_area);
  490. }
  491. else
  492. {
  493. $check = static::copy($path.$file, $new_path.$file, $source_area, $target_area);
  494. }
  495. // abort if something went wrong
  496. if ( ! $check)
  497. {
  498. throw new \FileAccessException('Directory copy aborted prematurely, part of the operation failed during copying: '.(is_array($file) ? $dir : $file));
  499. }
  500. }
  501. }
  502. /**
  503. * Create a new symlink
  504. *
  505. * @param string target of symlink
  506. * @param string destination of symlink
  507. * @param bool true for file, false for directory
  508. * @param string|File_Area|null file area name, object or null for base area
  509. * @return bool
  510. */
  511. public static function symlink($path, $link_path, $is_file = true, $area = null)
  512. {
  513. $path = rtrim(static::instance($area)->get_path($path), '\\/');
  514. $link_path = rtrim(static::instance($area)->get_path($link_path), '\\/');
  515. if ($is_file and ! is_file($path))
  516. {
  517. throw new \InvalidPathException('Cannot symlink: given file: "'.$path.'" does not exist.');
  518. }
  519. elseif ( ! $is_file and ! is_dir($path))
  520. {
  521. throw new \InvalidPathException('Cannot symlink: given directory: "'.$path.'" does not exist.');
  522. }
  523. elseif (file_exists($link_path))
  524. {
  525. throw new \FileAccessException('Cannot symlink: link: "'.$link_path.'" already exists.');
  526. }
  527. return symlink($path, $link_path);
  528. }
  529. /**
  530. * Delete file
  531. *
  532. * @param string path to file to delete
  533. * @param string|File_Area|null file area name, object or null for base area
  534. * @return bool
  535. */
  536. public static function delete($path, $area = null)
  537. {
  538. $path = rtrim(static::instance($area)->get_path($path), '\\/');
  539. if ( ! is_file($path) and ! is_link($path))
  540. {
  541. throw new \InvalidPathException('Cannot delete file: given path "'.$path.'" is not a file.');
  542. }
  543. return unlink($path);
  544. }
  545. /**
  546. * Delete directory
  547. *
  548. * @param string path to directory to delete
  549. * @param bool whether to also delete contents of subdirectories
  550. * @param bool whether to delete the parent dir itself when empty
  551. * @param string|File_Area|null file area name, object or null for base area
  552. * @return bool
  553. */
  554. public static function delete_dir($path, $recursive = true, $delete_top = true, $area = null)
  555. {
  556. $path = rtrim(static::instance($area)->get_path($path), '\\/').DS;
  557. if ( ! is_dir($path))
  558. {
  559. throw new \InvalidPathException('Cannot delete directory: given path: "'.$path.'" is not a directory.');
  560. }
  561. $files = static::read_dir($path, -1, array(), $area);
  562. $not_empty = false;
  563. $check = true;
  564. foreach ($files as $dir => $file)
  565. {
  566. if (is_array($file))
  567. {
  568. if ($recursive)
  569. {
  570. $check = static::delete_dir($path.$dir, true, true, $area);
  571. }
  572. else
  573. {
  574. $not_empty = true;
  575. }
  576. }
  577. else
  578. {
  579. $check = static::delete($path.$file, $area);
  580. }
  581. // abort if something went wrong
  582. if ( ! $check)
  583. {
  584. throw new \FileAccessException('Directory deletion aborted prematurely, part of the operation failed.');
  585. }
  586. }
  587. if ( ! $not_empty and $delete_top)
  588. {
  589. return rmdir($path);
  590. }
  591. return true;
  592. }
  593. /**
  594. * Open and lock file
  595. *
  596. * @param resource|string file resource or path
  597. * @param constant either valid lock constant or true=LOCK_EX / false=LOCK_UN
  598. * @param string|File_Area|null file area name, object or null for base area
  599. */
  600. public static function open_file($path, $lock = true, $area = null)
  601. {
  602. if (is_string($path))
  603. {
  604. $path = static::instance($area)->get_path($path);
  605. $resource = fopen($path, 'r+');
  606. }
  607. else
  608. {
  609. $resource = $path;
  610. }
  611. // Make sure the parameter is a valid resource
  612. if ( ! is_resource($resource))
  613. {
  614. return false;
  615. }
  616. // If locks aren't used, don't lock
  617. if ( ! static::instance($area)->use_locks())
  618. {
  619. return $resource;
  620. }
  621. // Accept valid lock constant or set to LOCK_EX
  622. $lock = in_array($lock, array(LOCK_SH, LOCK_EX, LOCK_NB)) ? $lock : LOCK_EX;
  623. // Try to get a lock, timeout after 5 seconds
  624. $lock_mtime = microtime(true);
  625. while ( ! flock($resource, $lock))
  626. {
  627. if (microtime(true) - $lock_mtime > 5)
  628. {
  629. throw new \FileAccessException('Could not secure file lock, timed out after 5 seconds.');
  630. }
  631. }
  632. return $resource;
  633. }
  634. /**
  635. * Close file resource & unlock
  636. *
  637. * @param resource open file resource
  638. * @param string|File_Area|null file area name, object or null for base area
  639. */
  640. public static function close_file($resource, $area = null)
  641. {
  642. // If locks aren't used, don't unlock
  643. if ( static::instance($area)->use_locks())
  644. {
  645. flock($resource, LOCK_UN);
  646. }
  647. fclose($resource);
  648. }
  649. /**
  650. * Get detailed information about a file
  651. *
  652. * @param string file path
  653. * @param string|File_Area|null file area name, object or null for base area
  654. */
  655. public static function file_info($path, $area = null)
  656. {
  657. $info = array(
  658. 'original' => $path,
  659. 'realpath' => '',
  660. 'dirname' => '',
  661. 'basename' => '',
  662. 'filename' => '',
  663. 'extension' => '',
  664. 'mimetype' => '',
  665. 'charset' => '',
  666. 'size' => 0,
  667. 'permissions' => '',
  668. 'time_created' => '',
  669. 'time_modified' => '',
  670. );
  671. if ( ! $info['realpath'] = static::instance($area)->get_path($path) or ! is_file($info['realpath']))
  672. {
  673. throw new \InvalidPathException('Filename given is not a valid file.');
  674. }
  675. $info = array_merge($info, pathinfo($info['realpath']));
  676. if ( ! $fileinfo = new \finfo(FILEINFO_MIME, \Config::get('file.magic_file', null)))
  677. {
  678. throw new \InvalidArgumentException('Can not retrieve information about this file.');
  679. }
  680. $fileinfo = explode(';', $fileinfo->file($info['realpath']));
  681. $info['mimetype'] = isset($fileinfo[0]) ? $fileinfo[0] : 'application/octet-stream';
  682. if (isset($fileinfo[1]))
  683. {
  684. $fileinfo = explode('=', $fileinfo[1]);
  685. $info['charset'] = isset($fileinfo[1]) ? $fileinfo[1] : '';
  686. }
  687. $info['size'] = static::get_size($info['realpath'], $area);
  688. $info['permissions'] = static::get_permissions($info['realpath'], $area);
  689. $info['time_created'] = static::get_time($info['realpath'], $type = 'created', $area);
  690. $info['time_modified'] = static::get_time($info['realpath'], $type = 'modified', $area);
  691. return $info;
  692. }
  693. /**
  694. * Download a file
  695. *
  696. * @param string file path
  697. * @param string|null custom name for the file to be downloaded
  698. * @param string|null custom mime type or null for file mime type
  699. * @param string|File_Area|null file area name, object or null for base area
  700. * @param bool delete the file after download when true
  701. * @param string disposition, must be 'attachment' or 'inline'
  702. */
  703. public static function download($path, $name = null, $mime = null, $area = null, $delete = false, $disposition = 'attachment')
  704. {
  705. $info = static::file_info($path, $area);
  706. $class = get_called_class();
  707. empty($mime) or $info['mimetype'] = $mime;
  708. empty($name) or $info['basename'] = $name;
  709. in_array($disposition, array('inline', 'attachment')) or $disposition = 'attachment';
  710. \Event::register('fuel-shutdown', function () use($info, $area, $class, $delete, $disposition) {
  711. if ( ! $file = call_user_func(array($class, 'open_file'), @fopen($info['realpath'], 'rb'), LOCK_SH, $area))
  712. {
  713. throw new \FileAccessException('Filename given could not be opened for download.');
  714. }
  715. while (ob_get_level() > 0)
  716. {
  717. ob_end_clean();
  718. }
  719. ini_get('zlib.output_compression') and ini_set('zlib.output_compression', 0);
  720. ! ini_get('safe_mode') and set_time_limit(0);
  721. header('Content-Type: '.$info['mimetype']);
  722. header('Content-Disposition: '.$disposition.'; filename="'.$info['basename'].'"');
  723. $disposition == 'attachment' and header('Content-Description: File Transfer');
  724. header('Content-Length: '.$info['size']);
  725. header('Content-Transfer-Encoding: binary');
  726. $disposition == 'attachment' and header('Expires: 0');
  727. $disposition == 'attachment' and header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  728. while( ! feof($file))
  729. {
  730. echo fread($file, 2048);
  731. }
  732. call_user_func(array($class, 'close_file'), $file, $area);
  733. if ($delete)
  734. {
  735. call_user_func(array($class, 'delete'), $info['realpath'], $area);
  736. }
  737. });
  738. exit;
  739. }
  740. }