PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/cake/libs/folder.php

https://bitbucket.org/aewalker/space-jam
PHP | 786 lines | 598 code | 29 blank | 159 comment | 26 complexity | ea930dca521ae5ba69df8a6d6739d8a3 MD5 | raw file
  1. <?php
  2. /**
  3. * Convenience class for handling directories.
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake
  16. * @subpackage cake.cake.libs
  17. * @since CakePHP(tm) v 0.2.9
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. /**
  21. * Included libraries.
  22. *
  23. */
  24. if (!class_exists('Object')) {
  25. require LIBS . 'object.php';
  26. }
  27. /**
  28. * Folder structure browser, lists folders and files.
  29. * Provides an Object interface for Common directory related tasks.
  30. *
  31. * @package cake
  32. * @subpackage cake.cake.libs
  33. */
  34. class Folder extends Object {
  35. /**
  36. * Path to Folder.
  37. *
  38. * @var string
  39. * @access public
  40. */
  41. var $path = null;
  42. /**
  43. * Sortedness. Whether or not list results
  44. * should be sorted by name.
  45. *
  46. * @var boolean
  47. * @access public
  48. */
  49. var $sort = false;
  50. /**
  51. * Mode to be used on create. Does nothing on windows platforms.
  52. *
  53. * @var integer
  54. * @access public
  55. */
  56. var $mode = 0755;
  57. /**
  58. * Holds messages from last method.
  59. *
  60. * @var array
  61. * @access private
  62. */
  63. var $__messages = array();
  64. /**
  65. * Holds errors from last method.
  66. *
  67. * @var array
  68. * @access private
  69. */
  70. var $__errors = false;
  71. /**
  72. * Holds array of complete directory paths.
  73. *
  74. * @var array
  75. * @access private
  76. */
  77. var $__directories;
  78. /**
  79. * Holds array of complete file paths.
  80. *
  81. * @var array
  82. * @access private
  83. */
  84. var $__files;
  85. /**
  86. * Constructor.
  87. *
  88. * @param string $path Path to folder
  89. * @param boolean $create Create folder if not found
  90. * @param mixed $mode Mode (CHMOD) to apply to created folder, false to ignore
  91. */
  92. function __construct($path = false, $create = false, $mode = false) {
  93. parent::__construct();
  94. if (empty($path)) {
  95. $path = TMP;
  96. }
  97. if ($mode) {
  98. $this->mode = $mode;
  99. }
  100. if (!file_exists($path) && $create === true) {
  101. $this->create($path, $this->mode);
  102. }
  103. if (!Folder::isAbsolute($path)) {
  104. $path = realpath($path);
  105. }
  106. if (!empty($path)) {
  107. $this->cd($path);
  108. }
  109. }
  110. /**
  111. * Return current path.
  112. *
  113. * @return string Current path
  114. * @access public
  115. */
  116. function pwd() {
  117. return $this->path;
  118. }
  119. /**
  120. * Change directory to $path.
  121. *
  122. * @param string $path Path to the directory to change to
  123. * @return string The new path. Returns false on failure
  124. * @access public
  125. */
  126. function cd($path) {
  127. $path = $this->realpath($path);
  128. if (is_dir($path)) {
  129. return $this->path = $path;
  130. }
  131. return false;
  132. }
  133. /**
  134. * Returns an array of the contents of the current directory.
  135. * The returned array holds two arrays: One of directories and one of files.
  136. *
  137. * @param boolean $sort Whether you want the results sorted, set this and the sort property
  138. * to false to get unsorted results.
  139. * @param mixed $exceptions Either an array or boolean true will not grab dot files
  140. * @param boolean $fullPath True returns the full path
  141. * @return mixed Contents of current directory as an array, an empty array on failure
  142. * @access public
  143. */
  144. function read($sort = true, $exceptions = false, $fullPath = false) {
  145. $dirs = $files = array();
  146. if (!$this->pwd()) {
  147. return array($dirs, $files);
  148. }
  149. if (is_array($exceptions)) {
  150. $exceptions = array_flip($exceptions);
  151. }
  152. $skipHidden = isset($exceptions['.']) || $exceptions === true;
  153. if (false === ($dir = @opendir($this->path))) {
  154. return array($dirs, $files);
  155. }
  156. while (false !== ($item = readdir($dir))) {
  157. if ($item === '.' || $item === '..' || ($skipHidden && $item[0] === '.') || isset($exceptions[$item])) {
  158. continue;
  159. }
  160. $path = Folder::addPathElement($this->path, $item);
  161. if (is_dir($path)) {
  162. $dirs[] = $fullPath ? $path : $item;
  163. } else {
  164. $files[] = $fullPath ? $path : $item;
  165. }
  166. }
  167. if ($sort || $this->sort) {
  168. sort($dirs);
  169. sort($files);
  170. }
  171. closedir($dir);
  172. return array($dirs, $files);
  173. }
  174. /**
  175. * Returns an array of all matching files in current directory.
  176. *
  177. * @param string $pattern Preg_match pattern (Defaults to: .*)
  178. * @param boolean $sort Whether results should be sorted.
  179. * @return array Files that match given pattern
  180. * @access public
  181. */
  182. function find($regexpPattern = '.*', $sort = false) {
  183. list($dirs, $files) = $this->read($sort);
  184. return array_values(preg_grep('/^' . $regexpPattern . '$/i', $files)); ;
  185. }
  186. /**
  187. * Returns an array of all matching files in and below current directory.
  188. *
  189. * @param string $pattern Preg_match pattern (Defaults to: .*)
  190. * @param boolean $sort Whether results should be sorted.
  191. * @return array Files matching $pattern
  192. * @access public
  193. */
  194. function findRecursive($pattern = '.*', $sort = false) {
  195. if (!$this->pwd()) {
  196. return array();
  197. }
  198. $startsOn = $this->path;
  199. $out = $this->_findRecursive($pattern, $sort);
  200. $this->cd($startsOn);
  201. return $out;
  202. }
  203. /**
  204. * Private helper function for findRecursive.
  205. *
  206. * @param string $pattern Pattern to match against
  207. * @param boolean $sort Whether results should be sorted.
  208. * @return array Files matching pattern
  209. * @access private
  210. */
  211. function _findRecursive($pattern, $sort = false) {
  212. list($dirs, $files) = $this->read($sort);
  213. $found = array();
  214. foreach ($files as $file) {
  215. if (preg_match('/^' . $pattern . '$/i', $file)) {
  216. $found[] = Folder::addPathElement($this->path, $file);
  217. }
  218. }
  219. $start = $this->path;
  220. foreach ($dirs as $dir) {
  221. $this->cd(Folder::addPathElement($start, $dir));
  222. $found = array_merge($found, $this->findRecursive($pattern, $sort));
  223. }
  224. return $found;
  225. }
  226. /**
  227. * Returns true if given $path is a Windows path.
  228. *
  229. * @param string $path Path to check
  230. * @return boolean true if windows path, false otherwise
  231. * @access public
  232. * @static
  233. */
  234. function isWindowsPath($path) {
  235. return (bool)preg_match('/^[A-Z]:\\\\/i', $path);
  236. }
  237. /**
  238. * Returns true if given $path is an absolute path.
  239. *
  240. * @param string $path Path to check
  241. * @return bool true if path is absolute.
  242. * @access public
  243. * @static
  244. */
  245. function isAbsolute($path) {
  246. return !empty($path) && ($path[0] === '/' || preg_match('/^[A-Z]:\\\\/i', $path));
  247. }
  248. /**
  249. * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
  250. *
  251. * @param string $path Path to check
  252. * @return string Set of slashes ("\\" or "/")
  253. * @access public
  254. * @static
  255. */
  256. function normalizePath($path) {
  257. return Folder::correctSlashFor($path);
  258. }
  259. /**
  260. * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
  261. *
  262. * @param string $path Path to check
  263. * @return string Set of slashes ("\\" or "/")
  264. * @access public
  265. * @static
  266. */
  267. function correctSlashFor($path) {
  268. return (Folder::isWindowsPath($path)) ? '\\' : '/';
  269. }
  270. /**
  271. * Returns $path with added terminating slash (corrected for Windows or other OS).
  272. *
  273. * @param string $path Path to check
  274. * @return string Path with ending slash
  275. * @access public
  276. * @static
  277. */
  278. function slashTerm($path) {
  279. if (Folder::isSlashTerm($path)) {
  280. return $path;
  281. }
  282. return $path . Folder::correctSlashFor($path);
  283. }
  284. /**
  285. * Returns $path with $element added, with correct slash in-between.
  286. *
  287. * @param string $path Path
  288. * @param string $element Element to and at end of path
  289. * @return string Combined path
  290. * @access public
  291. * @static
  292. */
  293. function addPathElement($path, $element) {
  294. return rtrim($path, DS) . DS . $element;
  295. }
  296. /**
  297. * Returns true if the File is in a given CakePath.
  298. *
  299. * @param string $path The path to check.
  300. * @return bool
  301. * @access public
  302. */
  303. function inCakePath($path = '') {
  304. $dir = substr(Folder::slashTerm(ROOT), 0, -1);
  305. $newdir = $dir . $path;
  306. return $this->inPath($newdir);
  307. }
  308. /**
  309. * Returns true if the File is in given path.
  310. *
  311. * @param string $path The path to check that the current pwd() resides with in.
  312. * @param boolean $reverse
  313. * @return bool
  314. * @access public
  315. */
  316. function inPath($path = '', $reverse = false) {
  317. $dir = Folder::slashTerm($path);
  318. $current = Folder::slashTerm($this->pwd());
  319. if (!$reverse) {
  320. $return = preg_match('/^(.*)' . preg_quote($dir, '/') . '(.*)/', $current);
  321. } else {
  322. $return = preg_match('/^(.*)' . preg_quote($current, '/') . '(.*)/', $dir);
  323. }
  324. return (bool)$return;
  325. }
  326. /**
  327. * Change the mode on a directory structure recursively. This includes changing the mode on files as well.
  328. *
  329. * @param string $path The path to chmod
  330. * @param integer $mode octal value 0755
  331. * @param boolean $recursive chmod recursively, set to false to only change the current directory.
  332. * @param array $exceptions array of files, directories to skip
  333. * @return boolean Returns TRUE on success, FALSE on failure
  334. * @access public
  335. */
  336. function chmod($path, $mode = false, $recursive = true, $exceptions = array()) {
  337. if (!$mode) {
  338. $mode = $this->mode;
  339. }
  340. if ($recursive === false && is_dir($path)) {
  341. if (@chmod($path, intval($mode, 8))) {
  342. $this->__messages[] = sprintf(__('%s changed to %s', true), $path, $mode);
  343. return true;
  344. }
  345. $this->__errors[] = sprintf(__('%s NOT changed to %s', true), $path, $mode);
  346. return false;
  347. }
  348. if (is_dir($path)) {
  349. $paths = $this->tree($path);
  350. foreach ($paths as $type) {
  351. foreach ($type as $key => $fullpath) {
  352. $check = explode(DS, $fullpath);
  353. $count = count($check);
  354. if (in_array($check[$count - 1], $exceptions)) {
  355. continue;
  356. }
  357. if (@chmod($fullpath, intval($mode, 8))) {
  358. $this->__messages[] = sprintf(__('%s changed to %s', true), $fullpath, $mode);
  359. } else {
  360. $this->__errors[] = sprintf(__('%s NOT changed to %s', true), $fullpath, $mode);
  361. }
  362. }
  363. }
  364. if (empty($this->__errors)) {
  365. return true;
  366. }
  367. }
  368. return false;
  369. }
  370. /**
  371. * Returns an array of nested directories and files in each directory
  372. *
  373. * @param string $path the directory path to build the tree from
  374. * @param mixed $exceptions Array of files to exclude, defaults to excluding hidden files.
  375. * @param string $type either file or dir. null returns both files and directories
  376. * @return mixed array of nested directories and files in each directory
  377. * @access public
  378. */
  379. function tree($path, $exceptions = true, $type = null) {
  380. $original = $this->path;
  381. $path = rtrim($path, DS);
  382. if (!$this->cd($path)) {
  383. if ($type === null) {
  384. return array(array(), array());
  385. }
  386. return array();
  387. }
  388. $this->__files = array();
  389. $this->__directories = array($this->realpath($path));
  390. $directories = array();
  391. if ($exceptions === false) {
  392. $exceptions = true;
  393. }
  394. while (!empty($this->__directories)) {
  395. $dir = array_pop($this->__directories);
  396. $this->__tree($dir, $exceptions);
  397. $directories[] = $dir;
  398. }
  399. if ($type === null) {
  400. return array($directories, $this->__files);
  401. }
  402. if ($type === 'dir') {
  403. return $directories;
  404. }
  405. $this->cd($original);
  406. return $this->__files;
  407. }
  408. /**
  409. * Private method to list directories and files in each directory
  410. *
  411. * @param string $path The Path to read.
  412. * @param mixed $exceptions Array of files to exclude from the read that will be performed.
  413. * @access private
  414. */
  415. function __tree($path, $exceptions) {
  416. $this->path = $path;
  417. list($dirs, $files) = $this->read(false, $exceptions, true);
  418. $this->__directories = array_merge($this->__directories, $dirs);
  419. $this->__files = array_merge($this->__files, $files);
  420. }
  421. /**
  422. * Create a directory structure recursively. Can be used to create
  423. * deep path structures like `/foo/bar/baz/shoe/horn`
  424. *
  425. * @param string $pathname The directory structure to create
  426. * @param integer $mode octal value 0755
  427. * @return boolean Returns TRUE on success, FALSE on failure
  428. * @access public
  429. */
  430. function create($pathname, $mode = false) {
  431. if (is_dir($pathname) || empty($pathname)) {
  432. return true;
  433. }
  434. if (!$mode) {
  435. $mode = $this->mode;
  436. }
  437. if (is_file($pathname)) {
  438. $this->__errors[] = sprintf(__('%s is a file', true), $pathname);
  439. return false;
  440. }
  441. $nextPathname = substr($pathname, 0, strrpos($pathname, DS));
  442. if ($this->create($nextPathname, $mode)) {
  443. if (!file_exists($pathname)) {
  444. $old = umask(0);
  445. if (mkdir($pathname, $mode)) {
  446. umask($old);
  447. $this->__messages[] = sprintf(__('%s created', true), $pathname);
  448. return true;
  449. } else {
  450. umask($old);
  451. $this->__errors[] = sprintf(__('%s NOT created', true), $pathname);
  452. return false;
  453. }
  454. }
  455. }
  456. return false;
  457. }
  458. /**
  459. * Returns the size in bytes of this Folder and its contents.
  460. *
  461. * @param string $directory Path to directory
  462. * @return int size in bytes of current folder
  463. * @access public
  464. */
  465. function dirsize() {
  466. $size = 0;
  467. $directory = Folder::slashTerm($this->path);
  468. $stack = array($directory);
  469. $count = count($stack);
  470. for ($i = 0, $j = $count; $i < $j; ++$i) {
  471. if (is_file($stack[$i])) {
  472. $size += filesize($stack[$i]);
  473. } elseif (is_dir($stack[$i])) {
  474. $dir = dir($stack[$i]);
  475. if ($dir) {
  476. while (false !== ($entry = $dir->read())) {
  477. if ($entry === '.' || $entry === '..') {
  478. continue;
  479. }
  480. $add = $stack[$i] . $entry;
  481. if (is_dir($stack[$i] . $entry)) {
  482. $add = Folder::slashTerm($add);
  483. }
  484. $stack[] = $add;
  485. }
  486. $dir->close();
  487. }
  488. }
  489. $j = count($stack);
  490. }
  491. return $size;
  492. }
  493. /**
  494. * Recursively Remove directories if the system allows.
  495. *
  496. * @param string $path Path of directory to delete
  497. * @return boolean Success
  498. * @access public
  499. */
  500. function delete($path = null) {
  501. if (!$path) {
  502. $path = $this->pwd();
  503. }
  504. if (!$path) {
  505. return null;
  506. }
  507. $path = Folder::slashTerm($path);
  508. if (is_dir($path) === true) {
  509. $normalFiles = glob($path . '*');
  510. $hiddenFiles = glob($path . '\.?*');
  511. $normalFiles = $normalFiles ? $normalFiles : array();
  512. $hiddenFiles = $hiddenFiles ? $hiddenFiles : array();
  513. $files = array_merge($normalFiles, $hiddenFiles);
  514. if (is_array($files)) {
  515. foreach ($files as $file) {
  516. if (preg_match('/(\.|\.\.)$/', $file)) {
  517. continue;
  518. }
  519. if (is_file($file) === true) {
  520. if (@unlink($file)) {
  521. $this->__messages[] = sprintf(__('%s removed', true), $file);
  522. } else {
  523. $this->__errors[] = sprintf(__('%s NOT removed', true), $file);
  524. }
  525. } elseif (is_dir($file) === true && $this->delete($file) === false) {
  526. return false;
  527. }
  528. }
  529. }
  530. $path = substr($path, 0, strlen($path) - 1);
  531. if (rmdir($path) === false) {
  532. $this->__errors[] = sprintf(__('%s NOT removed', true), $path);
  533. return false;
  534. } else {
  535. $this->__messages[] = sprintf(__('%s removed', true), $path);
  536. }
  537. }
  538. return true;
  539. }
  540. /**
  541. * Recursive directory copy.
  542. *
  543. * ### Options
  544. *
  545. * - `to` The directory to copy to.
  546. * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().
  547. * - `chmod` The mode to copy the files/directories with.
  548. * - `skip` Files/directories to skip.
  549. *
  550. * @param mixed $options Either an array of options (see above) or a string of the destination directory.
  551. * @return bool Success
  552. * @access public
  553. */
  554. function copy($options = array()) {
  555. if (!$this->pwd()) {
  556. return false;
  557. }
  558. $to = null;
  559. if (is_string($options)) {
  560. $to = $options;
  561. $options = array();
  562. }
  563. $options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()), $options);
  564. $fromDir = $options['from'];
  565. $toDir = $options['to'];
  566. $mode = $options['mode'];
  567. if (!$this->cd($fromDir)) {
  568. $this->__errors[] = sprintf(__('%s not found', true), $fromDir);
  569. return false;
  570. }
  571. if (!is_dir($toDir)) {
  572. $this->create($toDir, $mode);
  573. }
  574. if (!is_writable($toDir)) {
  575. $this->__errors[] = sprintf(__('%s not writable', true), $toDir);
  576. return false;
  577. }
  578. $exceptions = array_merge(array('.', '..', '.svn'), $options['skip']);
  579. if ($handle = @opendir($fromDir)) {
  580. while (false !== ($item = readdir($handle))) {
  581. if (!in_array($item, $exceptions)) {
  582. $from = Folder::addPathElement($fromDir, $item);
  583. $to = Folder::addPathElement($toDir, $item);
  584. if (is_file($from)) {
  585. if (copy($from, $to)) {
  586. chmod($to, intval($mode, 8));
  587. touch($to, filemtime($from));
  588. $this->__messages[] = sprintf(__('%s copied to %s', true), $from, $to);
  589. } else {
  590. $this->__errors[] = sprintf(__('%s NOT copied to %s', true), $from, $to);
  591. }
  592. }
  593. if (is_dir($from) && !file_exists($to)) {
  594. $old = umask(0);
  595. if (mkdir($to, $mode)) {
  596. umask($old);
  597. $old = umask(0);
  598. chmod($to, $mode);
  599. umask($old);
  600. $this->__messages[] = sprintf(__('%s created', true), $to);
  601. $options = array_merge($options, array('to'=> $to, 'from'=> $from));
  602. $this->copy($options);
  603. } else {
  604. $this->__errors[] = sprintf(__('%s not created', true), $to);
  605. }
  606. }
  607. }
  608. }
  609. closedir($handle);
  610. } else {
  611. return false;
  612. }
  613. if (!empty($this->__errors)) {
  614. return false;
  615. }
  616. return true;
  617. }
  618. /**
  619. * Recursive directory move.
  620. *
  621. * ### Options
  622. *
  623. * - `to` The directory to copy to.
  624. * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().
  625. * - `chmod` The mode to copy the files/directories with.
  626. * - `skip` Files/directories to skip.
  627. *
  628. * @param array $options (to, from, chmod, skip)
  629. * @return boolean Success
  630. * @access public
  631. */
  632. function move($options) {
  633. $to = null;
  634. if (is_string($options)) {
  635. $to = $options;
  636. $options = (array)$options;
  637. }
  638. $options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()), $options);
  639. if ($this->copy($options)) {
  640. if ($this->delete($options['from'])) {
  641. return $this->cd($options['to']);
  642. }
  643. }
  644. return false;
  645. }
  646. /**
  647. * get messages from latest method
  648. *
  649. * @return array
  650. * @access public
  651. */
  652. function messages() {
  653. return $this->__messages;
  654. }
  655. /**
  656. * get error from latest method
  657. *
  658. * @return array
  659. * @access public
  660. */
  661. function errors() {
  662. return $this->__errors;
  663. }
  664. /**
  665. * Get the real path (taking ".." and such into account)
  666. *
  667. * @param string $path Path to resolve
  668. * @return string The resolved path
  669. */
  670. function realpath($path) {
  671. $path = str_replace('/', DS, trim($path));
  672. if (strpos($path, '..') === false) {
  673. if (!Folder::isAbsolute($path)) {
  674. $path = Folder::addPathElement($this->path, $path);
  675. }
  676. return $path;
  677. }
  678. $parts = explode(DS, $path);
  679. $newparts = array();
  680. $newpath = '';
  681. if ($path[0] === DS) {
  682. $newpath = DS;
  683. }
  684. while (($part = array_shift($parts)) !== NULL) {
  685. if ($part === '.' || $part === '') {
  686. continue;
  687. }
  688. if ($part === '..') {
  689. if (!empty($newparts)) {
  690. array_pop($newparts);
  691. continue;
  692. } else {
  693. return false;
  694. }
  695. }
  696. $newparts[] = $part;
  697. }
  698. $newpath .= implode(DS, $newparts);
  699. return Folder::slashTerm($newpath);
  700. }
  701. /**
  702. * Returns true if given $path ends in a slash (i.e. is slash-terminated).
  703. *
  704. * @param string $path Path to check
  705. * @return boolean true if path ends with slash, false otherwise
  706. * @access public
  707. * @static
  708. */
  709. function isSlashTerm($path) {
  710. $lastChar = $path[strlen($path) - 1];
  711. return $lastChar === '/' || $lastChar === '\\';
  712. }
  713. }