123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- <?php
- /*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Symfony\Component\Finder\Adapter;
- use Symfony\Component\Finder\Exception\AccessDeniedException;
- use Symfony\Component\Finder\Iterator;
- use Symfony\Component\Finder\Shell\Shell;
- use Symfony\Component\Finder\Expression\Expression;
- use Symfony\Component\Finder\Shell\Command;
- use Symfony\Component\Finder\Iterator\SortableIterator;
- use Symfony\Component\Finder\Comparator\NumberComparator;
- use Symfony\Component\Finder\Comparator\DateComparator;
- /**
- * Shell engine implementation using GNU find command.
- *
- * @author Jean-François Simon <contact@jfsimon.fr>
- */
- abstract class AbstractFindAdapter extends AbstractAdapter
- {
- /**
- * @var Shell
- */
- protected $shell;
- /**
- * Constructor.
- */
- public function __construct()
- {
- $this->shell = new Shell();
- }
- /**
- * {@inheritdoc}
- */
- public function searchInDirectory($dir)
- {
- // having "/../" in path make find fail
- $dir = realpath($dir);
- // searching directories containing or not containing strings leads to no result
- if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) {
- return new Iterator\FilePathsIterator(array(), $dir);
- }
- $command = Command::create();
- $find = $this->buildFindCommand($command, $dir);
- if ($this->followLinks) {
- $find->add('-follow');
- }
- $find->add('-mindepth')->add($this->minDepth + 1);
- if (PHP_INT_MAX !== $this->maxDepth) {
- $find->add('-maxdepth')->add($this->maxDepth + 1);
- }
- if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
- $find->add('-type d');
- } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
- $find->add('-type f');
- }
- $this->buildNamesFiltering($find, $this->names);
- $this->buildNamesFiltering($find, $this->notNames, true);
- $this->buildPathsFiltering($find, $dir, $this->paths);
- $this->buildPathsFiltering($find, $dir, $this->notPaths, true);
- $this->buildSizesFiltering($find, $this->sizes);
- $this->buildDatesFiltering($find, $this->dates);
- $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs');
- $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut');
- if ($useGrep && ($this->contains || $this->notContains)) {
- $grep = $command->ins('grep');
- $this->buildContentFiltering($grep, $this->contains);
- $this->buildContentFiltering($grep, $this->notContains, true);
- }
- if ($useSort) {
- $this->buildSorting($command, $this->sort);
- }
- $command->setErrorHandler(
- $this->ignoreUnreadableDirs
- // If directory is unreadable and finder is set to ignore it, `stderr` is ignored.
- ? function ($stderr) { return; }
- : function ($stderr) { throw new AccessDeniedException($stderr); }
- );
- $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute());
- $iterator = new Iterator\FilePathsIterator($paths, $dir);
- if ($this->exclude) {
- $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
- }
- if (!$useGrep && ($this->contains || $this->notContains)) {
- $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
- }
- if ($this->filters) {
- $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
- }
- if (!$useSort && $this->sort) {
- $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
- $iterator = $iteratorAggregate->getIterator();
- }
- return $iterator;
- }
- /**
- * {@inheritdoc}
- */
- protected function canBeUsed()
- {
- return $this->shell->testCommand('find');
- }
- /**
- * @param Command $command
- * @param string $dir
- *
- * @return Command
- */
- protected function buildFindCommand(Command $command, $dir)
- {
- return $command
- ->ins('find')
- ->add('find ')
- ->arg($dir)
- ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions
- }
- /**
- * @param Command $command
- * @param string[] $names
- * @param Boolean $not
- */
- private function buildNamesFiltering(Command $command, array $names, $not = false)
- {
- if (0 === count($names)) {
- return;
- }
- $command->add($not ? '-not' : null)->cmd('(');
- foreach ($names as $i => $name) {
- $expr = Expression::create($name);
- // Find does not support expandable globs ("*.{a,b}" syntax).
- if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
- $expr = Expression::create($expr->getGlob()->toRegex(false));
- }
- // Fixes 'not search' and 'full path matching' regex problems.
- // - Jokers '.' are replaced by [^/].
- // - We add '[^/]*' before and after regex (if no ^|$ flags are present).
- if ($expr->isRegex()) {
- $regex = $expr->getRegex();
- $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*')
- ->setStartFlag(false)
- ->setStartJoker(true)
- ->replaceJokers('[^/]');
- if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
- $regex->setEndJoker(false)->append('[^/]*');
- }
- }
- $command
- ->add($i > 0 ? '-or' : null)
- ->add($expr->isRegex()
- ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
- : ($expr->isCaseSensitive() ? '-name' : '-iname')
- )
- ->arg($expr->renderPattern());
- }
- $command->cmd(')');
- }
- /**
- * @param Command $command
- * @param string $dir
- * @param string[] $paths
- * @param Boolean $not
- */
- private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
- {
- if (0 === count($paths)) {
- return;
- }
- $command->add($not ? '-not' : null)->cmd('(');
- foreach ($paths as $i => $path) {
- $expr = Expression::create($path);
- // Find does not support expandable globs ("*.{a,b}" syntax).
- if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
- $expr = Expression::create($expr->getGlob()->toRegex(false));
- }
- // Fixes 'not search' regex problems.
- if ($expr->isRegex()) {
- $regex = $expr->getRegex();
- $regex->prepend($regex->hasStartFlag() ? $dir.DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag());
- } else {
- $expr->prepend('*')->append('*');
- }
- $command
- ->add($i > 0 ? '-or' : null)
- ->add($expr->isRegex()
- ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
- : ($expr->isCaseSensitive() ? '-path' : '-ipath')
- )
- ->arg($expr->renderPattern());
- }
- $command->cmd(')');
- }
- /**
- * @param Command $command
- * @param NumberComparator[] $sizes
- */
- private function buildSizesFiltering(Command $command, array $sizes)
- {
- foreach ($sizes as $i => $size) {
- $command->add($i > 0 ? '-and' : null);
- switch ($size->getOperator()) {
- case '<=':
- $command->add('-size -'.($size->getTarget() + 1).'c');
- break;
- case '>=':
- $command->add('-size +'. ($size->getTarget() - 1).'c');
- break;
- case '>':
- $command->add('-size +'.$size->getTarget().'c');
- break;
- case '!=':
- $command->add('-size -'.$size->getTarget().'c');
- $command->add('-size +'.$size->getTarget().'c');
- case '<':
- default:
- $command->add('-size -'.$size->getTarget().'c');
- }
- }
- }
- /**
- * @param Command $command
- * @param DateComparator[] $dates
- */
- private function buildDatesFiltering(Command $command, array $dates)
- {
- foreach ($dates as $i => $date) {
- $command->add($i > 0 ? '-and' : null);
- $mins = (int) round((time()-$date->getTarget()) / 60);
- if (0 > $mins) {
- // mtime is in the future
- $command->add(' -mmin -0');
- // we will have no result so we don't need to continue
- return;
- }
- switch ($date->getOperator()) {
- case '<=':
- $command->add('-mmin +'.($mins - 1));
- break;
- case '>=':
- $command->add('-mmin -'.($mins + 1));
- break;
- case '>':
- $command->add('-mmin -'.$mins);
- break;
- case '!=':
- $command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
- break;
- case '<':
- default:
- $command->add('-mmin +'.$mins);
- }
- }
- }
- /**
- * @param Command $command
- * @param string $sort
- *
- * @throws \InvalidArgumentException
- */
- private function buildSorting(Command $command, $sort)
- {
- $this->buildFormatSorting($command, $sort);
- }
- /**
- * @param Command $command
- * @param string $sort
- */
- abstract protected function buildFormatSorting(Command $command, $sort);
- /**
- * @param Command $command
- * @param array $contains
- * @param Boolean $not
- */
- abstract protected function buildContentFiltering(Command $command, array $contains, $not = false);
- }
|