Folder.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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. * @param string $extensions which files to search for specifically
  37. *
  38. * @return int
  39. */
  40. public static function lastModifiedFile($path, $extensions = 'md|yaml')
  41. {
  42. $last_modified = 0;
  43. $dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
  44. $itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST);
  45. $itr = new \RegexIterator($itrItr, '/^.+\.'.$extensions.'$/i');
  46. /** @var \RecursiveDirectoryIterator $file */
  47. foreach ($itr as $filepath => $file) {
  48. $file_modified = $file->getMTime();
  49. if ($file_modified > $last_modified) {
  50. $last_modified = $file_modified;
  51. }
  52. }
  53. return $last_modified;
  54. }
  55. /**
  56. * Get relative path between target and base path. If path isn't relative, return full path.
  57. *
  58. * @param string $path
  59. * @param string $base
  60. * @return string
  61. */
  62. public static function getRelativePath($path, $base = GRAV_ROOT)
  63. {
  64. if ($base) {
  65. $base = preg_replace('![\\\/]+!', '/', $base);
  66. $path = preg_replace('![\\\/]+!', '/', $path);
  67. if (strpos($path, $base) === 0) {
  68. $path = ltrim(substr($path, strlen($base)), '/');
  69. }
  70. }
  71. return $path;
  72. }
  73. /**
  74. * Shift first directory out of the path.
  75. *
  76. * @param string $path
  77. * @return string
  78. */
  79. public static function shift(&$path)
  80. {
  81. $parts = explode('/', trim($path, '/'), 2);
  82. $result = array_shift($parts);
  83. $path = array_shift($parts);
  84. return $result ?: null;
  85. }
  86. /**
  87. * Return recursive list of all files and directories under given path.
  88. *
  89. * @param string $path
  90. * @param array $params
  91. * @return array
  92. * @throws \RuntimeException
  93. */
  94. public static function all($path, array $params = array())
  95. {
  96. if ($path === false) {
  97. throw new \RuntimeException("Path to {$path} doesn't exist.");
  98. }
  99. $compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
  100. $pattern = isset($params['pattern']) ? $params['pattern'] : null;
  101. $filters = isset($params['filters']) ? $params['filters'] : null;
  102. $recursive = isset($params['recursive']) ? $params['recursive'] : true;
  103. $key = isset($params['key']) ? 'get' . $params['key'] : null;
  104. $value = isset($params['value']) ? 'get' . $params['value'] : ($recursive ? 'getSubPathname' : 'getFilename');
  105. if ($recursive) {
  106. $directory = new \RecursiveDirectoryIterator($path,
  107. \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
  108. $iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
  109. } else {
  110. $iterator = new \FilesystemIterator($path);
  111. }
  112. $results = array();
  113. /** @var \RecursiveDirectoryIterator $file */
  114. foreach ($iterator as $file) {
  115. if ($compare && $pattern && !preg_match($pattern, $file->{$compare}())) {
  116. continue;
  117. }
  118. $fileKey = $key ? $file->{$key}() : null;
  119. $filePath = $file->{$value}();
  120. if ($filters) {
  121. if (isset($filters['key'])) {
  122. $fileKey = preg_replace($filters['key'], '', $fileKey);
  123. }
  124. if (isset($filters['value'])) {
  125. $filter = $filters['value'];
  126. if (is_callable($filter)) {
  127. $filePath = call_user_func($filter, $file);
  128. } else {
  129. $filePath = preg_replace($filter, '', $filePath);
  130. }
  131. }
  132. }
  133. if ($fileKey !== null) {
  134. $results[$fileKey] = $filePath;
  135. } else {
  136. $results[] = $filePath;
  137. }
  138. }
  139. return $results;
  140. }
  141. /**
  142. * Recursively copy directory in filesystem.
  143. *
  144. * @param string $source
  145. * @param string $target
  146. * @throws \RuntimeException
  147. */
  148. public static function copy($source, $target)
  149. {
  150. $source = rtrim($source, '\\/');
  151. $target = rtrim($target, '\\/');
  152. if (!is_dir($source)) {
  153. throw new \RuntimeException('Cannot copy non-existing folder.');
  154. }
  155. // Make sure that path to the target exists before copying.
  156. self::mkdir($target);
  157. $success = true;
  158. // Go through all sub-directories and copy everything.
  159. $files = self::all($source);
  160. foreach ($files as $file) {
  161. $src = $source .'/'. $file;
  162. $dst = $target .'/'. $file;
  163. if (is_dir($src)) {
  164. // Create current directory.
  165. $success &= @mkdir($dst);
  166. } else {
  167. // Or copy current file.
  168. $success &= @copy($src, $dst);
  169. }
  170. }
  171. if (!$success) {
  172. $error = error_get_last();
  173. throw new \RuntimeException($error['message']);
  174. }
  175. // Make sure that the change will be detected when caching.
  176. @touch(dirname($target));
  177. }
  178. /**
  179. * Move directory in filesystem.
  180. *
  181. * @param string $source
  182. * @param string $target
  183. * @throws \RuntimeException
  184. */
  185. public static function move($source, $target)
  186. {
  187. if (!is_dir($source)) {
  188. throw new \RuntimeException('Cannot move non-existing folder.');
  189. }
  190. // Make sure that path to the target exists before moving.
  191. self::mkdir(dirname($target));
  192. // Just rename the directory.
  193. $success = @rename($source, $target);
  194. if (!$success) {
  195. $error = error_get_last();
  196. throw new \RuntimeException($error['message']);
  197. }
  198. // Make sure that the change will be detected when caching.
  199. @touch(dirname($source));
  200. @touch(dirname($target));
  201. }
  202. /**
  203. * Recursively delete directory from filesystem.
  204. *
  205. * @param string $target
  206. * @throws \RuntimeException
  207. * @return bool
  208. */
  209. public static function delete($target)
  210. {
  211. if (!is_dir($target)) {
  212. return;
  213. }
  214. $success = self::doDelete($target);
  215. if (!$success) {
  216. $error = error_get_last();
  217. throw new \RuntimeException($error['message']);
  218. }
  219. // Make sure that the change will be detected when caching.
  220. @touch(dirname($target));
  221. return $success;
  222. }
  223. /**
  224. * @param string $folder
  225. * @throws \RuntimeException
  226. * @internal
  227. */
  228. public static function mkdir($folder)
  229. {
  230. if (is_dir($folder)) {
  231. return;
  232. }
  233. $success = @mkdir($folder, 0777, true);
  234. if (!$success) {
  235. $error = error_get_last();
  236. throw new \RuntimeException($error['message']);
  237. }
  238. }
  239. /**
  240. * Recursive copy of one directory to another
  241. *
  242. * @param $src
  243. * @param $dest
  244. *
  245. * @return bool
  246. */
  247. public static function rcopy($src, $dest)
  248. {
  249. // If the src is not a directory do a simple file copy
  250. if (!is_dir($src)) {
  251. copy($src, $dest);
  252. return true;
  253. }
  254. // If the destination directory does not exist create it
  255. if (!is_dir($dest)) {
  256. if (!mkdir($dest)) {
  257. // If the destination directory could not be created stop processing
  258. return false;
  259. }
  260. }
  261. // Open the source directory to read in files
  262. $i = new \DirectoryIterator($src);
  263. /** @var \DirectoryIterator $f */
  264. foreach ($i as $f) {
  265. if ($f->isFile()) {
  266. copy($f->getRealPath(), "$dest/" . $f->getFilename());
  267. } else {
  268. if (!$f->isDot() && $f->isDir()) {
  269. static::rcopy($f->getRealPath(), "$dest/$f");
  270. }
  271. }
  272. }
  273. return true;
  274. }
  275. /**
  276. * @param string $folder
  277. * @return bool
  278. * @internal
  279. */
  280. protected static function doDelete($folder)
  281. {
  282. // Special case for symbolic links.
  283. if (is_link($folder)) {
  284. return @unlink($folder);
  285. }
  286. $files = new \RecursiveIteratorIterator(
  287. new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
  288. \RecursiveIteratorIterator::CHILD_FIRST
  289. );
  290. /** @var \DirectoryIterator $fileinfo */
  291. foreach ($files as $fileinfo) {
  292. if ($fileinfo->isDir()) {
  293. if (false === rmdir($fileinfo->getRealPath())) {
  294. return false;
  295. }
  296. } else {
  297. if (false === unlink($fileinfo->getRealPath())) {
  298. return false;
  299. }
  300. }
  301. }
  302. return rmdir($folder);
  303. }
  304. }