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