BaseFileHelper.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. <?php
  2. /**
  3. * Filesystem helper class file.
  4. *
  5. * @link http://www.yiiframework.com/
  6. * @copyright Copyright (c) 2008 Yii Software LLC
  7. * @license http://www.yiiframework.com/license/
  8. */
  9. namespace yii\helpers;
  10. use Yii;
  11. /**
  12. * BaseFileHelper provides concrete implementation for [[FileHelper]].
  13. *
  14. * Do not use BaseFileHelper. Use [[FileHelper]] instead.
  15. *
  16. * @author Qiang Xue <[email protected]>
  17. * @author Alex Makarov <[email protected]>
  18. * @since 2.0
  19. */
  20. class BaseFileHelper
  21. {
  22. /**
  23. * Normalizes a file/directory path.
  24. * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`,
  25. * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux
  26. * will be normalized as '/home/demo'.
  27. * @param string $path the file/directory path to be normalized
  28. * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`.
  29. * @return string the normalized file/directory path
  30. */
  31. public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
  32. {
  33. return rtrim(strtr($path, ['/' => $ds, '\\' => $ds]), $ds);
  34. }
  35. /**
  36. * Returns the localized version of a specified file.
  37. *
  38. * The searching is based on the specified language code. In particular,
  39. * a file with the same name will be looked for under the subdirectory
  40. * whose name is the same as the language code. For example, given the file "path/to/view.php"
  41. * and language code "zh_CN", the localized file will be looked for as
  42. * "path/to/zh_CN/view.php". If the file is not found, the original file
  43. * will be returned.
  44. *
  45. * If the target and the source language codes are the same,
  46. * the original file will be returned.
  47. *
  48. * @param string $file the original file
  49. * @param string $language the target language that the file should be localized to.
  50. * If not set, the value of [[\yii\base\Application::language]] will be used.
  51. * @param string $sourceLanguage the language that the original file is in.
  52. * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used.
  53. * @return string the matching localized file, or the original file if the localized version is not found.
  54. * If the target and the source language codes are the same, the original file will be returned.
  55. */
  56. public static function localize($file, $language = null, $sourceLanguage = null)
  57. {
  58. if ($language === null) {
  59. $language = Yii::$app->language;
  60. }
  61. if ($sourceLanguage === null) {
  62. $sourceLanguage = Yii::$app->sourceLanguage;
  63. }
  64. if ($language === $sourceLanguage) {
  65. return $file;
  66. }
  67. $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . basename($file);
  68. return is_file($desiredFile) ? $desiredFile : $file;
  69. }
  70. /**
  71. * Determines the MIME type of the specified file.
  72. * This method will first try to determine the MIME type based on
  73. * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will
  74. * fall back to [[getMimeTypeByExtension()]].
  75. * @param string $file the file name.
  76. * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`.
  77. * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php).
  78. * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case
  79. * `finfo_open()` cannot determine it.
  80. * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined.
  81. */
  82. public static function getMimeType($file, $magicFile = null, $checkExtension = true)
  83. {
  84. if (function_exists('finfo_open')) {
  85. $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile);
  86. if ($info) {
  87. $result = finfo_file($info, $file);
  88. finfo_close($info);
  89. if ($result !== false) {
  90. return $result;
  91. }
  92. }
  93. }
  94. return $checkExtension ? static::getMimeTypeByExtension($file) : null;
  95. }
  96. /**
  97. * Determines the MIME type based on the extension name of the specified file.
  98. * This method will use a local map between extension names and MIME types.
  99. * @param string $file the file name.
  100. * @param string $magicFile the path of the file that contains all available MIME type information.
  101. * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used.
  102. * @return string the MIME type. Null is returned if the MIME type cannot be determined.
  103. */
  104. public static function getMimeTypeByExtension($file, $magicFile = null)
  105. {
  106. static $mimeTypes = [];
  107. if ($magicFile === null) {
  108. $magicFile = __DIR__ . '/mimeTypes.php';
  109. }
  110. if (!isset($mimeTypes[$magicFile])) {
  111. $mimeTypes[$magicFile] = require($magicFile);
  112. }
  113. if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') {
  114. $ext = strtolower($ext);
  115. if (isset($mimeTypes[$magicFile][$ext])) {
  116. return $mimeTypes[$magicFile][$ext];
  117. }
  118. }
  119. return null;
  120. }
  121. /**
  122. * Copies a whole directory as another one.
  123. * The files and sub-directories will also be copied over.
  124. * @param string $src the source directory
  125. * @param string $dst the destination directory
  126. * @param array $options options for directory copy. Valid options are:
  127. *
  128. * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775.
  129. * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting.
  130. * - filter: callback, a PHP callback that is called for each directory or file.
  131. * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
  132. * The callback can return one of the following values:
  133. *
  134. * * true: the directory or file will be copied (the "only" and "except" options will be ignored)
  135. * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored)
  136. * * null: the "only" and "except" options will determine whether the directory or file should be copied
  137. *
  138. * - only: array, list of patterns that the file paths should match if they want to be copied.
  139. * A path matches a pattern if it contains the pattern string at its end.
  140. * For example, '.php' matches all file paths ending with '.php'.
  141. * Note, the '/' characters in a pattern matches both '/' and '\' in the paths.
  142. * If a file path matches a pattern in both "only" and "except", it will NOT be copied.
  143. * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied.
  144. * A path matches a pattern if it contains the pattern string at its end.
  145. * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/'
  146. * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b';
  147. * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches
  148. * both '/' and '\' in the paths.
  149. * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true.
  150. * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
  151. * If the callback returns false, the copy operation for the sub-directory or file will be cancelled.
  152. * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
  153. * file to be copied from, while `$to` is the copy target.
  154. * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied.
  155. * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
  156. * file copied from, while `$to` is the copy target.
  157. */
  158. public static function copyDirectory($src, $dst, $options = [])
  159. {
  160. if (!is_dir($dst)) {
  161. static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true);
  162. }
  163. $handle = opendir($src);
  164. while (($file = readdir($handle)) !== false) {
  165. if ($file === '.' || $file === '..') {
  166. continue;
  167. }
  168. $from = $src . DIRECTORY_SEPARATOR . $file;
  169. $to = $dst . DIRECTORY_SEPARATOR . $file;
  170. if (static::filterPath($from, $options)) {
  171. if (isset($options['beforeCopy']) && !call_user_func($options['beforeCopy'], $from, $to)) {
  172. continue;
  173. }
  174. if (is_file($from)) {
  175. copy($from, $to);
  176. if (isset($options['fileMode'])) {
  177. @chmod($to, $options['fileMode']);
  178. }
  179. } else {
  180. static::copyDirectory($from, $to, $options);
  181. }
  182. if (isset($options['afterCopy'])) {
  183. call_user_func($options['afterCopy'], $from, $to);
  184. }
  185. }
  186. }
  187. closedir($handle);
  188. }
  189. /**
  190. * Removes a directory (and all its content) recursively.
  191. * @param string $dir the directory to be deleted recursively.
  192. */
  193. public static function removeDirectory($dir)
  194. {
  195. if (!is_dir($dir) || !($handle = opendir($dir))) {
  196. return;
  197. }
  198. while (($file = readdir($handle)) !== false) {
  199. if ($file === '.' || $file === '..') {
  200. continue;
  201. }
  202. $path = $dir . DIRECTORY_SEPARATOR . $file;
  203. if (is_file($path)) {
  204. unlink($path);
  205. } else {
  206. static::removeDirectory($path);
  207. }
  208. }
  209. closedir($handle);
  210. rmdir($dir);
  211. }
  212. /**
  213. * Returns the files found under the specified directory and subdirectories.
  214. * @param string $dir the directory under which the files will be looked for.
  215. * @param array $options options for file searching. Valid options are:
  216. *
  217. * - filter: callback, a PHP callback that is called for each directory or file.
  218. * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
  219. * The callback can return one of the following values:
  220. *
  221. * * true: the directory or file will be returned (the "only" and "except" options will be ignored)
  222. * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored)
  223. * * null: the "only" and "except" options will determine whether the directory or file should be returned
  224. *
  225. * - only: array, list of patterns that the file paths should match if they want to be returned.
  226. * A path matches a pattern if it contains the pattern string at its end.
  227. * For example, '.php' matches all file paths ending with '.php'.
  228. * Note, the '/' characters in a pattern matches both '/' and '\' in the paths.
  229. * If a file path matches a pattern in both "only" and "except", it will NOT be returned.
  230. * - except: array, list of patterns that the file paths or directory paths should match if they want to be excluded from the result.
  231. * A path matches a pattern if it contains the pattern string at its end.
  232. * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/'
  233. * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b';
  234. * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches
  235. * both '/' and '\' in the paths.
  236. * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true.
  237. * @return array files found under the directory. The file list is sorted.
  238. */
  239. public static function findFiles($dir, $options = [])
  240. {
  241. $list = [];
  242. $handle = opendir($dir);
  243. while (($file = readdir($handle)) !== false) {
  244. if ($file === '.' || $file === '..') {
  245. continue;
  246. }
  247. $path = $dir . DIRECTORY_SEPARATOR . $file;
  248. if (static::filterPath($path, $options)) {
  249. if (is_file($path)) {
  250. $list[] = $path;
  251. } elseif (!isset($options['recursive']) || $options['recursive']) {
  252. $list = array_merge($list, static::findFiles($path, $options));
  253. }
  254. }
  255. }
  256. closedir($handle);
  257. return $list;
  258. }
  259. /**
  260. * Checks if the given file path satisfies the filtering options.
  261. * @param string $path the path of the file or directory to be checked
  262. * @param array $options the filtering options. See [[findFiles()]] for explanations of
  263. * the supported options.
  264. * @return boolean whether the file or directory satisfies the filtering options.
  265. */
  266. public static function filterPath($path, $options)
  267. {
  268. if (isset($options['filter'])) {
  269. $result = call_user_func($options['filter'], $path);
  270. if (is_bool($result)) {
  271. return $result;
  272. }
  273. }
  274. if (empty($options['except']) && empty($options['only'])) {
  275. return true;
  276. }
  277. $path = str_replace('\\', '/', $path);
  278. if ($isDir = is_dir($path)) {
  279. $path .= '/';
  280. }
  281. $n = StringHelper::byteLength($path);
  282. if (!empty($options['except'])) {
  283. foreach ($options['except'] as $name) {
  284. if (StringHelper::byteSubstr($path, -StringHelper::byteLength($name), $n) === $name) {
  285. return false;
  286. }
  287. }
  288. }
  289. if (!$isDir && !empty($options['only'])) {
  290. foreach ($options['only'] as $name) {
  291. if (StringHelper::byteSubstr($path, -StringHelper::byteLength($name), $n) === $name) {
  292. return true;
  293. }
  294. }
  295. return false;
  296. }
  297. return true;
  298. }
  299. /**
  300. * Creates a new directory.
  301. *
  302. * This method is similar to the PHP `mkdir()` function except that
  303. * it uses `chmod()` to set the permission of the created directory
  304. * in order to avoid the impact of the `umask` setting.
  305. *
  306. * @param string $path path of the directory to be created.
  307. * @param integer $mode the permission to be set for the created directory.
  308. * @param boolean $recursive whether to create parent directories if they do not exist.
  309. * @return boolean whether the directory is created successfully
  310. */
  311. public static function createDirectory($path, $mode = 0775, $recursive = true)
  312. {
  313. if (is_dir($path)) {
  314. return true;
  315. }
  316. $parentDir = dirname($path);
  317. if ($recursive && !is_dir($parentDir)) {
  318. static::createDirectory($parentDir, $mode, true);
  319. }
  320. $result = mkdir($path, $mode);
  321. chmod($path, $mode);
  322. return $result;
  323. }
  324. }