comparison cake/libs/folder.php @ 0:261e66bd5a0c

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