1: <?php
2: /**
3: * CFileHelper class file.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: /**
12: * CFileHelper provides a set of helper methods for common file system operations.
13: *
14: * @author Qiang Xue <qiang.xue@gmail.com>
15: * @package system.utils
16: * @since 1.0
17: */
18: class CFileHelper
19: {
20: /**
21: * Returns the extension name of a file path.
22: * For example, the path "path/to/something.php" would return "php".
23: * @param string $path the file path
24: * @return string the extension name without the dot character.
25: * @since 1.1.2
26: */
27: public static function getExtension($path)
28: {
29: return pathinfo($path,PATHINFO_EXTENSION);
30: }
31:
32: /**
33: * Copies a directory recursively as another.
34: * If the destination directory does not exist, it will be created recursively.
35: * @param string $src the source directory
36: * @param string $dst the destination directory
37: * @param array $options options for directory copy. Valid options are:
38: * <ul>
39: * <li>fileTypes: array, list of file name suffix (without dot). Only files with these suffixes will be copied.</li>
40: * <li>exclude: array, list of directory and file exclusions. Each exclusion can be either a name or a path.
41: * If a file or directory name or path matches the exclusion, it will not be copied. For example, an exclusion of
42: * '.svn' will exclude all files and directories whose name is '.svn'. And an exclusion of '/a/b' will exclude
43: * file or directory '$src/a/b'. Note, that '/' should be used as separator regardless of the value of the DIRECTORY_SEPARATOR constant.
44: * </li>
45: * <li>level: integer, recursion depth, default=-1.
46: * Level -1 means copying all directories and files under the directory;
47: * Level 0 means copying only the files DIRECTLY under the directory;
48: * level N means copying those directories that are within N levels.
49: * </li>
50: * <li>newDirMode - the permission to be set for newly copied directories (defaults to 0777);</li>
51: * <li>newFileMode - the permission to be set for newly copied files (defaults to the current environment setting).</li>
52: * </ul>
53: */
54: public static function copyDirectory($src,$dst,$options=array())
55: {
56: $fileTypes=array();
57: $exclude=array();
58: $level=-1;
59: extract($options);
60: if(!is_dir($dst))
61: self::createDirectory($dst,isset($options['newDirMode'])?$options['newDirMode']:null,true);
62:
63: self::copyDirectoryRecursive($src,$dst,'',$fileTypes,$exclude,$level,$options);
64: }
65:
66: /**
67: * Removes a directory recursively.
68: * @param string $directory to be deleted recursively.
69: * @param array $options for the directory removal. Valid options are:
70: * <ul>
71: * <li>traverseSymlinks: boolean, whether symlinks to the directories should be traversed too.
72: * Defaults to `false`, meaning that the content of the symlinked directory would not be deleted.
73: * Only symlink would be removed in that default case.</li>
74: * </ul>
75: * Note, options parameter is available since 1.1.16
76: * @since 1.1.14
77: */
78: public static function removeDirectory($directory,$options=array())
79: {
80: if(!isset($options['traverseSymlinks']))
81: $options['traverseSymlinks']=false;
82: $items=glob($directory.DIRECTORY_SEPARATOR.'{,.}*',GLOB_MARK | GLOB_BRACE);
83: foreach($items as $item)
84: {
85: if(basename($item)=='.' || basename($item)=='..')
86: continue;
87: if(substr($item,-1)==DIRECTORY_SEPARATOR)
88: {
89: if(!$options['traverseSymlinks'] && is_link(rtrim($item,DIRECTORY_SEPARATOR)))
90: unlink(rtrim($item,DIRECTORY_SEPARATOR));
91: else
92: self::removeDirectory($item,$options);
93: }
94: else
95: unlink($item);
96: }
97: if(is_dir($directory=rtrim($directory,'\\/')))
98: {
99: if(is_link($directory))
100: unlink($directory);
101: else
102: rmdir($directory);
103: }
104: }
105:
106: /**
107: * Returns the files found under the specified directory and subdirectories.
108: * @param string $dir the directory under which the files will be looked for
109: * @param array $options options for file searching. Valid options are:
110: * <ul>
111: * <li>fileTypes: array, list of file name suffix (without dot). Only files with these suffixes will be returned.</li>
112: * <li>exclude: array, list of directory and file exclusions. Each exclusion can be either a name or a path.
113: * If a file or directory name or path matches the exclusion, it will not be copied. For example, an exclusion of
114: * '.svn' will exclude all files and directories whose name is '.svn'. And an exclusion of '/a/b' will exclude
115: * file or directory '$src/a/b'. Note, that '/' should be used as separator regardless of the value of the DIRECTORY_SEPARATOR constant.
116: * </li>
117: * <li>level: integer, recursion depth, default=-1.
118: * Level -1 means searching for all directories and files under the directory;
119: * Level 0 means searching for only the files DIRECTLY under the directory;
120: * level N means searching for those directories that are within N levels.
121: * </li>
122: * <li>absolutePaths: boolean, whether to return absolute paths or relative ones, defaults to true.</li>
123: * </ul>
124: * @return array files found under the directory. The file list is sorted.
125: */
126: public static function findFiles($dir,$options=array())
127: {
128: $fileTypes=array();
129: $exclude=array();
130: $level=-1;
131: $absolutePaths=true;
132: extract($options);
133: $list=self::findFilesRecursive($dir,'',$fileTypes,$exclude,$level,$absolutePaths);
134: sort($list);
135: return $list;
136: }
137:
138: /**
139: * Copies a directory.
140: * This method is mainly used by {@link copyDirectory}.
141: * @param string $src the source directory
142: * @param string $dst the destination directory
143: * @param string $base the path relative to the original source directory
144: * @param array $fileTypes list of file name suffix (without dot). Only files with these suffixes will be copied.
145: * @param array $exclude list of directory and file exclusions. Each exclusion can be either a name or a path.
146: * If a file or directory name or path matches the exclusion, it will not be copied. For example, an exclusion of
147: * '.svn' will exclude all files and directories whose name is '.svn'. And an exclusion of '/a/b' will exclude
148: * file or directory '$src/a/b'. Note, that '/' should be used as separator regardless of the value of the DIRECTORY_SEPARATOR constant.
149: * @param integer $level recursion depth. It defaults to -1.
150: * Level -1 means copying all directories and files under the directory;
151: * Level 0 means copying only the files DIRECTLY under the directory;
152: * level N means copying those directories that are within N levels.
153: * @param array $options additional options. The following options are supported:
154: * newDirMode - the permission to be set for newly copied directories (defaults to 0777);
155: * newFileMode - the permission to be set for newly copied files (defaults to the current environment setting).
156: */
157: protected static function copyDirectoryRecursive($src,$dst,$base,$fileTypes,$exclude,$level,$options)
158: {
159: if(!is_dir($dst))
160: self::createDirectory($dst,isset($options['newDirMode'])?$options['newDirMode']:null,false);
161:
162: $folder=opendir($src);
163: if($folder===false)
164: throw new Exception('Unable to open directory: ' . $src);
165: while(($file=readdir($folder))!==false)
166: {
167: if($file==='.' || $file==='..')
168: continue;
169: $path=$src.DIRECTORY_SEPARATOR.$file;
170: $isFile=is_file($path);
171: if(self::validatePath($base,$file,$isFile,$fileTypes,$exclude))
172: {
173: if($isFile)
174: {
175: copy($path,$dst.DIRECTORY_SEPARATOR.$file);
176: if(isset($options['newFileMode']))
177: @chmod($dst.DIRECTORY_SEPARATOR.$file,$options['newFileMode']);
178: }
179: elseif($level)
180: self::copyDirectoryRecursive($path,$dst.DIRECTORY_SEPARATOR.$file,$base.'/'.$file,$fileTypes,$exclude,$level-1,$options);
181: }
182: }
183: closedir($folder);
184: }
185:
186: /**
187: * Returns the files found under the specified directory and subdirectories.
188: * This method is mainly used by {@link findFiles}.
189: * @param string $dir the source directory
190: * @param string $base the path relative to the original source directory
191: * @param array $fileTypes list of file name suffix (without dot). Only files with these suffixes will be returned.
192: * @param array $exclude list of directory and file exclusions. Each exclusion can be either a name or a path.
193: * If a file or directory name or path matches the exclusion, it will not be copied. For example, an exclusion of
194: * '.svn' will exclude all files and directories whose name is '.svn'. And an exclusion of '/a/b' will exclude
195: * file or directory '$src/a/b'. Note, that '/' should be used as separator regardless of the value of the DIRECTORY_SEPARATOR constant.
196: * @param integer $level recursion depth. It defaults to -1.
197: * Level -1 means searching for all directories and files under the directory;
198: * Level 0 means searching for only the files DIRECTLY under the directory;
199: * level N means searching for those directories that are within N levels.
200: * @param boolean $absolutePaths whether to return absolute paths or relative ones
201: * @return array files found under the directory.
202: */
203: protected static function findFilesRecursive($dir,$base,$fileTypes,$exclude,$level,$absolutePaths)
204: {
205: $list=array();
206: $handle=opendir($dir.$base);
207: if($handle===false)
208: throw new Exception('Unable to open directory: ' . $dir);
209: while(($file=readdir($handle))!==false)
210: {
211: if($file==='.' || $file==='..')
212: continue;
213: $path=substr($base.DIRECTORY_SEPARATOR.$file,1);
214: $fullPath=$dir.DIRECTORY_SEPARATOR.$path;
215: $isFile=is_file($fullPath);
216: if(self::validatePath($base,$file,$isFile,$fileTypes,$exclude))
217: {
218: if($isFile)
219: $list[]=$absolutePaths?$fullPath:$path;
220: elseif($level)
221: $list=array_merge($list,self::findFilesRecursive($dir,$base.'/'.$file,$fileTypes,$exclude,$level-1,$absolutePaths));
222: }
223: }
224: closedir($handle);
225: return $list;
226: }
227:
228: /**
229: * Validates a file or directory.
230: * @param string $base the path relative to the original source directory
231: * @param string $file the file or directory name
232: * @param boolean $isFile whether this is a file
233: * @param array $fileTypes list of valid file name suffixes (without dot).
234: * @param array $exclude list of directory and file exclusions. Each exclusion can be either a name or a path.
235: * If a file or directory name or path matches the exclusion, false will be returned. For example, an exclusion of
236: * '.svn' will return false for all files and directories whose name is '.svn'. And an exclusion of '/a/b' will return false for
237: * file or directory '$src/a/b'. Note, that '/' should be used as separator regardless of the value of the DIRECTORY_SEPARATOR constant.
238: * @return boolean whether the file or directory is valid
239: */
240: protected static function validatePath($base,$file,$isFile,$fileTypes,$exclude)
241: {
242: foreach($exclude as $e)
243: {
244: if($file===$e || strpos($base.'/'.$file,$e)===0)
245: return false;
246: }
247: if(!$isFile || empty($fileTypes))
248: return true;
249: if(($type=self::getExtension($file))!=='')
250: return in_array($type,$fileTypes);
251: else
252: return false;
253: }
254:
255: /**
256: * Determines the MIME type of the specified file.
257: * This method will attempt the following approaches in order:
258: * <ol>
259: * <li>finfo</li>
260: * <li>mime_content_type</li>
261: * <li>{@link getMimeTypeByExtension}, when $checkExtension is set true.</li>
262: * </ol>
263: * @param string $file the file name.
264: * @param string $magicFile name of a magic database file, usually something like /path/to/magic.mime.
265: * This will be passed as the second parameter to {@link http://php.net/manual/en/function.finfo-open.php finfo_open}.
266: * Magic file format described in {@link http://linux.die.net/man/5/magic man 5 magic}, note that this file does not
267: * contain a standard PHP array as you might suppose. Specified magic file will be used only when fileinfo
268: * PHP extension is available. This parameter has been available since version 1.1.3.
269: * @param boolean $checkExtension whether to check the file extension in case the MIME type cannot be determined
270: * based on finfo and mime_content_type. Defaults to true. This parameter has been available since version 1.1.4.
271: * @return string the MIME type. Null is returned if the MIME type cannot be determined.
272: */
273: public static function getMimeType($file,$magicFile=null,$checkExtension=true)
274: {
275: if(function_exists('finfo_open'))
276: {
277: $options=defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME;
278: $info=$magicFile===null ? finfo_open($options) : finfo_open($options,$magicFile);
279:
280: if($info && ($result=finfo_file($info,$file))!==false)
281: return $result;
282: }
283:
284: if(function_exists('mime_content_type') && ($result=mime_content_type($file))!==false)
285: return $result;
286:
287: return $checkExtension ? self::getMimeTypeByExtension($file) : null;
288: }
289:
290: /**
291: * Determines the MIME type based on the extension name of the specified file.
292: * This method will use a local map between extension name and MIME type.
293: * @param string $file the file name.
294: * @param string $magicFile the path of the file that contains all available MIME type information.
295: * If this is not set, the default 'system.utils.mimeTypes' file will be used.
296: * This parameter has been available since version 1.1.3.
297: * @return string the MIME type. Null is returned if the MIME type cannot be determined.
298: */
299: public static function getMimeTypeByExtension($file,$magicFile=null)
300: {
301: static $extensions,$customExtensions=array();
302: if($magicFile===null && $extensions===null)
303: $extensions=require(Yii::getPathOfAlias('system.utils.mimeTypes').'.php');
304: elseif($magicFile!==null && !isset($customExtensions[$magicFile]))
305: $customExtensions[$magicFile]=require($magicFile);
306: if(($ext=self::getExtension($file))!=='')
307: {
308: $ext=strtolower($ext);
309: if($magicFile===null && isset($extensions[$ext]))
310: return $extensions[$ext];
311: elseif($magicFile!==null && isset($customExtensions[$magicFile][$ext]))
312: return $customExtensions[$magicFile][$ext];
313: }
314: return null;
315: }
316:
317: /**
318: * Determines the file extension name based on its MIME type.
319: * This method will use a local map between MIME type and extension name.
320: * @param string $file the file name.
321: * @param string $magicFile the path of the file that contains all available extension information.
322: * If this is not set, the default 'system.utils.fileExtensions' file will be used.
323: * This parameter has been available since version 1.1.16.
324: * @return string extension name. Null is returned if the extension cannot be determined.
325: */
326: public static function getExtensionByMimeType($file,$magicFile=null)
327: {
328: static $mimeTypes,$customMimeTypes=array();
329: if($magicFile===null && $mimeTypes===null)
330: $mimeTypes=require(Yii::getPathOfAlias('system.utils.fileExtensions').'.php');
331: elseif($magicFile!==null && !isset($customMimeTypes[$magicFile]))
332: $customMimeTypes[$magicFile]=require($magicFile);
333: if(($mime=self::getMimeType($file))!==null)
334: {
335: $mime=strtolower($mime);
336: if($magicFile===null && isset($mimeTypes[$mime]))
337: return $mimeTypes[$mime];
338: elseif($magicFile!==null && isset($customMimeTypes[$magicFile][$mime]))
339: return $customMimeTypes[$magicFile][$mime];
340: }
341: return null;
342: }
343:
344: /**
345: * Shared environment safe version of mkdir. Supports recursive creation.
346: * For avoidance of umask side-effects chmod is used.
347: *
348: * @param string $dst path to be created
349: * @param integer $mode the permission to be set for newly created directories, if not set - 0777 will be used
350: * @param boolean $recursive whether to create directory structure recursive if parent dirs do not exist
351: * @return boolean result of mkdir
352: * @see mkdir
353: */
354: public static function createDirectory($dst,$mode=null,$recursive=false)
355: {
356: if($mode===null)
357: $mode=0777;
358: $prevDir=dirname($dst);
359: if($recursive && !is_dir($dst) && !is_dir($prevDir))
360: self::createDirectory(dirname($dst),$mode,true);
361: $res=mkdir($dst, $mode);
362: @chmod($dst,$mode);
363: return $res;
364: }
365: }
366: