Drupal investigation

AbstractFindAdapter.php 11KB

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