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