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