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

/cake/libs/folder.php

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