commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / vendor / symfony / finder / Symfony / Component / 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 use Symfony\Component\Finder\Exception\AccessDeniedException;
15 use Symfony\Component\Finder\Iterator;
16 use Symfony\Component\Finder\Shell\Shell;
17 use Symfony\Component\Finder\Expression\Expression;
18 use Symfony\Component\Finder\Shell\Command;
19 use Symfony\Component\Finder\Comparator\NumberComparator;
20 use Symfony\Component\Finder\Comparator\DateComparator;
21
22 /**
23 * Shell engine implementation using GNU find command.
24 *
25 * @author Jean-François Simon <contact@jfsimon.fr>
26 */
27 abstract class AbstractFindAdapter extends AbstractAdapter
28 {
29 /**
30 * @var Shell
31 */
32 protected $shell;
33
34 /**
35 * Constructor.
36 */
37 public function __construct()
38 {
39 $this->shell = new Shell();
40 }
41
42 /**
43 * {@inheritdoc}
44 */
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(
95 $this->ignoreUnreadableDirs
96 // If directory is unreadable and finder is set to ignore it, `stderr` is ignored.
97 ? function ($stderr) { }
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
124 /**
125 * {@inheritdoc}
126 */
127 protected function canBeUsed()
128 {
129 return $this->shell->testCommand('find');
130 }
131
132 /**
133 * @param Command $command
134 * @param string $dir
135 *
136 * @return Command
137 */
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
147 /**
148 * @param Command $command
149 * @param string[] $names
150 * @param bool $not
151 */
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
194 /**
195 * @param Command $command
196 * @param string $dir
197 * @param string[] $paths
198 * @param bool $not
199 */
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
236 /**
237 * @param Command $command
238 * @param NumberComparator[] $sizes
239 */
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
266 /**
267 * @param Command $command
268 * @param DateComparator[] $dates
269 */
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
304 /**
305 * @param Command $command
306 * @param string $sort
307 *
308 * @throws \InvalidArgumentException
309 */
310 private function buildSorting(Command $command, $sort)
311 {
312 $this->buildFormatSorting($command, $sort);
313 }
314
315 /**
316 * @param Command $command
317 * @param string $sort
318 */
319 abstract protected function buildFormatSorting(Command $command, $sort);
320
321 /**
322 * @param Command $command
323 * @param array $contains
324 * @param bool $not
325 */
326 abstract protected function buildContentFiltering(Command $command, array $contains, $not = false);
327 }