folder.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: folder.php 8102 2009-03-15 00:21:28Z phpnut $ */
00003 /**
00004  * Convenience class for handling directories.
00005  *
00006  * PHP versions 4 and 5
00007  *
00008  * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
00009  * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00010  *
00011  * Licensed under The MIT License
00012  * Redistributions of files must retain the above copyright notice.
00013  *
00014  * @filesource
00015  * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00016  * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00017  * @package       cake
00018  * @subpackage    cake.cake.libs
00019  * @since         CakePHP(tm) v 0.2.9
00020  * @version       $Revision: 8102 $
00021  * @modifiedby    $LastChangedBy: phpnut $
00022  * @lastmodified  $Date: 2009-03-14 20:21:28 -0400 (Sat, 14 Mar 2009) $
00023  * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
00024  */
00025 /**
00026  * Included libraries.
00027  *
00028  */
00029 if (!class_exists('Object')) {
00030     uses('object');
00031 }
00032 /**
00033  * Folder structure browser, lists folders and files.
00034  *
00035  * Long description for class
00036  *
00037  * @package       cake
00038  * @subpackage    cake.cake.libs
00039  */
00040 class Folder extends Object {
00041 /**
00042  * Path to Folder.
00043  *
00044  * @var string
00045  * @access public
00046  */
00047     var $path = null;
00048 /**
00049  * Sortedness.
00050  *
00051  * @var boolean
00052  * @access public
00053  */
00054     var $sort = false;
00055 /**
00056  * mode to be used on create.
00057  *
00058  * @var boolean
00059  * @access public
00060  */
00061     var $mode = 0755;
00062 /**
00063  * holds messages from last method.
00064  *
00065  * @var array
00066  * @access private
00067  */
00068     var $__messages = array();
00069 /**
00070  * holds errors from last method.
00071  *
00072  * @var array
00073  * @access private
00074  */
00075     var $__errors = false;
00076 /**
00077  * holds array of complete directory paths.
00078  *
00079  * @var array
00080  * @access private
00081  */
00082     var $__directories;
00083 /**
00084  * holds array of complete file paths.
00085  *
00086  * @var array
00087  * @access private
00088  */
00089     var $__files;
00090 /**
00091  * Constructor.
00092  *
00093  * @param string $path Path to folder
00094  * @param boolean $create Create folder if not found
00095  * @param mixed $mode Mode (CHMOD) to apply to created folder, false to ignore
00096  */
00097     function __construct($path = false, $create = false, $mode = false) {
00098         parent::__construct();
00099         if (empty($path)) {
00100             $path = TMP;
00101         }
00102         if ($mode) {
00103             $this->mode = $mode;
00104         }
00105 
00106         if (!file_exists($path) && $create === true) {
00107             $this->create($path, $this->mode);
00108         }
00109         if (!Folder::isAbsolute($path)) {
00110             $path = realpath($path);
00111         }
00112         if (!empty($path)) {
00113             $this->cd($path);
00114         }
00115     }
00116 /**
00117  * Return current path.
00118  *
00119  * @return string Current path
00120  * @access public
00121  */
00122     function pwd() {
00123         return $this->path;
00124     }
00125 /**
00126  * Change directory to $path.
00127  *
00128  * @param string $path Path to the directory to change to
00129  * @return string The new path. Returns false on failure
00130  * @access public
00131  */
00132     function cd($path) {
00133         $path = $this->realpath($path);
00134         if (is_dir($path)) {
00135             return $this->path = $path;
00136         }
00137         return false;
00138     }
00139 /**
00140  * Returns an array of the contents of the current directory.
00141  * The returned array holds two arrays: One of directories and one of files.
00142  *
00143  * @param boolean $sort
00144  * @param mixed $exceptions Either an array or boolean true will not grab dot files
00145  * @param boolean $fullPath True returns the full path
00146  * @return mixed Contents of current directory as an array, an empty array on failure
00147  * @access public
00148  */
00149     function read($sort = true, $exceptions = false, $fullPath = false) {
00150         $dirs = $files = array();
00151 
00152         if (is_array($exceptions)) {
00153             $exceptions = array_flip($exceptions);
00154         }
00155         $skipHidden = isset($exceptions['.']) || $exceptions === true;
00156 
00157         if (false === ($dir = @opendir($this->path))) {
00158             return array($dirs, $files);
00159         }
00160 
00161         while (false !== ($item = readdir($dir))) {
00162             if ($item === '.' || $item === '..' || ($skipHidden && $item[0] === '.') || isset($exceptions[$item])) {
00163                 continue;
00164             }
00165 
00166             $path = Folder::addPathElement($this->path, $item);
00167             if (is_dir($path)) {
00168                 $dirs[] = $fullPath ? $path : $item;
00169             } else {
00170                 $files[] = $fullPath ? $path : $item;
00171             }
00172         }
00173 
00174         if ($sort || $this->sort) {
00175             sort($dirs);
00176             sort($files);
00177         }
00178 
00179         closedir($dir);
00180         return array($dirs, $files);
00181     }
00182 /**
00183  * Returns an array of all matching files in current directory.
00184  *
00185  * @param string $pattern Preg_match pattern (Defaults to: .*)
00186  * @return array Files that match given pattern
00187  * @access public
00188  */
00189     function find($regexpPattern = '.*', $sort = false) {
00190         list($dirs, $files) = $this->read($sort);
00191         return array_values(preg_grep('/^' . $regexpPattern . '$/i', $files)); ;
00192     }
00193 /**
00194  * Returns an array of all matching files in and below current directory.
00195  *
00196  * @param string $pattern Preg_match pattern (Defaults to: .*)
00197  * @return array Files matching $pattern
00198  * @access public
00199  */
00200     function findRecursive($pattern = '.*', $sort = false) {
00201         $startsOn = $this->path;
00202         $out = $this->_findRecursive($pattern, $sort);
00203         $this->cd($startsOn);
00204         return $out;
00205     }
00206 /**
00207  * Private helper function for findRecursive.
00208  *
00209  * @param string $pattern Pattern to match against
00210  * @return array Files matching pattern
00211  * @access private
00212  */
00213     function _findRecursive($pattern, $sort = false) {
00214         list($dirs, $files) = $this->read($sort);
00215         $found = array();
00216 
00217         foreach ($files as $file) {
00218             if (preg_match('/^' . $pattern . '$/i', $file)) {
00219                 $found[] = Folder::addPathElement($this->path, $file);
00220             }
00221         }
00222         $start = $this->path;
00223 
00224         foreach ($dirs as $dir) {
00225             $this->cd(Folder::addPathElement($start, $dir));
00226             $found = array_merge($found, $this->findRecursive($pattern, $sort));
00227         }
00228         return $found;
00229     }
00230 /**
00231  * Returns true if given $path is a Windows path.
00232  *
00233  * @param string $path Path to check
00234  * @return boolean true if windows path, false otherwise
00235  * @access public
00236  * @static
00237  */
00238     function isWindowsPath($path) {
00239         if (preg_match('/^[A-Z]:\\\\/i', $path)) {
00240             return true;
00241         }
00242         return false;
00243     }
00244 /**
00245  * Returns true if given $path is an absolute path.
00246  *
00247  * @param string $path Path to check
00248  * @return bool
00249  * @access public
00250  * @static
00251  */
00252     function isAbsolute($path) {
00253         $match = preg_match('/^\\//', $path) || preg_match('/^[A-Z]:\\\\/i', $path);
00254         return $match;
00255     }
00256 /**
00257  * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
00258  *
00259  * @param string $path Path to check
00260  * @return string Set of slashes ("\\" or "/")
00261  * @access public
00262  * @static
00263  */
00264     function normalizePath($path) {
00265         return Folder::correctSlashFor($path);
00266     }
00267 /**
00268  * Returns a correct set of slashes for given $path. (\\ for Windows paths and / for other paths.)
00269  *
00270  * @param string $path Path to check
00271  * @return string Set of slashes ("\\" or "/")
00272  * @access public
00273  * @static
00274  */
00275     function correctSlashFor($path) {
00276         if (Folder::isWindowsPath($path)) {
00277             return '\\';
00278         }
00279         return '/';
00280     }
00281 /**
00282  * Returns $path with added terminating slash (corrected for Windows or other OS).
00283  *
00284  * @param string $path Path to check
00285  * @return string Path with ending slash
00286  * @access public
00287  * @static
00288  */
00289     function slashTerm($path) {
00290         if (Folder::isSlashTerm($path)) {
00291             return $path;
00292         }
00293         return $path . Folder::correctSlashFor($path);
00294     }
00295 /**
00296  * Returns $path with $element added, with correct slash in-between.
00297  *
00298  * @param string $path Path
00299  * @param string $element Element to and at end of path
00300  * @return string Combined path
00301  * @access public
00302  * @static
00303  */
00304     function addPathElement($path, $element) {
00305         return Folder::slashTerm($path) . $element;
00306     }
00307 /**
00308  * Returns true if the File is in a given CakePath.
00309  *
00310  * @return bool
00311  * @access public
00312  */
00313     function inCakePath($path = '') {
00314         $dir = substr(Folder::slashTerm(ROOT), 0, -1);
00315         $newdir = $dir . $path;
00316 
00317         return $this->inPath($newdir);
00318     }
00319 /**
00320  * Returns true if the File is in given path.
00321  *
00322  * @return bool
00323  * @access public
00324  */
00325     function inPath($path = '', $reverse = false) {
00326         $dir = Folder::slashTerm($path);
00327         $current = Folder::slashTerm($this->pwd());
00328 
00329         if (!$reverse) {
00330             $return = preg_match('/^(.*)' . preg_quote($dir, '/') . '(.*)/', $current);
00331         } else {
00332             $return = preg_match('/^(.*)' . preg_quote($current, '/') . '(.*)/', $dir);
00333         }
00334         if ($return == 1) {
00335             return true;
00336         } else {
00337             return false;
00338         }
00339     }
00340 /**
00341  * Change the mode on a directory structure recursively. This includes changing the mode on files as well.
00342  *
00343  * @param string $path The path to chmod
00344  * @param integer $mode octal value 0755
00345  * @param boolean $recursive chmod recursively
00346  * @param array $exceptions array of files, directories to skip
00347  * @return boolean Returns TRUE on success, FALSE on failure
00348  * @access public
00349  */
00350     function chmod($path, $mode = false, $recursive = true, $exceptions = array()) {
00351         if (!$mode) {
00352             $mode = $this->mode;
00353         }
00354 
00355         if ($recursive === false && is_dir($path)) {
00356             if (@chmod($path, intval($mode, 8))) {
00357                 $this->__messages[] = sprintf(__('%s changed to %s', true), $path, $mode);
00358                 return true;
00359             }
00360 
00361             $this->__errors[] = sprintf(__('%s NOT changed to %s', true), $path, $mode);
00362             return false;
00363         }
00364 
00365         if (is_dir($path)) {
00366             $paths = $this->tree($path);
00367 
00368             foreach ($paths as $type) {
00369                 foreach ($type as $key => $fullpath) {
00370                     $check = explode(DS, $fullpath);
00371                     $count = count($check);
00372 
00373                     if (in_array($check[$count - 1], $exceptions)) {
00374                         continue;
00375                     }
00376 
00377                     if (@chmod($fullpath, intval($mode, 8))) {
00378                         $this->__messages[] = sprintf(__('%s changed to %s', true), $fullpath, $mode);
00379                     } else {
00380                         $this->__errors[] = sprintf(__('%s NOT changed to %s', true), $fullpath, $mode);
00381                     }
00382                 }
00383             }
00384 
00385             if (empty($this->__errors)) {
00386                 return true;
00387             }
00388         }
00389         return false;
00390     }
00391 /**
00392  * Returns an array of nested directories and files in each directory
00393  *
00394  * @param string $path the directory path to build the tree from
00395  * @param boolean $hidden return hidden files and directories
00396  * @param string $type either file or dir. null returns both files and directories
00397  * @return mixed array of nested directories and files in each directory
00398  * @access public
00399  */
00400     function tree($path, $exceptions = true, $type = null) {
00401         $original = $this->path;
00402         $path = rtrim($path, DS);
00403         $this->__files = array();
00404         $this->__directories = array($path);
00405         $directories = array();
00406 
00407         if ($exceptions === false) {
00408             $exceptions = true;
00409         }
00410         while (count($this->__directories)) {
00411             $dir = array_pop($this->__directories);
00412             $this->__tree($dir, $exceptions);
00413             $directories[] = $dir;
00414         }
00415 
00416         if ($type === null) {
00417             return array($directories, $this->__files);
00418         }
00419         if ($type === 'dir') {
00420             return $directories;
00421         }
00422         $this->cd($original);
00423 
00424         return $this->__files;
00425     }
00426 /**
00427  * Private method to list directories and files in each directory
00428  *
00429  * @param string $path
00430  * @param = boolean $hidden
00431  * @access private
00432  */
00433     function __tree($path, $exceptions) {
00434         if ($this->cd($path)) {
00435             list($dirs, $files) = $this->read(false, $exceptions, true);
00436             $this->__directories = array_merge($this->__directories, $dirs);
00437             $this->__files = array_merge($this->__files, $files);
00438         }
00439     }
00440 /**
00441  * Create a directory structure recursively.
00442  *
00443  * @param string $pathname The directory structure to create
00444  * @param integer $mode octal value 0755
00445  * @return boolean Returns TRUE on success, FALSE on failure
00446  * @access public
00447  */
00448     function create($pathname, $mode = false) {
00449         if (is_dir($pathname) || empty($pathname)) {
00450             return true;
00451         }
00452 
00453         if (!$mode) {
00454             $mode = $this->mode;
00455         }
00456 
00457         if (is_file($pathname)) {
00458             $this->__errors[] = sprintf(__('%s is a file', true), $pathname);
00459             return false;
00460         }
00461         $nextPathname = substr($pathname, 0, strrpos($pathname, DS));
00462 
00463         if ($this->create($nextPathname, $mode)) {
00464             if (!file_exists($pathname)) {
00465                 $old = umask(0);
00466                 if (mkdir($pathname, $mode)) {
00467                     umask($old);
00468                     $this->__messages[] = sprintf(__('%s created', true), $pathname);
00469                     return true;
00470                 } else {
00471                     umask($old);
00472                     $this->__errors[] = sprintf(__('%s NOT created', true), $pathname);
00473                     return false;
00474                 }
00475             }
00476         }
00477         return true;
00478     }
00479 /**
00480  * Returns the size in bytes of this Folder.
00481  *
00482  * @param string $directory Path to directory
00483  * @return int size in bytes of current folder
00484  * @access public
00485  */
00486     function dirsize() {
00487         $size = 0;
00488         $directory = Folder::slashTerm($this->path);
00489         $stack = array($directory);
00490         $count = count($stack);
00491         for ($i = 0, $j = $count; $i < $j; ++$i) {
00492             if (is_file($stack[$i])) {
00493                 $size += filesize($stack[$i]);
00494             } elseif (is_dir($stack[$i])) {
00495                 $dir = dir($stack[$i]);
00496                 if ($dir) {
00497                     while (false !== ($entry = $dir->read())) {
00498                         if ($entry === '.' || $entry === '..') {
00499                             continue;
00500                         }
00501                         $add = $stack[$i] . $entry;
00502 
00503                         if (is_dir($stack[$i] . $entry)) {
00504                             $add = Folder::slashTerm($add);
00505                         }
00506                         $stack[] = $add;
00507                     }
00508                     $dir->close();
00509                 }
00510             }
00511             $j = count($stack);
00512         }
00513         return $size;
00514     }
00515 /**
00516  * Recursively Remove directories if system allow.
00517  *
00518  * @param string $path Path of directory to delete
00519  * @return boolean Success
00520  * @access public
00521  */
00522     function delete($path = null) {
00523         if (!$path) {
00524             $path = $this->pwd();
00525         }
00526         $path = Folder::slashTerm($path);
00527         if (is_dir($path) === true) {
00528             $normalFiles = glob($path . '*');
00529             $hiddenFiles = glob($path . '\.?*');
00530 
00531             $normalFiles = $normalFiles ? $normalFiles : array();
00532             $hiddenFiles = $hiddenFiles ? $hiddenFiles : array();
00533 
00534             $files = array_merge($normalFiles, $hiddenFiles);
00535             if (is_array($files)) {
00536                 foreach ($files as $file) {
00537                     if (preg_match('/(\.|\.\.)$/', $file)) {
00538                         continue;
00539                     }
00540                     if (is_file($file) === true) {
00541                         if (@unlink($file)) {
00542                             $this->__messages[] = sprintf(__('%s removed', true), $file);
00543                         } else {
00544                             $this->__errors[] = sprintf(__('%s NOT removed', true), $file);
00545                         }
00546                     } elseif (is_dir($file) === true && $this->delete($file) === false) {
00547                         return false;
00548                     }
00549                 }
00550             }
00551             $path = substr($path, 0, strlen($path) - 1);
00552             if (rmdir($path) === false) {
00553                 $this->__errors[] = sprintf(__('%s NOT removed', true), $path);
00554                 return false;
00555             } else {
00556                 $this->__messages[] = sprintf(__('%s removed', true), $path);
00557             }
00558         }
00559         return true;
00560     }
00561 /**
00562  * Recursive directory copy.
00563  *
00564  * @param array $options (to, from, chmod, skip)
00565  * @return bool
00566  * @access public
00567  */
00568     function copy($options = array()) {
00569         $to = null;
00570         if (is_string($options)) {
00571             $to = $options;
00572             $options = array();
00573         }
00574         $options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()), $options);
00575 
00576         $fromDir = $options['from'];
00577         $toDir = $options['to'];
00578         $mode = $options['mode'];
00579 
00580         if (!$this->cd($fromDir)) {
00581             $this->__errors[] = sprintf(__('%s not found', true), $fromDir);
00582             return false;
00583         }
00584 
00585         if (!is_dir($toDir)) {
00586             $this->mkdir($toDir, $mode);
00587         }
00588 
00589         if (!is_writable($toDir)) {
00590             $this->__errors[] = sprintf(__('%s not writable', true), $toDir);
00591             return false;
00592         }
00593 
00594         $exceptions = array_merge(array('.', '..', '.svn'), $options['skip']);
00595         if ($handle = @opendir($fromDir)) {
00596             while (false !== ($item = readdir($handle))) {
00597                 if (!in_array($item, $exceptions)) {
00598                     $from = Folder::addPathElement($fromDir, $item);
00599                     $to = Folder::addPathElement($toDir, $item);
00600                     if (is_file($from)) {
00601                         if (copy($from, $to)) {
00602                             chmod($to, intval($mode, 8));
00603                             touch($to, filemtime($from));
00604                             $this->__messages[] = sprintf(__('%s copied to %s', true), $from, $to);
00605                         } else {
00606                             $this->__errors[] = sprintf(__('%s NOT copied to %s', true), $from, $to);
00607                         }
00608                     }
00609 
00610                     if (is_dir($from) && !file_exists($to)) {
00611                         $old = umask(0);
00612                         if (mkdir($to, $mode)) {
00613                             umask($old);
00614                             $old = umask(0);
00615                             chmod($to, $mode);
00616                             umask($old);
00617                             $this->__messages[] = sprintf(__('%s created', true), $to);
00618                             $options = array_merge($options, array('to'=> $to, 'from'=> $from));
00619                             $this->copy($options);
00620                         } else {
00621                             $this->__errors[] = sprintf(__('%s not created', true), $to);
00622                         }
00623                     }
00624                 }
00625             }
00626             closedir($handle);
00627         } else {
00628             return false;
00629         }
00630 
00631         if (!empty($this->__errors)) {
00632             return false;
00633         }
00634         return true;
00635     }
00636 /**
00637  * Recursive directory move.
00638  *
00639  * @param array $options (to, from, chmod, skip)
00640  * @return boolean Success
00641  * @access public
00642  */
00643     function move($options) {
00644         $to = null;
00645         if (is_string($options)) {
00646             $to = $options;
00647             $options = (array)$options;
00648         }
00649         $options = array_merge(array('to' => $to, 'from' => $this->path, 'mode' => $this->mode, 'skip' => array()), $options);
00650 
00651         if ($this->copy($options)) {
00652             if ($this->delete($options['from'])) {
00653                 return $this->cd($options['to']);
00654             }
00655         }
00656         return false;
00657     }
00658 /**
00659  * get messages from latest method
00660  *
00661  * @return array
00662  * @access public
00663  */
00664     function messages() {
00665         return $this->__messages;
00666     }
00667 /**
00668  * get error from latest method
00669  *
00670  * @return array
00671  * @access public
00672  */
00673     function errors() {
00674         return $this->__errors;
00675     }
00676 /**
00677  * nix flavored alias
00678  *
00679  * @see read
00680  * @access public
00681  */
00682     function ls($sort = true, $exceptions = false) {
00683         return $this->read($sort, $exceptions);
00684     }
00685 /**
00686  * nix flavored alias
00687  *
00688  * @see create
00689  * @access public
00690  */
00691     function mkdir($pathname, $mode = 0755) {
00692         return $this->create($pathname, $mode);
00693     }
00694 /**
00695  * nix flavored alias
00696  *
00697  * @see copy
00698  * @access public
00699  */
00700     function cp($options) {
00701         return $this->copy($options);
00702     }
00703 /**
00704  * nix flavored alias
00705  *
00706  * @see move
00707  * @access public
00708  */
00709     function mv($options) {
00710         return $this->move($options);
00711     }
00712 /**
00713  * nix flavored alias
00714  *
00715  * @see delete
00716  * @access public
00717  */
00718     function rm($path) {
00719         return $this->delete($path);
00720     }
00721 /**
00722  * Get the real path (taking ".." and such into account)
00723  *
00724  * @param string $path Path to resolve
00725  * @return string The resolved path
00726  */
00727     function realpath($path) {
00728         $path = str_replace('/', DS, trim($path));
00729         if (strpos($path, '..') === false) {
00730             if (!Folder::isAbsolute($path)) {
00731                 $path = Folder::addPathElement($this->path, $path);
00732             }
00733             return $path;
00734         }
00735         $parts = explode(DS, $path);
00736         $newparts = array();
00737         $newpath = '';
00738         if ($path[0] === DS) {
00739             $newpath = DS;
00740         }
00741 
00742         while (($part = array_shift($parts)) !== NULL) {
00743             if ($part === '.' || $part === '') {
00744                 continue;
00745             }
00746             if ($part === '..') {
00747                 if (count($newparts) > 0) {
00748                     array_pop($newparts);
00749                     continue;
00750                 } else {
00751                     return false;
00752                 }
00753             }
00754             $newparts[] = $part;
00755         }
00756         $newpath .= implode(DS, $newparts);
00757 
00758         return Folder::slashTerm($newpath);
00759     }
00760 /**
00761  * Returns true if given $path ends in a slash (i.e. is slash-terminated).
00762  *
00763  * @param string $path Path to check
00764  * @return boolean true if path ends with slash, false otherwise
00765  * @access public
00766  * @static
00767  */
00768     function isSlashTerm($path) {
00769         $lastChar = $path[strlen($path) - 1];
00770         return $lastChar === '/' || $lastChar === '\\';
00771     }
00772 }
00773 ?>

Generated on Sun Nov 22 00:30:53 2009 for CakePHP 1.2.x.x (v1.2.4.8284) by doxygen 1.4.7