統計
| ブランチ: | リビジョン:

pictcode / lib / Cake / Utility / Folder.php @ 635eef61

履歴 | 表示 | アノテート | ダウンロード (23.755 KB)

1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
11
 * @link          http://cakephp.org CakePHP(tm) Project
12
 * @package       Cake.Utility
13
 * @since         CakePHP(tm) v 0.2.9
14
 * @license       http://www.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
 * Path to Folder.
51
 *
52
 * @var string
53
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::$path
54
 */
55
        public $path = null;
56

    
57
/**
58
 * Sortedness. Whether or not list results
59
 * should be sorted by name.
60
 *
61
 * @var bool
62
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::$sort
63
 */
64
        public $sort = false;
65

    
66
/**
67
 * Mode to be used on create. Does nothing on Windows platforms.
68
 *
69
 * @var int
70
 * http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::$mode
71
 */
72
        public $mode = 0755;
73

    
74
/**
75
 * Holds messages from last method.
76
 *
77
 * @var array
78
 */
79
        protected $_messages = array();
80

    
81
/**
82
 * Holds errors from last method.
83
 *
84
 * @var array
85
 */
86
        protected $_errors = array();
87

    
88
/**
89
 * Holds array of complete directory paths.
90
 *
91
 * @var array
92
 */
93
        protected $_directories;
94

    
95
/**
96
 * Holds array of complete file paths.
97
 *
98
 * @var array
99
 */
100
        protected $_files;
101

    
102
/**
103
 * Constructor.
104
 *
105
 * @param string $path Path to folder
106
 * @param bool $create Create folder if not found
107
 * @param string|bool $mode Mode (CHMOD) to apply to created folder, false to ignore
108
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder
109
 */
110
        public function __construct($path = false, $create = false, $mode = false) {
111
                if (empty($path)) {
112
                        $path = TMP;
113
                }
114
                if ($mode) {
115
                        $this->mode = $mode;
116
                }
117

    
118
                if (!file_exists($path) && $create === true) {
119
                        $this->create($path, $this->mode);
120
                }
121
                if (!Folder::isAbsolute($path)) {
122
                        $path = realpath($path);
123
                }
124
                if (!empty($path)) {
125
                        $this->cd($path);
126
                }
127
        }
128

    
129
/**
130
 * Return current path.
131
 *
132
 * @return string Current path
133
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::pwd
134
 */
135
        public function pwd() {
136
                return $this->path;
137
        }
138

    
139
/**
140
 * Change directory to $path.
141
 *
142
 * @param string $path Path to the directory to change to
143
 * @return string The new path. Returns false on failure
144
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::cd
145
 */
146
        public function cd($path) {
147
                $path = $this->realpath($path);
148
                if (is_dir($path)) {
149
                        return $this->path = $path;
150
                }
151
                return false;
152
        }
153

    
154
/**
155
 * Returns an array of the contents of the current directory.
156
 * The returned array holds two arrays: One of directories and one of files.
157
 *
158
 * @param bool $sort Whether you want the results sorted, set this and the sort property
159
 *   to false to get unsorted results.
160
 * @param array|bool $exceptions Either an array or boolean true will not grab dot files
161
 * @param bool $fullPath True returns the full path
162
 * @return mixed Contents of current directory as an array, an empty array on failure
163
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::read
164
 */
165
        public function read($sort = true, $exceptions = false, $fullPath = false) {
166
                $dirs = $files = array();
167

    
168
                if (!$this->pwd()) {
169
                        return array($dirs, $files);
170
                }
171
                if (is_array($exceptions)) {
172
                        $exceptions = array_flip($exceptions);
173
                }
174
                $skipHidden = isset($exceptions['.']) || $exceptions === true;
175

    
176
                try {
177
                        $iterator = new DirectoryIterator($this->path);
178
                } catch (Exception $e) {
179
                        return array($dirs, $files);
180
                }
181

    
182
                foreach ($iterator as $item) {
183
                        if ($item->isDot()) {
184
                                continue;
185
                        }
186
                        $name = $item->getFileName();
187
                        if ($skipHidden && $name[0] === '.' || isset($exceptions[$name])) {
188
                                continue;
189
                        }
190
                        if ($fullPath) {
191
                                $name = $item->getPathName();
192
                        }
193
                        if ($item->isDir()) {
194
                                $dirs[] = $name;
195
                        } else {
196
                                $files[] = $name;
197
                        }
198
                }
199
                if ($sort || $this->sort) {
200
                        sort($dirs);
201
                        sort($files);
202
                }
203
                return array($dirs, $files);
204
        }
205

    
206
/**
207
 * Returns an array of all matching files in current directory.
208
 *
209
 * @param string $regexpPattern Preg_match pattern (Defaults to: .*)
210
 * @param bool $sort Whether results should be sorted.
211
 * @return array Files that match given pattern
212
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::find
213
 */
214
        public function find($regexpPattern = '.*', $sort = false) {
215
                list(, $files) = $this->read($sort);
216
                return array_values(preg_grep('/^' . $regexpPattern . '$/i', $files));
217
        }
218

    
219
/**
220
 * Returns an array of all matching files in and below current directory.
221
 *
222
 * @param string $pattern Preg_match pattern (Defaults to: .*)
223
 * @param bool $sort Whether results should be sorted.
224
 * @return array Files matching $pattern
225
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::findRecursive
226
 */
227
        public function findRecursive($pattern = '.*', $sort = false) {
228
                if (!$this->pwd()) {
229
                        return array();
230
                }
231
                $startsOn = $this->path;
232
                $out = $this->_findRecursive($pattern, $sort);
233
                $this->cd($startsOn);
234
                return $out;
235
        }
236

    
237
/**
238
 * Private helper function for findRecursive.
239
 *
240
 * @param string $pattern Pattern to match against
241
 * @param bool $sort Whether results should be sorted.
242
 * @return array Files matching pattern
243
 */
244
        protected function _findRecursive($pattern, $sort = false) {
245
                list($dirs, $files) = $this->read($sort);
246
                $found = array();
247

    
248
                foreach ($files as $file) {
249
                        if (preg_match('/^' . $pattern . '$/i', $file)) {
250
                                $found[] = Folder::addPathElement($this->path, $file);
251
                        }
252
                }
253
                $start = $this->path;
254

    
255
                foreach ($dirs as $dir) {
256
                        $this->cd(Folder::addPathElement($start, $dir));
257
                        $found = array_merge($found, $this->findRecursive($pattern, $sort));
258
                }
259
                return $found;
260
        }
261

    
262
/**
263
 * Returns true if given $path is a Windows path.
264
 *
265
 * @param string $path Path to check
266
 * @return bool true if Windows path, false otherwise
267
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::isWindowsPath
268
 */
269
        public static function isWindowsPath($path) {
270
                return (preg_match('/^[A-Z]:\\\\/i', $path) || substr($path, 0, 2) === '\\\\');
271
        }
272

    
273
/**
274
 * Returns true if given $path is an absolute path.
275
 *
276
 * @param string $path Path to check
277
 * @return bool true if path is absolute.
278
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::isAbsolute
279
 */
280
        public static function isAbsolute($path) {
281
                if (empty($path)) {
282
                        return false;
283
                }
284

    
285
                return $path[0] === '/' ||
286
                        preg_match('/^[A-Z]:\\\\/i', $path) ||
287
                        substr($path, 0, 2) === '\\\\' ||
288
                        static::isRegisteredStreamWrapper($path);
289
        }
290

    
291
/**
292
 * Returns true if given $path is a registered stream wrapper.
293
 *
294
 * @param string $path Path to check
295
 * @return boo true If path is registered stream wrapper.
296
 */
297
        public static function isRegisteredStreamWrapper($path) {
298
                if (preg_match('/^[A-Z]+(?=:\/\/)/i', $path, $matches) &&
299
                        in_array($matches[0], stream_get_wrappers())
300
                ) {
301
                        return true;
302
                }
303
                return false;
304
        }
305

    
306
/**
307
 * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
308
 *
309
 * @param string $path Path to check
310
 * @return string Set of slashes ("\\" or "/")
311
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::normalizePath
312
 */
313
        public static function normalizePath($path) {
314
                return Folder::correctSlashFor($path);
315
        }
316

    
317
/**
318
 * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
319
 *
320
 * @param string $path Path to check
321
 * @return string Set of slashes ("\\" or "/")
322
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::correctSlashFor
323
 */
324
        public static function correctSlashFor($path) {
325
                return (Folder::isWindowsPath($path)) ? '\\' : '/';
326
        }
327

    
328
/**
329
 * Returns $path with added terminating slash (corrected for Windows or other OS).
330
 *
331
 * @param string $path Path to check
332
 * @return string Path with ending slash
333
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::slashTerm
334
 */
335
        public static function slashTerm($path) {
336
                if (Folder::isSlashTerm($path)) {
337
                        return $path;
338
                }
339
                return $path . Folder::correctSlashFor($path);
340
        }
341

    
342
/**
343
 * Returns $path with $element added, with correct slash in-between.
344
 *
345
 * @param string $path Path
346
 * @param string|array $element Element to add at end of path
347
 * @return string Combined path
348
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::addPathElement
349
 */
350
        public static function addPathElement($path, $element) {
351
                $element = (array)$element;
352
                array_unshift($element, rtrim($path, DS));
353
                return implode(DS, $element);
354
        }
355

    
356
/**
357
 * Returns true if the File is in a given CakePath.
358
 *
359
 * @param string $path The path to check.
360
 * @return bool
361
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::inCakePath
362
 */
363
        public function inCakePath($path = '') {
364
                $dir = substr(Folder::slashTerm(ROOT), 0, -1);
365
                $newdir = $dir . $path;
366

    
367
                return $this->inPath($newdir);
368
        }
369

    
370
/**
371
 * Returns true if the File is in given path.
372
 *
373
 * @param string $path The path to check that the current pwd() resides with in.
374
 * @param bool $reverse Reverse the search, check that pwd() resides within $path.
375
 * @return bool
376
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::inPath
377
 */
378
        public function inPath($path = '', $reverse = false) {
379
                $dir = Folder::slashTerm($path);
380
                $current = Folder::slashTerm($this->pwd());
381

    
382
                if (!$reverse) {
383
                        $return = preg_match('/^(.*)' . preg_quote($dir, '/') . '(.*)/', $current);
384
                } else {
385
                        $return = preg_match('/^(.*)' . preg_quote($current, '/') . '(.*)/', $dir);
386
                }
387
                return (bool)$return;
388
        }
389

    
390
/**
391
 * Change the mode on a directory structure recursively. This includes changing the mode on files as well.
392
 *
393
 * @param string $path The path to chmod.
394
 * @param int $mode Octal value, e.g. 0755.
395
 * @param bool $recursive Chmod recursively, set to false to only change the current directory.
396
 * @param array $exceptions Array of files, directories to skip.
397
 * @return bool Success.
398
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::chmod
399
 */
400
        public function chmod($path, $mode = false, $recursive = true, $exceptions = array()) {
401
                if (!$mode) {
402
                        $mode = $this->mode;
403
                }
404

    
405
                if ($recursive === false && is_dir($path)) {
406
                        //@codingStandardsIgnoreStart
407
                        if (@chmod($path, intval($mode, 8))) {
408
                                //@codingStandardsIgnoreEnd
409
                                $this->_messages[] = __d('cake_dev', '%s changed to %s', $path, $mode);
410
                                return true;
411
                        }
412

    
413
                        $this->_errors[] = __d('cake_dev', '%s NOT changed to %s', $path, $mode);
414
                        return false;
415
                }
416

    
417
                if (is_dir($path)) {
418
                        $paths = $this->tree($path);
419

    
420
                        foreach ($paths as $type) {
421
                                foreach ($type as $fullpath) {
422
                                        $check = explode(DS, $fullpath);
423
                                        $count = count($check);
424

    
425
                                        if (in_array($check[$count - 1], $exceptions)) {
426
                                                continue;
427
                                        }
428

    
429
                                        //@codingStandardsIgnoreStart
430
                                        if (@chmod($fullpath, intval($mode, 8))) {
431
                                                //@codingStandardsIgnoreEnd
432
                                                $this->_messages[] = __d('cake_dev', '%s changed to %s', $fullpath, $mode);
433
                                        } else {
434
                                                $this->_errors[] = __d('cake_dev', '%s NOT changed to %s', $fullpath, $mode);
435
                                        }
436
                                }
437
                        }
438

    
439
                        if (empty($this->_errors)) {
440
                                return true;
441
                        }
442
                }
443
                return false;
444
        }
445

    
446
/**
447
 * Returns an array of nested directories and files in each directory
448
 *
449
 * @param string $path the directory path to build the tree from
450
 * @param array|bool $exceptions Either an array of files/folder to exclude
451
 *   or boolean true to not grab dot files/folders
452
 * @param string $type either 'file' or 'dir'. null returns both files and directories
453
 * @return mixed array of nested directories and files in each directory
454
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::tree
455
 */
456
        public function tree($path = null, $exceptions = false, $type = null) {
457
                if (!$path) {
458
                        $path = $this->path;
459
                }
460
                $files = array();
461
                $directories = array($path);
462

    
463
                if (is_array($exceptions)) {
464
                        $exceptions = array_flip($exceptions);
465
                }
466
                $skipHidden = false;
467
                if ($exceptions === true) {
468
                        $skipHidden = true;
469
                } elseif (isset($exceptions['.'])) {
470
                        $skipHidden = true;
471
                        unset($exceptions['.']);
472
                }
473

    
474
                try {
475
                        $directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::KEY_AS_PATHNAME | RecursiveDirectoryIterator::CURRENT_AS_SELF);
476
                        $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
477
                } catch (Exception $e) {
478
                        if ($type === null) {
479
                                return array(array(), array());
480
                        }
481
                        return array();
482
                }
483

    
484
                foreach ($iterator as $itemPath => $fsIterator) {
485
                        if ($skipHidden) {
486
                                $subPathName = $fsIterator->getSubPathname();
487
                                if ($subPathName{0} === '.' || strpos($subPathName, DS . '.') !== false) {
488
                                        continue;
489
                                }
490
                        }
491
                        $item = $fsIterator->current();
492
                        if (!empty($exceptions) && isset($exceptions[$item->getFilename()])) {
493
                                continue;
494
                        }
495

    
496
                        if ($item->isFile()) {
497
                                $files[] = $itemPath;
498
                        } elseif ($item->isDir() && !$item->isDot()) {
499
                                $directories[] = $itemPath;
500
                        }
501
                }
502
                if ($type === null) {
503
                        return array($directories, $files);
504
                }
505
                if ($type === 'dir') {
506
                        return $directories;
507
                }
508
                return $files;
509
        }
510

    
511
/**
512
 * Create a directory structure recursively.
513
 *
514
 * Can be used to create deep path structures like `/foo/bar/baz/shoe/horn`
515
 *
516
 * @param string $pathname The directory structure to create. Either an absolute or relative
517
 *   path. If the path is relative and exists in the process' cwd it will not be created.
518
 *   Otherwise relative paths will be prefixed with the current pwd().
519
 * @param int $mode octal value 0755
520
 * @return bool Returns TRUE on success, FALSE on failure
521
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::create
522
 */
523
        public function create($pathname, $mode = false) {
524
                if (is_dir($pathname) || empty($pathname)) {
525
                        return true;
526
                }
527

    
528
                if (!static::isAbsolute($pathname)) {
529
                        $pathname = static::addPathElement($this->pwd(), $pathname);
530
                }
531

    
532
                if (!$mode) {
533
                        $mode = $this->mode;
534
                }
535

    
536
                if (is_file($pathname)) {
537
                        $this->_errors[] = __d('cake_dev', '%s is a file', $pathname);
538
                        return false;
539
                }
540
                $pathname = rtrim($pathname, DS);
541
                $nextPathname = substr($pathname, 0, strrpos($pathname, DS));
542

    
543
                if ($this->create($nextPathname, $mode)) {
544
                        if (!file_exists($pathname)) {
545
                                $old = umask(0);
546
                                if (mkdir($pathname, $mode)) {
547
                                        umask($old);
548
                                        $this->_messages[] = __d('cake_dev', '%s created', $pathname);
549
                                        return true;
550
                                }
551
                                umask($old);
552
                                $this->_errors[] = __d('cake_dev', '%s NOT created', $pathname);
553
                                return false;
554
                        }
555
                }
556
                return false;
557
        }
558

    
559
/**
560
 * Returns the size in bytes of this Folder and its contents.
561
 *
562
 * @return int size in bytes of current folder
563
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::dirsize
564
 */
565
        public function dirsize() {
566
                $size = 0;
567
                $directory = Folder::slashTerm($this->path);
568
                $stack = array($directory);
569
                $count = count($stack);
570
                for ($i = 0, $j = $count; $i < $j; ++$i) {
571
                        if (is_file($stack[$i])) {
572
                                $size += filesize($stack[$i]);
573
                        } elseif (is_dir($stack[$i])) {
574
                                $dir = dir($stack[$i]);
575
                                if ($dir) {
576
                                        while (false !== ($entry = $dir->read())) {
577
                                                if ($entry === '.' || $entry === '..') {
578
                                                        continue;
579
                                                }
580
                                                $add = $stack[$i] . $entry;
581

    
582
                                                if (is_dir($stack[$i] . $entry)) {
583
                                                        $add = Folder::slashTerm($add);
584
                                                }
585
                                                $stack[] = $add;
586
                                        }
587
                                        $dir->close();
588
                                }
589
                        }
590
                        $j = count($stack);
591
                }
592
                return $size;
593
        }
594

    
595
/**
596
 * Recursively Remove directories if the system allows.
597
 *
598
 * @param string $path Path of directory to delete
599
 * @return bool Success
600
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::delete
601
 */
602
        public function delete($path = null) {
603
                if (!$path) {
604
                        $path = $this->pwd();
605
                }
606
                if (!$path) {
607
                        return false;
608
                }
609
                $path = Folder::slashTerm($path);
610
                if (is_dir($path)) {
611
                        try {
612
                                $directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::CURRENT_AS_SELF);
613
                                $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::CHILD_FIRST);
614
                        } catch (Exception $e) {
615
                                return false;
616
                        }
617

    
618
                        foreach ($iterator as $item) {
619
                                $filePath = $item->getPathname();
620
                                if ($item->isFile() || $item->isLink()) {
621
                                        //@codingStandardsIgnoreStart
622
                                        if (@unlink($filePath)) {
623
                                                //@codingStandardsIgnoreEnd
624
                                                $this->_messages[] = __d('cake_dev', '%s removed', $filePath);
625
                                        } else {
626
                                                $this->_errors[] = __d('cake_dev', '%s NOT removed', $filePath);
627
                                        }
628
                                } elseif ($item->isDir() && !$item->isDot()) {
629
                                        //@codingStandardsIgnoreStart
630
                                        if (@rmdir($filePath)) {
631
                                                //@codingStandardsIgnoreEnd
632
                                                $this->_messages[] = __d('cake_dev', '%s removed', $filePath);
633
                                        } else {
634
                                                $this->_errors[] = __d('cake_dev', '%s NOT removed', $filePath);
635
                                                return false;
636
                                        }
637
                                }
638
                        }
639

    
640
                        $path = rtrim($path, DS);
641
                        //@codingStandardsIgnoreStart
642
                        if (@rmdir($path)) {
643
                                //@codingStandardsIgnoreEnd
644
                                $this->_messages[] = __d('cake_dev', '%s removed', $path);
645
                        } else {
646
                                $this->_errors[] = __d('cake_dev', '%s NOT removed', $path);
647
                                return false;
648
                        }
649
                }
650
                return true;
651
        }
652

    
653
/**
654
 * Recursive directory copy.
655
 *
656
 * ### Options
657
 *
658
 * - `to` The directory to copy to.
659
 * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().
660
 * - `mode` The mode to copy the files/directories with as integer, e.g. 0775.
661
 * - `skip` Files/directories to skip.
662
 * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP
663
 *
664
 * @param array|string $options Either an array of options (see above) or a string of the destination directory.
665
 * @return bool Success.
666
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::copy
667
 */
668
        public function copy($options) {
669
                if (!$this->pwd()) {
670
                        return false;
671
                }
672
                $to = null;
673
                if (is_string($options)) {
674
                        $to = $options;
675
                        $options = array();
676
                }
677
                $options += array(
678
                        'to' => $to,
679
                        'from' => $this->path,
680
                        'mode' => $this->mode,
681
                        'skip' => array(),
682
                        'scheme' => Folder::MERGE
683
                );
684

    
685
                $fromDir = $options['from'];
686
                $toDir = $options['to'];
687
                $mode = $options['mode'];
688

    
689
                if (!$this->cd($fromDir)) {
690
                        $this->_errors[] = __d('cake_dev', '%s not found', $fromDir);
691
                        return false;
692
                }
693

    
694
                if (!is_dir($toDir)) {
695
                        $this->create($toDir, $mode);
696
                }
697

    
698
                if (!is_writable($toDir)) {
699
                        $this->_errors[] = __d('cake_dev', '%s not writable', $toDir);
700
                        return false;
701
                }
702

    
703
                $exceptions = array_merge(array('.', '..', '.svn'), $options['skip']);
704
                //@codingStandardsIgnoreStart
705
                if ($handle = @opendir($fromDir)) {
706
                        //@codingStandardsIgnoreEnd
707
                        while (($item = readdir($handle)) !== false) {
708
                                $to = Folder::addPathElement($toDir, $item);
709
                                if (($options['scheme'] != Folder::SKIP || !is_dir($to)) && !in_array($item, $exceptions)) {
710
                                        $from = Folder::addPathElement($fromDir, $item);
711
                                        if (is_file($from) && (!is_file($to) || $options['scheme'] != Folder::SKIP)) {
712
                                                if (copy($from, $to)) {
713
                                                        chmod($to, intval($mode, 8));
714
                                                        touch($to, filemtime($from));
715
                                                        $this->_messages[] = __d('cake_dev', '%s copied to %s', $from, $to);
716
                                                } else {
717
                                                        $this->_errors[] = __d('cake_dev', '%s NOT copied to %s', $from, $to);
718
                                                }
719
                                        }
720

    
721
                                        if (is_dir($from) && file_exists($to) && $options['scheme'] === Folder::OVERWRITE) {
722
                                                $this->delete($to);
723
                                        }
724

    
725
                                        if (is_dir($from) && !file_exists($to)) {
726
                                                $old = umask(0);
727
                                                if (mkdir($to, $mode)) {
728
                                                        umask($old);
729
                                                        $old = umask(0);
730
                                                        chmod($to, $mode);
731
                                                        umask($old);
732
                                                        $this->_messages[] = __d('cake_dev', '%s created', $to);
733
                                                        $options = array('to' => $to, 'from' => $from) + $options;
734
                                                        $this->copy($options);
735
                                                } else {
736
                                                        $this->_errors[] = __d('cake_dev', '%s not created', $to);
737
                                                }
738
                                        } elseif (is_dir($from) && $options['scheme'] === Folder::MERGE) {
739
                                                $options = array('to' => $to, 'from' => $from) + $options;
740
                                                $this->copy($options);
741
                                        }
742
                                }
743
                        }
744
                        closedir($handle);
745
                } else {
746
                        return false;
747
                }
748

    
749
                if (!empty($this->_errors)) {
750
                        return false;
751
                }
752
                return true;
753
        }
754

    
755
/**
756
 * Recursive directory move.
757
 *
758
 * ### Options
759
 *
760
 * - `to` The directory to copy to.
761
 * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().
762
 * - `chmod` The mode to copy the files/directories with.
763
 * - `skip` Files/directories to skip.
764
 * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP
765
 *
766
 * @param array $options (to, from, chmod, skip, scheme)
767
 * @return bool Success
768
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::move
769
 */
770
        public function move($options) {
771
                $to = null;
772
                if (is_string($options)) {
773
                        $to = $options;
774
                        $options = (array)$options;
775
                }
776
                $options += array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array());
777

    
778
                if ($this->copy($options)) {
779
                        if ($this->delete($options['from'])) {
780
                                return (bool)$this->cd($options['to']);
781
                        }
782
                }
783
                return false;
784
        }
785

    
786
/**
787
 * get messages from latest method
788
 *
789
 * @param bool $reset Reset message stack after reading
790
 * @return array
791
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::messages
792
 */
793
        public function messages($reset = true) {
794
                $messages = $this->_messages;
795
                if ($reset) {
796
                        $this->_messages = array();
797
                }
798
                return $messages;
799
        }
800

    
801
/**
802
 * get error from latest method
803
 *
804
 * @param bool $reset Reset error stack after reading
805
 * @return array
806
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::errors
807
 */
808
        public function errors($reset = true) {
809
                $errors = $this->_errors;
810
                if ($reset) {
811
                        $this->_errors = array();
812
                }
813
                return $errors;
814
        }
815

    
816
/**
817
 * Get the real path (taking ".." and such into account)
818
 *
819
 * @param string $path Path to resolve
820
 * @return string The resolved path
821
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::realpath
822
 */
823
        public function realpath($path) {
824
                if (strpos($path, '..') === false) {
825
                        if (!Folder::isAbsolute($path)) {
826
                                $path = Folder::addPathElement($this->path, $path);
827
                        }
828
                        return $path;
829
                }
830
                $path = str_replace('/', DS, trim($path));
831
                $parts = explode(DS, $path);
832
                $newparts = array();
833
                $newpath = '';
834
                if ($path[0] === DS) {
835
                        $newpath = DS;
836
                }
837

    
838
                while (($part = array_shift($parts)) !== null) {
839
                        if ($part === '.' || $part === '') {
840
                                continue;
841
                        }
842
                        if ($part === '..') {
843
                                if (!empty($newparts)) {
844
                                        array_pop($newparts);
845
                                        continue;
846
                                }
847
                                return false;
848
                        }
849
                        $newparts[] = $part;
850
                }
851
                $newpath .= implode(DS, $newparts);
852

    
853
                return Folder::slashTerm($newpath);
854
        }
855

    
856
/**
857
 * Returns true if given $path ends in a slash (i.e. is slash-terminated).
858
 *
859
 * @param string $path Path to check
860
 * @return bool true if path ends with slash, false otherwise
861
 * @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::isSlashTerm
862
 */
863
        public static function isSlashTerm($path) {
864
                $lastChar = $path[strlen($path) - 1];
865
                return $lastChar === '/' || $lastChar === '\\';
866
        }
867

    
868
}