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