PageRenderTime 62ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/fuel/core/classes/file.php

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