tikiwiki/packages/tiki-pkg-mediaalchemyst/alchemy/phpexiftool/lib/PHPExiftool/Reader.php
2023-11-20 20:52:04 +00:00

542 lines
13 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* This file is part of the PHPExiftool package.
*
* (c) Alchemy <support@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPExiftool;
use Doctrine\Common\Collections\ArrayCollection;
use PHPExiftool\Exception\EmptyCollectionException;
use PHPExiftool\Exception\LogicException;
use PHPExiftool\Exception\RuntimeException;
use Psr\Log\LoggerInterface;
/**
*
* Exiftool Reader, inspired by Symfony2 Finder.
*
* It scans files and directories, and provide an iterator on the FileEntities
* generated based on the results.
*
* Example usage:
*
* $Reader = new Reader();
*
* $Reader->in('/path/to/directory')
* ->exclude('tests')
* ->extensions(array('jpg', 'xml));
*
* //Throws an exception if no file found
* $first = $Reader->first();
*
* //Returns null if no file found
* $first = $Reader->getOneOrNull();
*
* foreach($Reader as $entity)
* {
* //Do your logic with FileEntity
* }
*
*
* @todo implement match conditions (-if EXPR) (name or metadata tag)
* @todo implement match filter
* @todo implement sort
* @todo implement -l
*
* @author Romain Neutron <imprec@gmail.com>
*/
class Reader implements \IteratorAggregate
{
protected $files = array();
protected $dirs = array();
protected $excludeDirs = array();
protected $extensions = array();
protected $extensionsToggle = null;
protected $followSymLinks = false;
protected $recursive = true;
protected $ignoreDotFile = false;
protected $sort = array();
protected $parser;
protected $exiftool;
protected $timeout = 60;
/**
*
* @var ArrayCollection
*/
protected $collection;
protected $readers = array();
/**
* Constructor
*/
public function __construct(Exiftool $exiftool, RDFParser $parser)
{
$this->exiftool = $exiftool;
$this->parser = $parser;
}
public function __destruct()
{
$this->parser = null;
$this->collection = null;
}
public function setTimeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
public function reset()
{
$this->files
= $this->dirs
= $this->excludeDirs
= $this->extensions
= $this->sort
= $this->readers = array();
$this->recursive = true;
$this->ignoreDotFile = $this->followSymLinks = false;
$this->extensionsToggle = null;
return $this;
}
/**
* Implements \IteratorAggregate Interface
*
* @return \Iterator
*/
public function getIterator()
{
return $this->all()->getIterator();
}
/**
* Add files to scan
*
* Example usage:
*
* // Will scan 3 files : dc00.jpg in CWD and absolute
* // paths /tmp/image.jpg and /tmp/raw.CR2
* $Reader ->files('dc00.jpg')
* ->files(array('/tmp/image.jpg', '/tmp/raw.CR2'))
*
* @param string|array $files The files
* @return Reader
*/
public function files($files)
{
$this->resetResults();
$this->files = array_merge($this->files, (array) $files);
return $this;
}
/**
* Add dirs to scan
*
* Example usage:
*
* // Will scan 3 dirs : documents in CWD and absolute
* // paths /usr and /var
* $Reader ->in('documents')
* ->in(array('/tmp', '/var'))
*
* @param string|array $dirs The directories
* @return Reader
*/
public function in($dirs)
{
$this->resetResults();
$this->dirs = array_merge($this->dirs, (array) $dirs);
return $this;
}
/**
* Append a reader to this one.
* Finale result will be the sum of the current reader and all appended ones.
*
* @param Reader $reader The reader to append
* @return Reader
*/
public function append(Reader $reader)
{
$this->resetResults();
$this->readers[] = $reader;
return $this;
}
/**
* Sort results with one or many criteria
*
* Example usage:
*
* // Will sort by directory then filename
* $Reader ->in('documents')
* ->sort(array('directory', 'filename'))
*
* // Will sort by filename
* $Reader ->in('documents')
* ->sort('filename')
*
* @param string|array $by
* @return Reader
*/
public function sort($by)
{
static $availableSorts = array(
'directory', 'filename', 'createdate', 'modifydate', 'filesize'
);
foreach ((array) $by as $sort) {
if ( ! in_array($sort, $availableSorts)) {
continue;
}
$this->sort[] = $sort;
}
return $this;
}
/**
* Exclude directories from scan
*
* Warning: only first depth directories can be excluded
* Imagine a directory structure like below, With a scan in "root", only
* sub1 or sub2 can be excluded, not subsub.
*
* root
* ├── sub1
* └── sub2
*    └── subsub
*
* Example usage:
*
* // Will scan documents recursively, discarding documents/test
* $Reader ->in('documents')
* ->exclude(array('test'))
*
* @param string|array $dirs The directories
* @return Reader
*/
public function exclude($dirs)
{
$this->resetResults();
$this->excludeDirs = array_merge($this->excludeDirs, (array) $dirs);
return $this;
}
/**
* Restrict / Discard files based on extensions
* Extensions are case insensitive
*
* @param string|array $extensions The list of extension
* @param Boolean $restrict Toggle restrict/discard method
* @return Reader
* @throws LogicException
*/
public function extensions($extensions, $restrict = true)
{
$this->resetResults();
if ( ! is_null($this->extensionsToggle)) {
if ((boolean) $restrict !== $this->extensionsToggle) {
throw new LogicException('You cannot restrict extensions AND exclude extension at the same time');
}
}
$this->extensionsToggle = (boolean) $restrict;
$this->extensions = array_merge($this->extensions, (array) $extensions);
return $this;
}
/**
* Toggle to enable follow Symbolic Links
*
* @return Reader
*/
public function followSymLinks()
{
$this->resetResults();
$this->followSymLinks = true;
return $this;
}
/**
* Ignore files starting with a dot (.)
*
* Folders starting with a dot are always exluded due to exiftool behaviour.
* You should include them manually
*
* @return Reader
*/
public function ignoreDotFiles()
{
$this->resetResults();
$this->ignoreDotFile = true;
return $this;
}
/**
* Disable recursivity in directories scan.
* If you only specify files, this toggle has no effect
*
* @return Reader
*/
public function notRecursive()
{
$this->resetResults();
$this->recursive = false;
return $this;
}
/**
* Return the first result. If no result available, null is returned
*
* @return FileEntity
*/
public function getOneOrNull()
{
return count($this->all()) === 0 ? null : $this->all()->first();
}
/**
* Return the first result. If no result available, throws an exception
*
* @return FileEntity
* @throws EmptyCollectionException
*/
public function first()
{
if (count($this->all()) === 0) {
throw new EmptyCollectionException('Collection is empty');
}
return $this->all()->first();
}
/**
* Perform the scan and returns all the results
*
* @return ArrayCollection
*/
public function all()
{
if (! $this->collection) {
$this->collection = $this->buildQueryAndExecute();
}
if ($this->readers) {
$elements = $this->collection->toArray();
$this->collection = null;
foreach ($this->readers as $reader) {
$elements = array_merge($elements, $reader->all()->toArray());
}
$this->collection = new ArrayCollection($elements);
}
return $this->collection;
}
public static function create(LoggerInterface $logger)
{
return new static(new Exiftool($logger), new RDFParser());
}
/**
* Reset any computed result
*
* @return Reader
*/
protected function resetResults()
{
$this->collection = null;
return $this;
}
/**
* Build the command returns an ArrayCollection of FileEntity
*
* @return ArrayCollection
*/
protected function buildQueryAndExecute()
{
$result = '';
try {
$result = trim($this->exiftool->executeCommand($this->buildQuery(), $this->timeout));
} catch (RuntimeException $e) {
/**
* In case no file found, an exit code 1 is returned
*/
if (! $this->ignoreDotFile) {
throw $e;
}
}
if ($result === '') {
return new ArrayCollection();
}
$this->parser->open($result);
return $this->parser->ParseEntities();
}
/**
* Compute raw exclude rules to simple ones, based on exclude dirs and search dirs
*
* @param string $rawExcludeDirs
* @param string $rawDirs
* @return array
* @throws RuntimeException
*/
protected function computeExcludeDirs($rawExcludeDirs, $rawSearchDirs)
{
$excludeDirs = array();
foreach ($rawExcludeDirs as $excludeDir) {
$found = false;
/**
* is this a relative path ?
*/
foreach ($rawSearchDirs as $dir) {
$currentPrefix = realpath($dir) . DIRECTORY_SEPARATOR;
$supposedExcluded = str_replace($currentPrefix, '', realpath($currentPrefix . $excludeDir));
if (! $supposedExcluded) {
continue;
}
if (strpos($supposedExcluded, DIRECTORY_SEPARATOR) === false) {
$excludeDirs[] = $supposedExcluded;
$found = true;
break;
}
}
if ($found) {
continue;
}
/**
* is this an absolute path ?
*/
$supposedExcluded = realpath($excludeDir);
if ($supposedExcluded) {
foreach ($rawSearchDirs as $dir) {
$searchDir = realpath($dir) . DIRECTORY_SEPARATOR;
$supposedRelative = str_replace($searchDir, '', $supposedExcluded);
if (strpos($supposedRelative, DIRECTORY_SEPARATOR) !== false) {
continue;
}
if (strpos($supposedExcluded, $searchDir) !== 0) {
continue;
}
if ( ! trim($supposedRelative)) {
continue;
}
$excludeDirs[] = $supposedRelative;
$found = true;
break;
}
}
if (! $found) {
throw new RuntimeException(sprintf("Invalid exclude dir %s ; Exclude dir is limited to the name of a directory at first depth", $excludeDir));
}
}
return $excludeDirs;
}
/**
* Build query from criterias
*
* @return string
*
* @throws LogicException
*/
protected function buildQuery()
{
if (! $this->dirs && ! $this->files) {
throw new LogicException('You have not set any files or directory');
}
$command = '-n -q -b -X -charset UTF8';
if ($this->recursive) {
$command .= ' -r';
}
if (!empty($this->extensions)) {
if (! $this->extensionsToggle) {
$extensionPrefix = ' --ext';
} else {
$extensionPrefix = ' -ext';
}
foreach ($this->extensions as $extension) {
$command .= $extensionPrefix . ' ' . escapeshellarg($extension);
}
}
if (! $this->followSymLinks) {
$command .= ' -i SYMLINKS';
}
if ($this->ignoreDotFile) {
$command .= " -if '\$filename !~ /^\./'";
}
foreach ($this->sort as $sort) {
$command .= ' -fileOrder ' . $sort;
}
foreach ($this->computeExcludeDirs($this->excludeDirs, $this->dirs) as $excludedDir) {
$command .= ' -i ' . escapeshellarg($excludedDir);
}
foreach ($this->dirs as $dir) {
$command .= ' ' . escapeshellarg(realpath($dir));
}
foreach ($this->files as $file) {
$command .= ' ' . escapeshellarg(realpath($file));
}
return $command;
}
}