Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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