Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
16591 anikendra 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
		$path = str_replace('/', DS, trim($path));
825
		if (strpos($path, '..') === false) {
826
			if (!Folder::isAbsolute($path)) {
827
				$path = Folder::addPathElement($this->path, $path);
828
			}
829
			return $path;
830
		}
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
}