PageRenderTime 26ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php

https://gitlab.com/techniconline/kmc
PHP | 331 lines | 207 code | 48 blank | 76 comment | 34 complexity | b0e6ac6bf987ce85fb2e71413fe7ff58 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Finder\Adapter;
  11. use Symfony\Component\Finder\Exception\AccessDeniedException;
  12. use Symfony\Component\Finder\Iterator;
  13. use Symfony\Component\Finder\Shell\Shell;
  14. use Symfony\Component\Finder\Expression\Expression;
  15. use Symfony\Component\Finder\Shell\Command;
  16. use Symfony\Component\Finder\Comparator\NumberComparator;
  17. use Symfony\Component\Finder\Comparator\DateComparator;
  18. /**
  19. * Shell engine implementation using GNU find command.
  20. *
  21. * @author Jean-François Simon <contact@jfsimon.fr>
  22. */
  23. abstract class AbstractFindAdapter extends AbstractAdapter
  24. {
  25. /**
  26. * @var Shell
  27. */
  28. protected $shell;
  29. /**
  30. * Constructor.
  31. */
  32. public function __construct()
  33. {
  34. $this->shell = new Shell();
  35. }
  36. /**
  37. * {@inheritdoc}
  38. */
  39. public function searchInDirectory($dir)
  40. {
  41. // having "/../" in path make find fail
  42. $dir = realpath($dir);
  43. // searching directories containing or not containing strings leads to no result
  44. if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) {
  45. return new Iterator\FilePathsIterator(array(), $dir);
  46. }
  47. $command = Command::create();
  48. $find = $this->buildFindCommand($command, $dir);
  49. if ($this->followLinks) {
  50. $find->add('-follow');
  51. }
  52. $find->add('-mindepth')->add($this->minDepth + 1);
  53. if (PHP_INT_MAX !== $this->maxDepth) {
  54. $find->add('-maxdepth')->add($this->maxDepth + 1);
  55. }
  56. if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
  57. $find->add('-type d');
  58. } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
  59. $find->add('-type f');
  60. }
  61. $this->buildNamesFiltering($find, $this->names);
  62. $this->buildNamesFiltering($find, $this->notNames, true);
  63. $this->buildPathsFiltering($find, $dir, $this->paths);
  64. $this->buildPathsFiltering($find, $dir, $this->notPaths, true);
  65. $this->buildSizesFiltering($find, $this->sizes);
  66. $this->buildDatesFiltering($find, $this->dates);
  67. $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs');
  68. $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut');
  69. if ($useGrep && ($this->contains || $this->notContains)) {
  70. $grep = $command->ins('grep');
  71. $this->buildContentFiltering($grep, $this->contains);
  72. $this->buildContentFiltering($grep, $this->notContains, true);
  73. }
  74. if ($useSort) {
  75. $this->buildSorting($command, $this->sort);
  76. }
  77. $command->setErrorHandler(
  78. $this->ignoreUnreadableDirs
  79. // If directory is unreadable and finder is set to ignore it, `stderr` is ignored.
  80. ? function ($stderr) {
  81. return;
  82. }
  83. : function ($stderr) {
  84. throw new AccessDeniedException($stderr);
  85. }
  86. );
  87. $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute());
  88. $iterator = new Iterator\FilePathsIterator($paths, $dir);
  89. if ($this->exclude) {
  90. $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
  91. }
  92. if (!$useGrep && ($this->contains || $this->notContains)) {
  93. $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
  94. }
  95. if ($this->filters) {
  96. $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
  97. }
  98. if (!$useSort && $this->sort) {
  99. $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
  100. $iterator = $iteratorAggregate->getIterator();
  101. }
  102. return $iterator;
  103. }
  104. /**
  105. * {@inheritdoc}
  106. */
  107. protected function canBeUsed()
  108. {
  109. return $this->shell->testCommand('find');
  110. }
  111. /**
  112. * @param Command $command
  113. * @param string $dir
  114. *
  115. * @return Command
  116. */
  117. protected function buildFindCommand(Command $command, $dir)
  118. {
  119. return $command
  120. ->ins('find')
  121. ->add('find ')
  122. ->arg($dir)
  123. ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions
  124. }
  125. /**
  126. * @param Command $command
  127. * @param string[] $names
  128. * @param bool $not
  129. */
  130. private function buildNamesFiltering(Command $command, array $names, $not = false)
  131. {
  132. if (0 === count($names)) {
  133. return;
  134. }
  135. $command->add($not ? '-not' : null)->cmd('(');
  136. foreach ($names as $i => $name) {
  137. $expr = Expression::create($name);
  138. // Find does not support expandable globs ("*.{a,b}" syntax).
  139. if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
  140. $expr = Expression::create($expr->getGlob()->toRegex(false));
  141. }
  142. // Fixes 'not search' and 'full path matching' regex problems.
  143. // - Jokers '.' are replaced by [^/].
  144. // - We add '[^/]*' before and after regex (if no ^|$ flags are present).
  145. if ($expr->isRegex()) {
  146. $regex = $expr->getRegex();
  147. $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*')
  148. ->setStartFlag(false)
  149. ->setStartJoker(true)
  150. ->replaceJokers('[^/]');
  151. if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
  152. $regex->setEndJoker(false)->append('[^/]*');
  153. }
  154. }
  155. $command
  156. ->add($i > 0 ? '-or' : null)
  157. ->add($expr->isRegex()
  158. ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
  159. : ($expr->isCaseSensitive() ? '-name' : '-iname')
  160. )
  161. ->arg($expr->renderPattern());
  162. }
  163. $command->cmd(')');
  164. }
  165. /**
  166. * @param Command $command
  167. * @param string $dir
  168. * @param string[] $paths
  169. * @param bool $not
  170. */
  171. private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
  172. {
  173. if (0 === count($paths)) {
  174. return;
  175. }
  176. $command->add($not ? '-not' : null)->cmd('(');
  177. foreach ($paths as $i => $path) {
  178. $expr = Expression::create($path);
  179. // Find does not support expandable globs ("*.{a,b}" syntax).
  180. if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
  181. $expr = Expression::create($expr->getGlob()->toRegex(false));
  182. }
  183. // Fixes 'not search' regex problems.
  184. if ($expr->isRegex()) {
  185. $regex = $expr->getRegex();
  186. $regex->prepend($regex->hasStartFlag() ? preg_quote($dir) . DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag());
  187. } else {
  188. $expr->prepend('*')->append('*');
  189. }
  190. $command
  191. ->add($i > 0 ? '-or' : null)
  192. ->add($expr->isRegex()
  193. ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
  194. : ($expr->isCaseSensitive() ? '-path' : '-ipath')
  195. )
  196. ->arg($expr->renderPattern());
  197. }
  198. $command->cmd(')');
  199. }
  200. /**
  201. * @param Command $command
  202. * @param NumberComparator[] $sizes
  203. */
  204. private function buildSizesFiltering(Command $command, array $sizes)
  205. {
  206. foreach ($sizes as $i => $size) {
  207. $command->add($i > 0 ? '-and' : null);
  208. switch ($size->getOperator()) {
  209. case '<=':
  210. $command->add('-size -' . ($size->getTarget() + 1) . 'c');
  211. break;
  212. case '>=':
  213. $command->add('-size +' . ($size->getTarget() - 1) . 'c');
  214. break;
  215. case '>':
  216. $command->add('-size +' . $size->getTarget() . 'c');
  217. break;
  218. case '!=':
  219. $command->add('-size -' . $size->getTarget() . 'c');
  220. $command->add('-size +' . $size->getTarget() . 'c');
  221. break;
  222. case '<':
  223. default:
  224. $command->add('-size -' . $size->getTarget() . 'c');
  225. }
  226. }
  227. }
  228. /**
  229. * @param Command $command
  230. * @param DateComparator[] $dates
  231. */
  232. private function buildDatesFiltering(Command $command, array $dates)
  233. {
  234. foreach ($dates as $i => $date) {
  235. $command->add($i > 0 ? '-and' : null);
  236. $mins = (int)round((time() - $date->getTarget()) / 60);
  237. if (0 > $mins) {
  238. // mtime is in the future
  239. $command->add(' -mmin -0');
  240. // we will have no result so we don't need to continue
  241. return;
  242. }
  243. switch ($date->getOperator()) {
  244. case '<=':
  245. $command->add('-mmin +' . ($mins - 1));
  246. break;
  247. case '>=':
  248. $command->add('-mmin -' . ($mins + 1));
  249. break;
  250. case '>':
  251. $command->add('-mmin -' . $mins);
  252. break;
  253. case '!=':
  254. $command->add('-mmin +' . $mins . ' -or -mmin -' . $mins);
  255. break;
  256. case '<':
  257. default:
  258. $command->add('-mmin +' . $mins);
  259. }
  260. }
  261. }
  262. /**
  263. * @param Command $command
  264. * @param string $sort
  265. *
  266. * @throws \InvalidArgumentException
  267. */
  268. private function buildSorting(Command $command, $sort)
  269. {
  270. $this->buildFormatSorting($command, $sort);
  271. }
  272. /**
  273. * @param Command $command
  274. * @param string $sort
  275. */
  276. abstract protected function buildFormatSorting(Command $command, $sort);
  277. /**
  278. * @param Command $command
  279. * @param array $contains
  280. * @param bool $not
  281. */
  282. abstract protected function buildContentFiltering(Command $command, array $contains, $not = false);
  283. }