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