FilesUtil.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <?php
  2. namespace PhpZip\Util;
  3. use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
  4. use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
  5. /**
  6. * Files util.
  7. *
  8. * @author Ne-Lexa alexey@nelexa.ru
  9. * @license MIT
  10. *
  11. * @internal
  12. */
  13. final class FilesUtil
  14. {
  15. /**
  16. * Is empty directory.
  17. *
  18. * @param string $dir Directory
  19. *
  20. * @return bool
  21. */
  22. public static function isEmptyDir($dir)
  23. {
  24. if (!is_readable($dir)) {
  25. return false;
  26. }
  27. return \count(scandir($dir)) === 2;
  28. }
  29. /**
  30. * Remove recursive directory.
  31. *
  32. * @param string $dir directory path
  33. */
  34. public static function removeDir($dir)
  35. {
  36. $files = new \RecursiveIteratorIterator(
  37. new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
  38. \RecursiveIteratorIterator::CHILD_FIRST
  39. );
  40. /** @var \SplFileInfo $fileInfo */
  41. foreach ($files as $fileInfo) {
  42. $function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
  43. $function($fileInfo->getPathname());
  44. }
  45. @rmdir($dir);
  46. }
  47. /**
  48. * Convert glob pattern to regex pattern.
  49. *
  50. * @param string $globPattern
  51. *
  52. * @return string
  53. */
  54. public static function convertGlobToRegEx($globPattern)
  55. {
  56. // Remove beginning and ending * globs because they're useless
  57. $globPattern = trim($globPattern, '*');
  58. $escaping = false;
  59. $inCurrent = 0;
  60. $chars = str_split($globPattern);
  61. $regexPattern = '';
  62. foreach ($chars as $currentChar) {
  63. switch ($currentChar) {
  64. case '*':
  65. $regexPattern .= ($escaping ? '\\*' : '.*');
  66. $escaping = false;
  67. break;
  68. case '?':
  69. $regexPattern .= ($escaping ? '\\?' : '.');
  70. $escaping = false;
  71. break;
  72. case '.':
  73. case '(':
  74. case ')':
  75. case '+':
  76. case '|':
  77. case '^':
  78. case '$':
  79. case '@':
  80. case '%':
  81. $regexPattern .= '\\' . $currentChar;
  82. $escaping = false;
  83. break;
  84. case '\\':
  85. if ($escaping) {
  86. $regexPattern .= '\\\\';
  87. $escaping = false;
  88. } else {
  89. $escaping = true;
  90. }
  91. break;
  92. case '{':
  93. if ($escaping) {
  94. $regexPattern .= '\\{';
  95. } else {
  96. $regexPattern = '(';
  97. $inCurrent++;
  98. }
  99. $escaping = false;
  100. break;
  101. case '}':
  102. if ($inCurrent > 0 && !$escaping) {
  103. $regexPattern .= ')';
  104. $inCurrent--;
  105. } elseif ($escaping) {
  106. $regexPattern = '\\}';
  107. } else {
  108. $regexPattern = '}';
  109. }
  110. $escaping = false;
  111. break;
  112. case ',':
  113. if ($inCurrent > 0 && !$escaping) {
  114. $regexPattern .= '|';
  115. } elseif ($escaping) {
  116. $regexPattern .= '\\,';
  117. } else {
  118. $regexPattern = ',';
  119. }
  120. break;
  121. default:
  122. $escaping = false;
  123. $regexPattern .= $currentChar;
  124. }
  125. }
  126. return $regexPattern;
  127. }
  128. /**
  129. * Search files.
  130. *
  131. * @param string $inputDir
  132. * @param bool $recursive
  133. * @param array $ignoreFiles
  134. *
  135. * @return array Searched file list
  136. */
  137. public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
  138. {
  139. if ($recursive) {
  140. $directoryIterator = new \RecursiveDirectoryIterator($inputDir);
  141. if (!empty($ignoreFiles)) {
  142. $directoryIterator = new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles);
  143. }
  144. $iterator = new \RecursiveIteratorIterator($directoryIterator);
  145. } else {
  146. $directoryIterator = new \DirectoryIterator($inputDir);
  147. if (!empty($ignoreFiles)) {
  148. $directoryIterator = new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
  149. }
  150. $iterator = new \IteratorIterator($directoryIterator);
  151. }
  152. $fileList = [];
  153. foreach ($iterator as $file) {
  154. if ($file instanceof \SplFileInfo) {
  155. $fileList[] = $file->getPathname();
  156. }
  157. }
  158. return $fileList;
  159. }
  160. /**
  161. * Search files from glob pattern.
  162. *
  163. * @param string $globPattern
  164. * @param int $flags
  165. * @param bool $recursive
  166. *
  167. * @return array Searched file list
  168. */
  169. public static function globFileSearch($globPattern, $flags = 0, $recursive = true)
  170. {
  171. $flags = (int) $flags;
  172. $recursive = (bool) $recursive;
  173. $files = glob($globPattern, $flags);
  174. if (!$recursive) {
  175. return $files;
  176. }
  177. foreach (glob(\dirname($globPattern) . \DIRECTORY_SEPARATOR . '*', \GLOB_ONLYDIR | \GLOB_NOSORT) as $dir) {
  178. // Unpacking the argument via ... is supported starting from php 5.6 only
  179. /** @noinspection SlowArrayOperationsInLoopInspection */
  180. $files = array_merge($files, self::globFileSearch($dir . \DIRECTORY_SEPARATOR . basename($globPattern), $flags, $recursive));
  181. }
  182. return $files;
  183. }
  184. /**
  185. * Search files from regex pattern.
  186. *
  187. * @param string $folder
  188. * @param string $pattern
  189. * @param bool $recursive
  190. *
  191. * @return array Searched file list
  192. */
  193. public static function regexFileSearch($folder, $pattern, $recursive = true)
  194. {
  195. if ($recursive) {
  196. $directoryIterator = new \RecursiveDirectoryIterator($folder);
  197. $iterator = new \RecursiveIteratorIterator($directoryIterator);
  198. } else {
  199. $directoryIterator = new \DirectoryIterator($folder);
  200. $iterator = new \IteratorIterator($directoryIterator);
  201. }
  202. $regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH);
  203. $fileList = [];
  204. foreach ($regexIterator as $file) {
  205. if ($file instanceof \SplFileInfo) {
  206. $fileList[] = $file->getPathname();
  207. }
  208. }
  209. return $fileList;
  210. }
  211. /**
  212. * Convert bytes to human size.
  213. *
  214. * @param int $size Size bytes
  215. * @param string|null $unit Unit support 'GB', 'MB', 'KB'
  216. *
  217. * @return string
  218. */
  219. public static function humanSize($size, $unit = null)
  220. {
  221. if (($unit === null && $size >= 1 << 30) || $unit === 'GB') {
  222. return number_format($size / (1 << 30), 2) . 'GB';
  223. }
  224. if (($unit === null && $size >= 1 << 20) || $unit === 'MB') {
  225. return number_format($size / (1 << 20), 2) . 'MB';
  226. }
  227. if (($unit === null && $size >= 1 << 10) || $unit === 'KB') {
  228. return number_format($size / (1 << 10), 2) . 'KB';
  229. }
  230. return number_format($size) . ' bytes';
  231. }
  232. /**
  233. * Normalizes zip path.
  234. *
  235. * @param string $path Zip path
  236. *
  237. * @return string
  238. */
  239. public static function normalizeZipPath($path)
  240. {
  241. return implode(
  242. \DIRECTORY_SEPARATOR,
  243. array_filter(
  244. explode('/', (string) $path),
  245. static function ($part) {
  246. return $part !== '.' && $part !== '..';
  247. }
  248. )
  249. );
  250. }
  251. /**
  252. * Returns whether the file path is an absolute path.
  253. *
  254. * @param string $file A file path
  255. *
  256. * @return bool
  257. *
  258. * @see source symfony filesystem component
  259. */
  260. public static function isAbsolutePath($file)
  261. {
  262. return strspn($file, '/\\', 0, 1)
  263. || (
  264. \strlen($file) > 3 && ctype_alpha($file[0])
  265. && $file[1] === ':'
  266. && strspn($file, '/\\', 2, 1)
  267. )
  268. || parse_url($file, \PHP_URL_SCHEME) !== null;
  269. }
  270. /**
  271. * @param string $target
  272. * @param string $path
  273. * @param bool $allowSymlink
  274. *
  275. * @return bool
  276. */
  277. public static function symlink($target, $path, $allowSymlink)
  278. {
  279. if (\DIRECTORY_SEPARATOR === '\\' || !$allowSymlink) {
  280. return file_put_contents($path, $target) !== false;
  281. }
  282. return symlink($target, $path);
  283. }
  284. /**
  285. * @param string $file
  286. *
  287. * @return bool
  288. */
  289. public static function isBadCompressionFile($file)
  290. {
  291. $badCompressFileExt = [
  292. 'dic',
  293. 'dng',
  294. 'f4v',
  295. 'flipchart',
  296. 'h264',
  297. 'lrf',
  298. 'mobi',
  299. 'mts',
  300. 'nef',
  301. 'pspimage',
  302. ];
  303. $ext = strtolower(pathinfo($file, \PATHINFO_EXTENSION));
  304. if (\in_array($ext, $badCompressFileExt, true)) {
  305. return true;
  306. }
  307. $mimeType = self::getMimeTypeFromFile($file);
  308. return self::isBadCompressionMimeType($mimeType);
  309. }
  310. /**
  311. * @param string $mimeType
  312. *
  313. * @return bool
  314. */
  315. public static function isBadCompressionMimeType($mimeType)
  316. {
  317. static $badDeflateCompMimeTypes = [
  318. 'application/epub+zip',
  319. 'application/gzip',
  320. 'application/vnd.debian.binary-package',
  321. 'application/vnd.oasis.opendocument.graphics',
  322. 'application/vnd.oasis.opendocument.presentation',
  323. 'application/vnd.oasis.opendocument.text',
  324. 'application/vnd.oasis.opendocument.text-master',
  325. 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  326. 'application/vnd.rn-realmedia',
  327. 'application/x-7z-compressed',
  328. 'application/x-arj',
  329. 'application/x-bzip2',
  330. 'application/x-hwp',
  331. 'application/x-lzip',
  332. 'application/x-lzma',
  333. 'application/x-ms-reader',
  334. 'application/x-rar',
  335. 'application/x-rpm',
  336. 'application/x-stuffit',
  337. 'application/x-tar',
  338. 'application/x-xz',
  339. 'application/zip',
  340. 'application/zlib',
  341. 'audio/flac',
  342. 'audio/mpeg',
  343. 'audio/ogg',
  344. 'audio/vnd.dolby.dd-raw',
  345. 'audio/webm',
  346. 'audio/x-ape',
  347. 'audio/x-hx-aac-adts',
  348. 'audio/x-m4a',
  349. 'audio/x-m4a',
  350. 'audio/x-wav',
  351. 'image/gif',
  352. 'image/heic',
  353. 'image/jp2',
  354. 'image/jpeg',
  355. 'image/png',
  356. 'image/vnd.djvu',
  357. 'image/webp',
  358. 'image/x-canon-cr2',
  359. 'video/ogg',
  360. 'video/webm',
  361. 'video/x-matroska',
  362. 'video/x-ms-asf',
  363. 'x-epoc/x-sisx-app',
  364. ];
  365. if (\in_array($mimeType, $badDeflateCompMimeTypes, true)) {
  366. return true;
  367. }
  368. return false;
  369. }
  370. /**
  371. * @param string $file
  372. *
  373. * @return string
  374. *
  375. * @noinspection PhpComposerExtensionStubsInspection
  376. */
  377. public static function getMimeTypeFromFile($file)
  378. {
  379. if (\function_exists('mime_content_type')) {
  380. return mime_content_type($file);
  381. }
  382. return 'application/octet-stream';
  383. }
  384. /**
  385. * @param string $contents
  386. *
  387. * @return string
  388. * @noinspection PhpComposerExtensionStubsInspection
  389. */
  390. public static function getMimeTypeFromString($contents)
  391. {
  392. $contents = (string) $contents;
  393. $finfo = new \finfo(\FILEINFO_MIME);
  394. $mimeType = $finfo->buffer($contents);
  395. if ($mimeType === false) {
  396. $mimeType = 'application/octet-stream';
  397. }
  398. return explode(';', $mimeType)[0];
  399. }
  400. }