Folder.php 23 KB

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