CFileHelper.php 16 KB

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