/system/src/Grav/Common/Filesystem/Folder.php

https://gitlab.com/x33n/grav · PHP · 303 lines · 247 code · 15 blank · 41 comment · 6 complexity · 5283c49231c36ddbd66a433733b14dd2 MD5 · raw file

  1. <?php
  2. namespace Grav\Common\Filesystem;
  3. /**
  4. * Folder helper class.
  5. *
  6. * @author RocketTheme
  7. * @license MIT
  8. */
  9. abstract class Folder
  10. {
  11. /**
  12. * Recursively find the last modified time under given path.
  13. *
  14. * @param string $path
  15. * @return int
  16. */
  17. public static function lastModifiedFolder($path)
  18. {
  19. $last_modified = 0;
  20. $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
  21. $filterItr = new RecursiveFolderFilterIterator($dirItr);
  22. $itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
  23. /** @var \RecursiveDirectoryIterator $file */
  24. foreach ($itr as $dir) {
  25. $dir_modified = $dir->getMTime();
  26. if ($dir_modified > $last_modified) {
  27. $last_modified = $dir_modified;
  28. }
  29. }
  30. return $last_modified;
  31. }
  32. /**
  33. * Recursively find the last modified time under given path by file.
  34. *
  35. * @param string $path
  36. * @return int
  37. */
  38. public static function lastModifiedFile($path)
  39. {
  40. $last_modified = 0;
  41. $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
  42. $filterItr = new RecursiveFileFilterIterator($dirItr);
  43. $itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
  44. /** @var \RecursiveDirectoryIterator $file */
  45. foreach ($itr as $file) {
  46. if ($file->isDir()) {
  47. continue;
  48. }
  49. $file_modified = $file->getMTime();
  50. if ($file_modified > $last_modified) {
  51. $last_modified = $file_modified;
  52. }
  53. }
  54. return $last_modified;
  55. }
  56. /**
  57. * Get relative path between target and base path. If path isn't relative, return full path.
  58. *
  59. * @param string $path
  60. * @param string $base
  61. * @return string
  62. */
  63. public static function getRelativePath($path, $base = GRAV_ROOT)
  64. {
  65. if ($base) {
  66. $base = preg_replace('![\\\/]+!', '/', $base);
  67. $path = preg_replace('![\\\/]+!', '/', $path);
  68. if (strpos($path, $base) === 0) {
  69. $path = ltrim(substr($path, strlen($base)), '/');
  70. }
  71. }
  72. return $path;
  73. }
  74. /**
  75. * Shift first directory out of the path.
  76. *
  77. * @param string $path
  78. * @return string
  79. */
  80. public static function shift(&$path)
  81. {
  82. $parts = explode('/', trim($path, '/'), 2);
  83. $result = array_shift($parts);
  84. $path = array_shift($parts);
  85. return $result ?: null;
  86. }
  87. /**
  88. * Return recursive list of all files and directories under given path.
  89. *
  90. * @param string $path
  91. * @param array $params
  92. * @return array
  93. * @throws \RuntimeException
  94. */
  95. public static function all($path, array $params = array())
  96. {
  97. if ($path === false) {
  98. throw new \RuntimeException("Path to {$path} doesn't exist.");
  99. }
  100. $compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
  101. $pattern = isset($params['pattern']) ? $params['pattern'] : null;
  102. $filters = isset($params['filters']) ? $params['filters'] : null;
  103. $recursive = isset($params['recursive']) ? $params['recursive'] : true;
  104. $key = isset($params['key']) ? 'get' . $params['key'] : null;
  105. $value = isset($params['value']) ? 'get' . $params['value'] : ($recursive ? 'getSubPathname' : 'getFilename');
  106. if ($recursive) {
  107. $directory = new \RecursiveDirectoryIterator($path,
  108. \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
  109. $iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
  110. } else {
  111. $iterator = new \FilesystemIterator($path);
  112. }
  113. $results = array();
  114. /** @var \RecursiveDirectoryIterator $file */
  115. foreach ($iterator as $file) {
  116. if ($compare && $pattern && !preg_match($pattern, $file->{$compare}())) {
  117. continue;
  118. }
  119. $fileKey = $key ? $file->{$key}() : null;
  120. $filePath = $file->{$value}();
  121. if ($filters) {
  122. if (isset($filters['key'])) {
  123. $fileKey = preg_replace($filters['key'], '', $fileKey);
  124. }
  125. if (isset($filters['value'])) {
  126. $filter = $filters['value'];
  127. if (is_callable($filter)) {
  128. $filePath = call_user_func($filter, $file);
  129. } else {
  130. $filePath = preg_replace($filter, '', $filePath);
  131. }
  132. }
  133. }
  134. if ($fileKey !== null) {
  135. $results[$fileKey] = $filePath;
  136. } else {
  137. $results[] = $filePath;
  138. }
  139. }
  140. return $results;
  141. }
  142. /**
  143. * Recursively copy directory in filesystem.
  144. *
  145. * @param string $source
  146. * @param string $target
  147. * @throws \RuntimeException
  148. */
  149. public static function copy($source, $target)
  150. {
  151. $source = rtrim($source, '\\/');
  152. $target = rtrim($target, '\\/');
  153. if (!is_dir($source)) {
  154. throw new \RuntimeException('Cannot copy non-existing folder.');
  155. }
  156. // Make sure that path to the target exists before copying.
  157. self::mkdir($target);
  158. $success = true;
  159. // Go through all sub-directories and copy everything.
  160. $files = self::all($source);
  161. foreach ($files as $file) {
  162. $src = $source .'/'. $file;
  163. $dst = $target .'/'. $file;
  164. if (is_dir($src)) {
  165. // Create current directory.
  166. $success &= @mkdir($dst);
  167. } else {
  168. // Or copy current file.
  169. $success &= @copy($src, $dst);
  170. }
  171. }
  172. if (!$success) {
  173. $error = error_get_last();
  174. throw new \RuntimeException($error['message']);
  175. }
  176. // Make sure that the change will be detected when caching.
  177. @touch(dirname($target));
  178. }
  179. /**
  180. * Move directory in filesystem.
  181. *
  182. * @param string $source
  183. * @param string $target
  184. * @throws \RuntimeException
  185. */
  186. public static function move($source, $target)
  187. {
  188. if (!is_dir($source)) {
  189. throw new \RuntimeException('Cannot move non-existing folder.');
  190. }
  191. // Make sure that path to the target exists before moving.
  192. self::mkdir(dirname($target));
  193. // Just rename the directory.
  194. $success = @rename($source, $target);
  195. if (!$success) {
  196. $error = error_get_last();
  197. throw new \RuntimeException($error['message']);
  198. }
  199. // Make sure that the change will be detected when caching.
  200. @touch(dirname($source));
  201. @touch(dirname($target));
  202. }
  203. /**
  204. * Recursively delete directory from filesystem.
  205. *
  206. * @param string $target
  207. * @throws \RuntimeException
  208. * @return bool
  209. */
  210. public static function delete($target)
  211. {
  212. if (!is_dir($target)) {
  213. throw new \RuntimeException('Cannot delete non-existing folder.');
  214. }
  215. $success = self::doDelete($target);
  216. if (!$success) {
  217. $error = error_get_last();
  218. throw new \RuntimeException($error['message']);
  219. }
  220. // Make sure that the change will be detected when caching.
  221. @touch(dirname($target));
  222. return $success;
  223. }
  224. /**
  225. * @param string $folder
  226. * @throws \RuntimeException
  227. * @internal
  228. */
  229. public static function mkdir($folder)
  230. {
  231. if (is_dir($folder)) {
  232. return;
  233. }
  234. $success = @mkdir($folder, 0777, true);
  235. if (!$success) {
  236. $error = error_get_last();
  237. throw new \RuntimeException($error['message']);
  238. }
  239. }
  240. /**
  241. * @param string $folder
  242. * @return bool
  243. * @internal
  244. */
  245. protected static function doDelete($folder)
  246. {
  247. // Special case for symbolic links.
  248. if (is_link($folder)) {
  249. return @unlink($folder);
  250. }
  251. // Go through all items in filesystem and recursively remove everything.
  252. $files = array_diff(scandir($folder), array('.', '..'));
  253. foreach ($files as $file) {
  254. $path = "{$folder}/{$file}";
  255. (is_dir($path)) ? self::doDelete($path) : @unlink($path);
  256. }
  257. return @rmdir($folder);
  258. }
  259. }