├── tests
├── fixtures
│ ├── css
│ │ ├── reset.css
│ │ ├── old
│ │ │ └── old_style.css
│ │ └── style.css
│ ├── base.css
│ ├── js
│ │ └── script.js
│ └── images
│ │ ├── 1-top-left.jpg
│ │ ├── 5-left-top.jpg
│ │ ├── 2-top-right.jpg
│ │ ├── 6-right-top.jpg
│ │ ├── 3-bottom-right.jpg
│ │ ├── 4-bottom-left.jpg
│ │ ├── 7-right-bottom.jpg
│ │ ├── 8-left-bottom.jpg
│ │ └── nut.svg
├── fixtures2
│ └── empty.jpg
├── FilesystemTestCase.php
├── Handler
│ ├── Image
│ │ ├── ExifTest.php
│ │ ├── TypeTest.php
│ │ └── InfoTest.php
│ ├── DirectoryTest.php
│ └── FileTest.php
├── Iterator
│ ├── CallbackMapIteratorTest.php
│ ├── AppendIteratorTest.php
│ ├── IteratorTestCase.php
│ ├── FileContentFilterIteratorTest.php
│ ├── RecursiveDirectoryIteratorTest.php
│ ├── PathFilterIteratorTest.php
│ ├── DateRangeFilterIteratorTest.php
│ ├── ExcludeDirectoryFilterIteratorTest.php
│ ├── SortableIteratorTest.php
│ └── GlobIteratorTest.php
└── Adapter
│ └── LocalTest.php
├── .gitignore
├── src
├── Exception
│ ├── NotSupportedException.php
│ ├── PluginNotFoundException.php
│ ├── RootViolationException.php
│ ├── LogicException.php
│ ├── RuntimeException.php
│ ├── DumpException.php
│ ├── BadMethodCallException.php
│ ├── InvalidArgumentException.php
│ ├── IncludeFileException.php
│ ├── ExceptionInterface.php
│ ├── FileExistsException.php
│ ├── FileNotFoundException.php
│ ├── DirectoryCreationException.php
│ ├── IOException.php
│ └── ParseException.php
├── Cached
│ ├── Memory.php
│ ├── Adapter.php
│ ├── Psr6Cache.php
│ ├── ImageInfoCacheTrait.php
│ ├── PsrSimpleCache.php
│ └── DoctrineCache.php
├── AggregateFilesystemInterface.php
├── SupportsIncludeFileInterface.php
├── Handler
│ ├── ImageInterface.php
│ ├── ParsableInterface.php
│ ├── Image
│ │ ├── SvgType.php
│ │ ├── TypeInterface.php
│ │ ├── Dimensions.php
│ │ ├── Exif.php
│ │ ├── CoreType.php
│ │ ├── Type.php
│ │ └── Info.php
│ ├── JsonFile.php
│ ├── Image.php
│ ├── Directory.php
│ ├── FileInterface.php
│ ├── DirectoryInterface.php
│ ├── YamlFile.php
│ ├── File.php
│ ├── HandlerInterface.php
│ └── BaseHandler.php
├── PluginInterface.php
├── Capability
│ ├── ImageInfo.php
│ └── IncludeFile.php
├── MountPointAwareInterface.php
├── Iterator
│ ├── AppendIterator.php
│ ├── CallbackMapIterator.php
│ ├── PathFilterIterator.php
│ ├── DateRangeFilterIterator.php
│ ├── EnsureHandlerIterator.php
│ ├── FileContentFilterIterator.php
│ ├── GlobIterator.php
│ ├── SortableIterator.php
│ ├── ExcludeDirectoryFilterIterator.php
│ ├── MapIterator.php
│ └── RecursiveDirectoryIterator.php
├── MountPointAwareTrait.php
├── Adapter
│ ├── Sftp.php
│ ├── Memory.php
│ ├── Ftp.php
│ ├── Cached.php
│ ├── Local.php
│ └── S3.php
├── ConfigAwareTrait.php
├── CompositeFilesystemInterface.php
├── Json.php
├── LazyFilesystem.php
├── Plugin
│ └── PluggableTrait.php
├── FilesystemWrapperTrait.php
└── FilesystemInterface.php
├── README.md
├── .travis.yml
├── phpunit.xml.dist
└── composer.json
/tests/fixtures/css/reset.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/fixtures2/empty.jpg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/fixtures/css/old/old_style.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /test/temp
2 | /vendor
3 | composer.lock
4 |
--------------------------------------------------------------------------------
/tests/fixtures/base.css:
--------------------------------------------------------------------------------
1 | .koala {
2 | color: grey;
3 | width: 100%
4 | }
5 |
--------------------------------------------------------------------------------
/tests/fixtures/css/style.css:
--------------------------------------------------------------------------------
1 | .koala {
2 | color: white;
3 | side: small;
4 | }
5 |
--------------------------------------------------------------------------------
/tests/fixtures/js/script.js:
--------------------------------------------------------------------------------
1 | var BoltFilesystem = {
2 | console.debug('DROP BEAR ALERT!');
3 | };
4 |
--------------------------------------------------------------------------------
/tests/fixtures/images/1-top-left.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/filesystem/HEAD/tests/fixtures/images/1-top-left.jpg
--------------------------------------------------------------------------------
/tests/fixtures/images/5-left-top.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/filesystem/HEAD/tests/fixtures/images/5-left-top.jpg
--------------------------------------------------------------------------------
/tests/fixtures/images/2-top-right.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/filesystem/HEAD/tests/fixtures/images/2-top-right.jpg
--------------------------------------------------------------------------------
/tests/fixtures/images/6-right-top.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/filesystem/HEAD/tests/fixtures/images/6-right-top.jpg
--------------------------------------------------------------------------------
/tests/fixtures/images/3-bottom-right.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/filesystem/HEAD/tests/fixtures/images/3-bottom-right.jpg
--------------------------------------------------------------------------------
/tests/fixtures/images/4-bottom-left.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/filesystem/HEAD/tests/fixtures/images/4-bottom-left.jpg
--------------------------------------------------------------------------------
/tests/fixtures/images/7-right-bottom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/filesystem/HEAD/tests/fixtures/images/7-right-bottom.jpg
--------------------------------------------------------------------------------
/tests/fixtures/images/8-left-bottom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bolt/filesystem/HEAD/tests/fixtures/images/8-left-bottom.jpg
--------------------------------------------------------------------------------
/src/Exception/NotSupportedException.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class IncludeFileException extends IOException
11 | {
12 | }
13 |
--------------------------------------------------------------------------------
/src/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface ExceptionInterface
11 | {
12 | }
13 |
--------------------------------------------------------------------------------
/src/Handler/ImageInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface ImageInterface extends FileInterface
11 | {
12 | /**
13 | * Returns the info for this image.
14 | *
15 | * @return Image\Info
16 | */
17 | public function getInfo();
18 | }
19 |
--------------------------------------------------------------------------------
/src/PluginInterface.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | interface ImageInfo
14 | {
15 | /**
16 | * Return the info for an image.
17 | *
18 | * @param string $path The path to the image.
19 | *
20 | * @throws IOException
21 | *
22 | * @return Image\Info
23 | */
24 | public function getImageInfo($path);
25 | }
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | sudo: false
4 | dist: trusty
5 |
6 | php:
7 | - 5.5
8 | - 5.6
9 | - 7.0
10 | - 7.1
11 | - 7.2
12 | - hhvm
13 |
14 | matrix:
15 | fast_finish: true
16 | allow_failures:
17 | - php: hhvm
18 |
19 | before_install:
20 |
21 | before_script:
22 | # Set up Composer
23 | - composer self-update || true
24 | - composer install --prefer-dist
25 |
26 | script:
27 | # PHPUnit
28 | - vendor/bin/phpunit
29 |
30 | after_script:
31 |
32 | # Cache vendor dirs
33 | cache:
34 | directories:
35 | - vendor
36 | - $COMPOSER_CACHE_DIR
37 |
38 |
--------------------------------------------------------------------------------
/src/MountPointAwareInterface.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | interface MountPointAwareInterface
9 | {
10 | /**
11 | * Returns the aggregate filesystem's mount point.
12 | *
13 | * @return string|null
14 | */
15 | public function getMountPoint();
16 |
17 | /**
18 | * WARNING: Do not call this unless you know what you are doing.
19 | *
20 | * @param string $mountPoint
21 | *
22 | * @internal
23 | */
24 | public function setMountPoint($mountPoint);
25 | }
26 |
--------------------------------------------------------------------------------
/src/Exception/DirectoryCreationException.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class DirectoryCreationException extends IOException
11 | {
12 | /**
13 | * Constructor.
14 | *
15 | * @param string $path
16 | * @param \Exception $previous
17 | */
18 | public function __construct($path, \Exception $previous = null)
19 | {
20 | parent::__construct('Failed to create directory: ' . $path, $path, 0, $previous);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Iterator/AppendIterator.php:
--------------------------------------------------------------------------------
1 | getArrayIterator()->append($iterator);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 | tests
13 |
14 |
15 |
16 |
17 | src
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Handler/ParsableInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface ParsableInterface
11 | {
12 | /**
13 | * Read and parse the file's contents.
14 | *
15 | * @param array $options
16 | *
17 | * @return mixed
18 | */
19 | public function parse($options = []);
20 |
21 | /**
22 | * Dump the data to the file.
23 | *
24 | * @param mixed $contents
25 | * @param array $options
26 | */
27 | public function dump($contents, $options = []);
28 | }
29 |
--------------------------------------------------------------------------------
/src/Capability/IncludeFile.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface IncludeFile
13 | {
14 | /**
15 | * Load a PHP file.
16 | *
17 | * @param string $path The file to include.
18 | * @param bool $once Whether to include the file only once.
19 | *
20 | * @throws IncludeFileException On failure.
21 | *
22 | * @return mixed Returns the return from the file or true if $once is true and this is a subsequent call.
23 | */
24 | public function includeFile($path, $once = true);
25 | }
26 |
--------------------------------------------------------------------------------
/src/MountPointAwareTrait.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | trait MountPointAwareTrait
9 | {
10 | /** @var string|null */
11 | protected $mountPoint;
12 |
13 | /**
14 | * Returns the aggregate filesystem's mount point.
15 | *
16 | * @return string|null
17 | */
18 | public function getMountPoint()
19 | {
20 | return $this->mountPoint;
21 | }
22 |
23 | /**
24 | * WARNING: Do not call this unless you know what you are doing.
25 | *
26 | * @param string $mountPoint
27 | */
28 | public function setMountPoint($mountPoint)
29 | {
30 | $this->mountPoint = $mountPoint;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Adapter/Sftp.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class Sftp extends SftpAdapter
14 | {
15 | /**
16 | * @inheritdoc
17 | */
18 | public function createDir($dirname, Config $config)
19 | {
20 | if ($this->has($dirname)) {
21 | return true;
22 | }
23 |
24 | $connection = $this->getConnection();
25 | if (!$connection->mkdir($dirname, $this->directoryPerm, true)) {
26 | return false;
27 | }
28 | // \phpseclib\Net\SFTP::mkdir() v2 fails to apply the correct
29 | // permissions on mkdir() when a umask of 022 is set, but chmod() still
30 | // works.
31 | $connection->chmod($this->directoryPerm, $dirname, true);
32 |
33 | return ['path' => $dirname];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Exception/IOException.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class IOException extends RuntimeException
11 | {
12 | /** @var string|null */
13 | private $path;
14 |
15 | /**
16 | * Constructor.
17 | *
18 | * @param string $message
19 | * @param string|null $path
20 | * @param int $code
21 | * @param \Exception|null $previous
22 | */
23 | public function __construct($message, $path = null, $code = 0, \Exception $previous = null)
24 | {
25 | $this->path = $path;
26 | parent::__construct($message, $code, $previous);
27 | }
28 |
29 | /**
30 | * Returns the associated path for the exception.
31 | *
32 | * @return string|null
33 | */
34 | public function getPath()
35 | {
36 | return $this->path;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Handler/Image/SvgType.php:
--------------------------------------------------------------------------------
1 | toString();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/FilesystemTestCase.php:
--------------------------------------------------------------------------------
1 | rootDir = __DIR__ . '/..';
22 | $this->tempDir = $this->rootDir . '/tests/temp';
23 | }
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | protected function tearDown()
29 | {
30 | parent::tearDown();
31 |
32 | $this->removeDirectory($this->tempDir);
33 | }
34 |
35 | protected function removeDirectory($dir)
36 | {
37 | if (!file_exists($dir)) {
38 | return;
39 | }
40 |
41 | $fs = new Symfony\Filesystem();
42 | $fs->remove($dir);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Iterator/CallbackMapIterator.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class CallbackMapIterator extends MapIterator
13 | {
14 | /** @var callable */
15 | protected $callback;
16 |
17 | /**
18 | * Constructor.
19 | *
20 | * @param array|Traversable $iterable The object to iterate.
21 | * @param callable $callback A callback which takes ($value, &$key) and returns the new value.
22 | */
23 | public function __construct($iterable, callable $callback)
24 | {
25 | parent::__construct($iterable);
26 | $this->callback = $callback;
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | protected function map($value, &$key)
33 | {
34 | $callback = $this->callback;
35 | // Don't use call_user_func, as the key won't be passed by reference
36 | return $callback($value, $key);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Handler/Image/TypeInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface TypeInterface
11 | {
12 | /**
13 | * Returns the ID. (probably IMAGETYPE_* constant).
14 | *
15 | * @return int
16 | */
17 | public function getId();
18 |
19 | /**
20 | * Returns the MIME Type associated with this type.
21 | *
22 | * @return string
23 | */
24 | public function getMimeType();
25 |
26 | /**
27 | * Returns the file extension for this type.
28 | *
29 | * @param bool $includeDot Whether to prepend a dot to the extension or not
30 | *
31 | * @return string
32 | */
33 | public function getExtension($includeDot = true);
34 |
35 | /**
36 | * Returns the name of this type.
37 | *
38 | * @return string
39 | */
40 | public function toString();
41 |
42 | /**
43 | * Returns the name of this type.
44 | */
45 | public function __toString();
46 | }
47 |
--------------------------------------------------------------------------------
/src/ConfigAwareTrait.php:
--------------------------------------------------------------------------------
1 | config = $config ? Util::ensureConfig($config) : new Config;
26 | }
27 |
28 | /**
29 | * Get the Config.
30 | *
31 | * @return Config config object
32 | */
33 | public function getConfig()
34 | {
35 | return $this->config;
36 | }
37 |
38 | /**
39 | * Convert a config array to a Config object with the correct fallback.
40 | *
41 | * @param array $config
42 | *
43 | * @return Config
44 | */
45 | protected function prepareConfig(array $config)
46 | {
47 | $config = new Config($config);
48 | $config->setFallback($this->getConfig());
49 |
50 | return $config;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/CompositeFilesystemInterface.php:
--------------------------------------------------------------------------------
1 | Filesystem]
13 | */
14 | public function mountFilesystems(array $filesystems);
15 |
16 | /**
17 | * Mount a filesystem.
18 | *
19 | * @param string $mountPoint
20 | * @param FilesystemInterface $filesystem
21 | */
22 | public function mountFilesystem($mountPoint, FilesystemInterface $filesystem);
23 |
24 | /**
25 | * Get the filesystem at the given mount point.
26 | *
27 | * @param string $mountPoint
28 | *
29 | * @throws LogicException If the filesystem does not exist.
30 | *
31 | * @return FilesystemInterface
32 | */
33 | public function getFilesystem($mountPoint);
34 |
35 | /**
36 | * Check if the filesystem at the given mount point exists.
37 | *
38 | * @param string $mountPoint
39 | *
40 | * @return bool
41 | */
42 | public function hasFilesystem($mountPoint);
43 | }
44 |
--------------------------------------------------------------------------------
/src/Iterator/PathFilterIterator.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class PathFilterIterator extends PathFilterIteratorBase
14 | {
15 | /**
16 | * {@inheritdoc}
17 | */
18 | public function accept()
19 | {
20 | /** @var File $file */
21 | $file = $this->current();
22 |
23 | $filename = $file->getPath();
24 |
25 | // should at least not match one rule to exclude
26 | foreach ($this->noMatchRegexps as $regex) {
27 | if (preg_match($regex, $filename)) {
28 | return false;
29 | }
30 | }
31 |
32 | // should at least match one rule
33 | $match = true;
34 | if ($this->matchRegexps) {
35 | $match = false;
36 | foreach ($this->matchRegexps as $regex) {
37 | if (preg_match($regex, $filename)) {
38 | return true;
39 | }
40 | }
41 | }
42 |
43 | return $match;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Handler/Image/ExifTest.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class ExifTest extends TestCase
15 | {
16 | public function testConstruct()
17 | {
18 | $exif = new Exif([]);
19 | $this->assertInstanceOf('Bolt\Filesystem\Handler\Image\Exif', $exif);
20 | }
21 |
22 | public function testCast()
23 | {
24 | $exif = new Exif([]);
25 | $this->assertInstanceOf('Bolt\Filesystem\Handler\Image\Exif', $exif->cast(new PHPExif\Exif([])));
26 | }
27 |
28 | public function testInvalidGps()
29 | {
30 | $exif = new Exif([]);
31 | $this->assertFalse($exif->getLatitude());
32 | }
33 |
34 | public function testGetLatitude()
35 | {
36 | $exif = new Exif([Exif::GPS => '35.25513,149.1093073']);
37 | $this->assertSame(35.25513, $exif->getLatitude());
38 | }
39 |
40 | public function testGetLongitude()
41 | {
42 | $exif = new Exif([Exif::GPS => '35.25513,149.1093073']);
43 | $this->assertSame(149.1093073, $exif->getLongitude());
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Cached/ImageInfoCacheTrait.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | trait ImageInfoCacheTrait
13 | {
14 | /**
15 | * @param string $path
16 | *
17 | * @return Image\Info|array|false
18 | */
19 | public function getImageInfo($path)
20 | {
21 | if (isset($this->cache[$path]['image_info'])) {
22 | return $this->cache[$path]['image_info'];
23 | }
24 |
25 | return false;
26 | }
27 |
28 | public function cleanContents(array $contents)
29 | {
30 | $cachedProperties = array_flip($this->getPersistedProperties());
31 |
32 | foreach ($contents as $path => $object) {
33 | if (is_array($object)) {
34 | $contents[$path] = array_intersect_key($object, $cachedProperties);
35 | }
36 | }
37 |
38 | return $contents;
39 | }
40 |
41 | protected function getPersistedProperties()
42 | {
43 | return [
44 | 'path', 'dirname', 'basename', 'extension', 'filename',
45 | 'size', 'mimetype', 'visibility', 'timestamp', 'type',
46 | 'image_info',
47 | ];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Adapter/Memory.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class Memory extends MemoryAdapter implements Capability\IncludeFile
16 | {
17 | private $includedFiles = [];
18 |
19 | /**
20 | * {@inheritdoc}
21 | */
22 | public function includeFile($path, $once = true)
23 | {
24 | if ($once && isset($this->includedFiles[$path])) {
25 | return true;
26 | }
27 |
28 | $contents = $this->read($path)['contents'];
29 | try {
30 | $contents = Thrower::call(__NAMESPACE__ . '\evalContents', $contents);
31 | } catch (\Exception $e) {
32 | throw new IncludeFileException($e->getMessage(), $path, 0, $e);
33 | }
34 |
35 | $this->includedFiles[$path] = true;
36 |
37 | return $contents;
38 | }
39 | }
40 |
41 | /**
42 | * Scope isolated include.
43 | *
44 | * Prevents access to $this/self from included files.
45 | *
46 | * @param string $__data
47 | *
48 | * @return mixed
49 | */
50 | function evalContents($__data)
51 | {
52 | return eval('?>' . $__data);
53 | }
54 |
--------------------------------------------------------------------------------
/src/Cached/PsrSimpleCache.php:
--------------------------------------------------------------------------------
1 | cache = $cache;
30 | $this->key = $key;
31 | $this->expire = $expire;
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function save()
38 | {
39 | $this->cache->set($this->key, $this->getForStorage(), $this->expire);
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function load()
46 | {
47 | if ($value = $this->cache->get($this->key)) {
48 | $this->setFromStorage($value);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Cached/DoctrineCache.php:
--------------------------------------------------------------------------------
1 | doctrine = $cache;
30 | $this->key = $key;
31 | $this->lifeTime = $lifeTime;
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function save()
38 | {
39 | $contents = $this->getForStorage();
40 | $this->doctrine->save($this->key, $contents, $this->lifeTime);
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function load()
47 | {
48 | $contents = $this->doctrine->fetch($this->key);
49 | if ($contents !== false) {
50 | $this->setFromStorage($contents);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Iterator/DateRangeFilterIterator.php:
--------------------------------------------------------------------------------
1 |
13 | * @author Carson Full
14 | */
15 | class DateRangeFilterIterator extends \FilterIterator
16 | {
17 | private $comparators = [];
18 |
19 | /**
20 | * Constructor.
21 | *
22 | * @param \Iterator $iterator The Iterator to filter
23 | * @param DateComparator[] $comparators An array of DateComparator instances
24 | */
25 | public function __construct(\Iterator $iterator, array $comparators)
26 | {
27 | $this->comparators = $comparators;
28 |
29 | parent::__construct($iterator);
30 | }
31 |
32 | public function accept()
33 | {
34 | /** @var Directory|File $handler */
35 | $handler = $this->current();
36 |
37 | if (!$handler->exists()) {
38 | return false;
39 | }
40 |
41 | $timestamp = $handler->getTimestamp();
42 | foreach ($this->comparators as $compare) {
43 | if (!$compare->test($timestamp)) {
44 | return false;
45 | }
46 | }
47 |
48 | return true;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Handler/JsonFile.php:
--------------------------------------------------------------------------------
1 |
13 | * @author Carson Full
14 | */
15 | class JsonFile extends File implements ParsableInterface
16 | {
17 | /**
18 | * {@inheritdoc}
19 | */
20 | public function parse($options = [])
21 | {
22 | $options += [
23 | 'depth' => 512,
24 | 'flags' => 0,
25 | ];
26 |
27 | $contents = $this->read();
28 |
29 | try {
30 | return Json::parse($contents, $options['flags'], $options['depth']);
31 | } catch (\Bolt\Common\Exception\ParseException $e) {
32 | throw new ParseException($e->getRawMessage(), $e->getParsedLine(), $e->getSnippet(), $e);
33 | }
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function dump($contents, $options = [])
40 | {
41 | $options += [
42 | 'flags' => 448,
43 | ];
44 |
45 | try {
46 | $content = Json::dump($contents, $options['flags']);
47 | } catch (\Bolt\Common\Exception\DumpException $e) {
48 | throw new DumpException($e->getMessage(), $e->getCode(), $e);
49 | }
50 |
51 | $this->put($content);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Iterator/EnsureHandlerIterator.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class EnsureHandlerIterator extends MapIterator
17 | {
18 | /** @var FilesystemInterface */
19 | private $filesystem;
20 |
21 | /**
22 | * Constructor.
23 | *
24 | * @param FilesystemInterface $filesystem
25 | * @param array|Traversable $iterable
26 | */
27 | public function __construct(FilesystemInterface $filesystem, $iterable)
28 | {
29 | parent::__construct($iterable);
30 | $this->filesystem = $filesystem;
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | protected function map($value, &$key)
37 | {
38 | if ($value instanceof HandlerInterface) {
39 | return $value;
40 | }
41 | if (is_string($value)) {
42 | return $this->filesystem->get($value);
43 | }
44 |
45 | throw new InvalidArgumentException(sprintf('Iterators or arrays given to Finder::append() must give path strings or %s objects.', HandlerInterface::class));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/Iterator/CallbackMapIteratorTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(ArrayIterator::class, $it->getInnerIterator());
24 |
25 | $expected = [
26 | 'foo' => 'foo.',
27 | 'bar' => 'bar.',
28 | ];
29 | $this->assertEquals($expected, $it->toArray());
30 | $this->assertEquals($expected, $it->toArray(), 'Should be able to be iterated multiple times');
31 | }
32 |
33 | public function testIterator()
34 | {
35 | $input = new ArrayIterator([
36 | 'foo',
37 | 'bar',
38 | ]);
39 | $it = new CallbackMapIterator($input, function ($item) {
40 | return $item . '.';
41 | });
42 |
43 | $this->assertEquals(['foo.', 'bar.'], $it->toArray());
44 | }
45 |
46 | /**
47 | * @expectedException \InvalidArgumentException
48 | */
49 | public function testNonIterable()
50 | {
51 | new CallbackMapIterator(new \stdClass(), 'var_dump');
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Iterator/FileContentFilterIterator.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class FileContentFilterIterator extends FilecontentFilterIteratorBase
15 | {
16 | /**
17 | * {@inheritdoc}
18 | */
19 | public function accept()
20 | {
21 | if (!$this->matchRegexps && !$this->noMatchRegexps) {
22 | return true;
23 | }
24 |
25 | /** @var File $handler */
26 | $handler = $this->current();
27 |
28 | if (!$handler->isFile()) {
29 | return false;
30 | }
31 |
32 | try {
33 | $content = $handler->read();
34 | } catch (IOException $e) {
35 | return false;
36 | }
37 |
38 | // should at least not matach one rule to exclude
39 | foreach ($this->noMatchRegexps as $regex) {
40 | if (preg_match($regex, $content)) {
41 | return false;
42 | }
43 | }
44 |
45 | // should at least match one rule
46 | $match = true;
47 | if ($this->matchRegexps) {
48 | $match = false;
49 | foreach ($this->matchRegexps as $regex) {
50 | if (preg_match($regex, $content)) {
51 | return true;
52 | }
53 | }
54 | }
55 |
56 | return $match;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Adapter/Ftp.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | class Ftp extends FtpAdapter
14 | {
15 | /** @var int */
16 | protected $directoryPerm = 0744;
17 |
18 | /**
19 | * @inheritdoc
20 | */
21 | public function __construct(array $config)
22 | {
23 | $this->configurable[] = 'directoryPerm';
24 | parent::__construct($config);
25 | }
26 |
27 | /**
28 | * @return int
29 | */
30 | public function getDirectoryPerm()
31 | {
32 | return $this->directoryPerm;
33 | }
34 |
35 | /**
36 | * @param int $directoryPerm
37 | *
38 | * @return Ftp
39 | */
40 | public function setDirectoryPerm($directoryPerm)
41 | {
42 | $this->directoryPerm = $directoryPerm;
43 |
44 | return $this;
45 | }
46 |
47 | /**
48 | * @inheritdoc
49 | */
50 | public function createDir($dirname, Config $config)
51 | {
52 | if ($this->has($dirname)) {
53 | return true;
54 | }
55 |
56 | return parent::createDir($dirname, $config);
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | protected function createActualDirectory($directory, $connection)
63 | {
64 | $result = parent::createActualDirectory($directory, $connection);
65 | if ($result) {
66 | ftp_chmod($connection, $this->directoryPerm, $directory);
67 | }
68 |
69 | return $result;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Handler/Image.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class Image extends File implements ImageInterface
13 | {
14 | /** @var Image\Info */
15 | protected $info;
16 |
17 | /**
18 | * {@inheritdoc}
19 | */
20 | public function getInfo($cache = true)
21 | {
22 | if (!$cache) {
23 | $this->info = null;
24 | }
25 | if (!$this->info) {
26 | $this->info = $this->filesystem->getImageInfo($this->path);
27 | }
28 |
29 | return $this->info;
30 | }
31 |
32 | /**
33 | * @inheritdoc
34 | *
35 | * Use MIME Type from Info as it has handles SVG detection better.
36 | */
37 | public function getMimeType()
38 | {
39 | return $this->getInfo()->getMime();
40 | }
41 |
42 | /**
43 | * Pass-through to plugins, then Image\Info. This is for BC.
44 | *
45 | * @param string $method
46 | * @param array $arguments
47 | *
48 | * @return mixed
49 | */
50 | public function __call($method, array $arguments)
51 | {
52 | try {
53 | return parent::__call($method, $arguments);
54 | } catch (BadMethodCallException $e) {
55 | }
56 |
57 | $info = $this->getInfo();
58 | if (method_exists($info, 'get' . $method)) {
59 | return call_user_func([$info, 'get' . $method]);
60 | } elseif (method_exists($info, 'is' . $method)) {
61 | return call_user_func([$info, 'is' . $method]);
62 | }
63 |
64 | throw $e;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bolt/filesystem",
3 | "description": "Bolt's filesystem abstraction layer",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Carson Full",
8 | "email": "carsonfull@gmail.com"
9 | },
10 | {
11 | "name": "Gawain Lynch",
12 | "email": "gawain.lynch@gmail.com"
13 | }
14 | ],
15 | "require": {
16 | "bolt/common": "^1.0",
17 | "ext-json": "*",
18 | "guzzlehttp/psr7": "^1.2",
19 | "league/flysystem": "^1.0",
20 | "miljar/php-exif": "^0.6",
21 | "nesbot/carbon": "^1.20",
22 | "php": "^5.5.9 || ^7.0",
23 | "webmozart/glob": "^4.0",
24 | "symfony/finder": "^2.7 || ^3.0 || ^4.0",
25 | "symfony/yaml": "^2.7 || ^3.0 || ^4.0"
26 | },
27 | "require-dev": {
28 | "contao/imagine-svg": "^0.1.2",
29 | "doctrine/cache": "^1.6",
30 | "league/flysystem-aws-s3-v3": "^1.0",
31 | "league/flysystem-cached-adapter": "^1.0",
32 | "league/flysystem-memory": "^1.0",
33 | "league/flysystem-sftp": "^1.0",
34 | "phpunit/phpunit": "^4.8.36 || ^5.0 || ^6.0",
35 | "psr/simple-cache": "^1.0",
36 | "symfony/filesystem": "^2.7 || ^3.0 || ^4.0"
37 | },
38 | "suggest": {
39 | "contao/imagine-svg": "To parse SVG image info"
40 | },
41 | "autoload": {
42 | "psr-4": {
43 | "Bolt\\Filesystem\\": "src"
44 | }
45 | },
46 | "autoload-dev": {
47 | "psr-4": {
48 | "Bolt\\Filesystem\\Tests\\": "tests"
49 | }
50 | },
51 | "extra": {
52 | "branch-alias": {
53 | "dev-master": "2.x-dev"
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Iterator/AppendIteratorTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Bug does not exist on HHVM');
19 | }
20 |
21 | $this->assertSequence(new \AppendIterator(), '..123.456');
22 | }
23 |
24 | /**
25 | * This asserts that our AppendIterator fixes it.
26 | */
27 | public function testFix()
28 | {
29 | $this->assertSequence(new AppendIterator(), '.123.456');
30 | }
31 |
32 | private function assertSequence(\AppendIterator $it, $sequence)
33 | {
34 | $str = new TestStr();
35 | $i1 = new TestArrayIterator([1, 2, 3], $str);
36 | $i2 = new TestArrayIterator([4, 5, 6], $str);
37 |
38 | $it->append($i1);
39 | $it->append($i2);
40 |
41 | foreach ($it as $item) {
42 | $str->str .= $item;
43 | }
44 | $this->assertSame($sequence, $str->str);
45 | }
46 | }
47 |
48 | class TestArrayIterator extends ArrayIterator
49 | {
50 | private $str;
51 |
52 | public function __construct(array $array, TestStr $str)
53 | {
54 | parent::__construct($array);
55 | $this->str = $str;
56 | }
57 |
58 | public function rewind()
59 | {
60 | $this->str->str .= '.';
61 | parent::rewind();
62 | }
63 | }
64 |
65 | class TestStr
66 | {
67 | public $str = '';
68 | }
69 |
--------------------------------------------------------------------------------
/src/Iterator/GlobIterator.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class GlobIterator extends GlobFilterIterator
16 | {
17 | /**
18 | * Constructor.
19 | *
20 | * @param FilesystemInterface $filesystem The filesystem to search
21 | * @param string $glob The glob pattern
22 | * @param int $flags A bitwise combination of the flag constants in {@see Glob}
23 | */
24 | public function __construct(FilesystemInterface $filesystem, $glob, $flags = 0)
25 | {
26 | // Glob code requires absolute paths, so prefix path
27 | // with leading slash, but not before mount point
28 | if (strpos($glob, '://') > 0) {
29 | $glob = str_replace('://', ':///', $glob);
30 | } else {
31 | $glob = '/' . ltrim($glob, '/');
32 | }
33 |
34 | if (!Glob::isDynamic($glob)) {
35 | // If the glob is a file path, return that path.
36 | $innerIterator = new \ArrayIterator([$glob => $filesystem->get($glob)]);
37 | } else {
38 | $basePath = Glob::getBasePath($glob);
39 |
40 | $innerIterator = new RecursiveIteratorIterator(
41 | new RecursiveDirectoryIterator(
42 | $filesystem,
43 | $basePath,
44 | RecursiveDirectoryIterator::KEY_FOR_GLOB
45 | ),
46 | RecursiveIteratorIterator::SELF_FIRST
47 | );
48 | }
49 |
50 | parent::__construct($glob, $innerIterator, static::FILTER_KEY, $flags);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Iterator/IteratorTestCase.php:
--------------------------------------------------------------------------------
1 | getPath();
16 | },
17 | iterator_to_array($iterator)
18 | );
19 |
20 | sort($values);
21 | sort($expected);
22 |
23 | $this->assertEquals($expected, array_values($values));
24 |
25 | $this->assertIteratorInForeach($expected, $iterator);
26 | }
27 |
28 | protected function assertOrderedIterator($expected, Traversable $iterator)
29 | {
30 | $values = array_map(
31 | function (HandlerInterface $handler) {
32 | return $handler->getPath();
33 | },
34 | iterator_to_array($iterator)
35 | );
36 |
37 | $this->assertEquals($expected, array_values($values));
38 | }
39 |
40 | /**
41 | * Same as IteratorTestCase::assertIterator with foreach usage.
42 | *
43 | * @param array $expected
44 | * @param Traversable $iterator
45 | */
46 | protected function assertIteratorInForeach($expected, Traversable $iterator)
47 | {
48 | $values = [];
49 | foreach ($iterator as $handler) {
50 | /** @var HandlerInterface $handler */
51 | $this->assertInstanceOf(HandlerInterface::class, $handler);
52 | $values[] = $handler->getPath();
53 | }
54 |
55 | sort($values);
56 | sort($expected);
57 |
58 | $this->assertEquals($expected, array_values($values));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/Handler/DirectoryTest.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class DirectoryTest extends FilesystemTestCase
19 | {
20 | /** @var FilesystemInterface */
21 | protected $filesystem;
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | protected function setUp()
27 | {
28 | $this->filesystem = new Filesystem(new Local(__DIR__ . '/../'));
29 | }
30 |
31 | public function testConstruct()
32 | {
33 | $dir = new Directory($this->filesystem);
34 | $this->assertInstanceOf(Directory::class, $dir);
35 |
36 | $filesystem = new Filesystem(new Local(__DIR__));
37 | $dir = new Directory($filesystem);
38 | $this->assertInstanceOf(Directory::class, $dir);
39 | }
40 |
41 | public function testSetFilesystem()
42 | {
43 | $dir = new Directory($this->filesystem);
44 | $filesystem = new Filesystem(new Local(__DIR__));
45 | $dir->setFilesystem($filesystem);
46 | $this->assertInstanceOf(Filesystem::class, $dir->getFilesystem());
47 | }
48 |
49 | public function testGet()
50 | {
51 | $dir = new Directory($this->filesystem);
52 | $this->assertInstanceOf(File::class, $dir->get('fixtures/base.css'));
53 | }
54 |
55 | public function testGetContents()
56 | {
57 | $dir = new Directory($this->filesystem);
58 | $content = $dir->getContents();
59 | $this->assertInstanceOf(HandlerInterface::class, $content[0]);
60 | }
61 |
62 | public function testExists()
63 | {
64 | $dir = new Directory($this->filesystem);
65 | $this->assertTrue($dir->exists());
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Exception/ParseException.php:
--------------------------------------------------------------------------------
1 | getCode() : 0, $previous);
20 | }
21 |
22 | /**
23 | * Casts Symfony's Yaml ParseException to ours.
24 | *
25 | * @param YamlParseException $exception
26 | *
27 | * @return ParseException
28 | */
29 | public static function castFromYaml(YamlParseException $exception)
30 | {
31 | $message = static::parseRawMessage($exception->getMessage());
32 |
33 | return new static($message, $exception->getParsedLine(), $exception->getSnippet(), $exception);
34 | }
35 |
36 | /**
37 | * Parse the raw message from Symfony's Yaml ParseException
38 | *
39 | * @param string $message
40 | *
41 | * @return string
42 | */
43 | private static function parseRawMessage($message)
44 | {
45 | $dot = false;
46 | if (substr($message, -1) === '.') {
47 | $message = substr($message, 0, -1);
48 | $dot = true;
49 | }
50 |
51 | if (($pos = strpos($message, ' at line')) > 0) {
52 | $message = substr($message, 0, $pos);
53 | } elseif (($pos = strpos($message, ' (near')) > 0) {
54 | $message = substr($message, 0, $pos);
55 | }
56 |
57 | if ($dot) {
58 | $message .= '.';
59 | }
60 |
61 | return $message;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Json.php:
--------------------------------------------------------------------------------
1 | getMessage(), $e->getCode(), $e);
37 | }
38 | }
39 |
40 | /**
41 | * Dumps a array/object into a JSON string.
42 | *
43 | * @param mixed $data Data to encode into a formatted JSON string
44 | * @param int $options json_encode options
45 | * (defaults to JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
46 | *
47 | * @throws DumpException If dumping fails
48 | *
49 | * @return string
50 | *
51 | * @deprecated since 2.4 and will be removed in 3.0. Use {@see \Bolt\Common\Json::dump} instead.
52 | */
53 | public static function dump($data, $options = 448)
54 | {
55 | Deprecated::method(2.4, \Bolt\Common\Json::class . '::dump');
56 |
57 | try {
58 | return \Bolt\Common\Json::dump($data, $options);
59 | } catch (\Bolt\Common\Exception\ParseException $e) {
60 | throw new ParseException($e->getRawMessage(), $e->getParsedLine(), $e->getSnippet(), $e);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Handler/Image/Dimensions.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class Dimensions
13 | {
14 | /** @var int */
15 | protected $width;
16 | /** @var int */
17 | protected $height;
18 |
19 | /**
20 | * Constructor.
21 | *
22 | * @param int $width The width
23 | * @param int $height The height
24 | */
25 | public function __construct($width = 0, $height = 0)
26 | {
27 | $this->setWidth($width);
28 | $this->setHeight($height);
29 | }
30 |
31 | /**
32 | * Returns the width.
33 | *
34 | * @return int
35 | */
36 | public function getWidth()
37 | {
38 | return $this->width;
39 | }
40 |
41 | /**
42 | * Sets the width.
43 | *
44 | * @param int $width
45 | *
46 | * @return Dimensions
47 | */
48 | public function setWidth($width)
49 | {
50 | $this->verify($width);
51 | $this->width = (int) $width;
52 |
53 | return $this;
54 | }
55 |
56 | /**
57 | * Returns the height.
58 | *
59 | * @return int
60 | */
61 | public function getHeight()
62 | {
63 | return $this->height;
64 | }
65 |
66 | /**
67 | * Sets the height.
68 | *
69 | * @param int $height
70 | *
71 | * @return Dimensions
72 | */
73 | public function setHeight($height)
74 | {
75 | $this->verify($height);
76 | $this->height = (int) $height;
77 |
78 | return $this;
79 | }
80 |
81 | /**
82 | * @inheritDoc
83 | */
84 | public function __toString()
85 | {
86 | return $this->width . ' × ' . $this->height . ' px';
87 | }
88 |
89 | /**
90 | * Verifies that the dimension is valid.
91 | *
92 | * @param int|mixed $point
93 | */
94 | protected function verify($point)
95 | {
96 | if (!is_numeric($point)) {
97 | throw new InvalidArgumentException('Dimensions point is expected to be numeric');
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/tests/fixtures/images/nut.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/src/Handler/Image/Exif.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class Exif extends PHPExif\Exif
13 | {
14 | /**
15 | * Casts Exif to this sub-class.
16 | *
17 | * @param PHPExif\Exif $exif
18 | *
19 | * @return Exif
20 | */
21 | public static function cast(PHPExif\Exif $exif)
22 | {
23 | $new = new static($exif->getData());
24 | $new->setRawData($exif->getRawData());
25 |
26 | return $new;
27 | }
28 |
29 | /**
30 | * Returns the aspect ratio.
31 | *
32 | * @return float
33 | */
34 | public function getAspectRatio()
35 | {
36 | if ($this->getWidth() == 0 || $this->getHeight() == 0) {
37 | return 0.0;
38 | }
39 |
40 | // Account for image rotation
41 | if (in_array($this->getOrientation(), [5, 6, 7, 8])) {
42 | return $this->getHeight() / $this->getWidth();
43 | }
44 |
45 | return $this->getWidth() / $this->getHeight();
46 | }
47 |
48 | /**
49 | * Returns the latitude from the GPS data, if it exists.
50 | *
51 | * @return bool|float
52 | */
53 | public function getLatitude()
54 | {
55 | return $this->getGpsPart(0);
56 | }
57 |
58 | /**
59 | * Returns the longitude from the GPS data, if it exists.
60 | *
61 | * @return bool|float
62 | */
63 | public function getLongitude()
64 | {
65 | return $this->getGpsPart(1);
66 | }
67 |
68 | /**
69 | * @param $index
70 | *
71 | * @return bool|float
72 | */
73 | private function getGpsPart($index)
74 | {
75 | $gps = $this->getGPS();
76 | if ($gps === false) {
77 | return false;
78 | }
79 |
80 | $parts = explode(',', $gps);
81 | if (!isset($parts[$index])) {
82 | return false;
83 | }
84 |
85 | return (float) $parts[$index];
86 | }
87 |
88 | /**
89 | * Returns the creation datetime, if it exists.
90 | *
91 | * @deprecated Use {@see Exif::getCreationDate} instead.
92 | *
93 | * @return bool|\DateTime
94 | */
95 | public function getDateTime()
96 | {
97 | return $this->getCreationDate();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Handler/Directory.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class Directory extends BaseHandler implements DirectoryInterface
11 | {
12 | /**
13 | * {@inheritdoc}
14 | */
15 | public function isRoot()
16 | {
17 | return $this->path === '';
18 | }
19 |
20 | /**
21 | * {@inheritdoc}
22 | */
23 | public function create($config = [])
24 | {
25 | $this->filesystem->createDir($this->path, $config);
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | public function delete()
32 | {
33 | $this->filesystem->deleteDir($this->path);
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function copy($target, $override = null)
40 | {
41 | $this->filesystem->copyDir($this->path, $target, $override);
42 |
43 | return new static($this->filesystem, $target);
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public function mirror($target, $config = [])
50 | {
51 | $this->filesystem->mirror($this->path, $target, $config);
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | public function get($path, HandlerInterface $handler = null)
58 | {
59 | return $this->filesystem->get($this->path . '/' . $path, $handler);
60 | }
61 |
62 | /**
63 | * {@inheritdoc}
64 | */
65 | public function getFile($path, FileInterface $handler = null)
66 | {
67 | return $this->filesystem->getFile($this->path . '/' . $path, $handler);
68 | }
69 |
70 | /**
71 | * {@inheritdoc}
72 | */
73 | public function getDir($path)
74 | {
75 | return $this->filesystem->getDir($this->path . '/' . $path);
76 | }
77 |
78 | /**
79 | * {@inheritdoc}
80 | */
81 | public function getImage($path)
82 | {
83 | return $this->filesystem->getImage($this->path . '/' . $path);
84 | }
85 |
86 | /**
87 | * {@inheritdoc}
88 | */
89 | public function getContents($recursive = false)
90 | {
91 | return $this->filesystem->listContents($this->path, $recursive);
92 | }
93 |
94 | /**
95 | * {@inheritdoc}
96 | */
97 | public function find()
98 | {
99 | return $this->filesystem->find()->in($this->path);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Iterator/SortableIterator.php:
--------------------------------------------------------------------------------
1 |
14 | * @author Carson Full
15 | */
16 | class SortableIterator implements \IteratorAggregate
17 | {
18 | const SORT_BY_NAME = 1;
19 | const SORT_BY_TYPE = 2;
20 | const SORT_BY_TIME = 3;
21 |
22 | private $iterator;
23 | private $sort;
24 |
25 | /**
26 | * Constructor.
27 | *
28 | * @param \Traversable $iterator The Iterator to filter
29 | * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
30 | *
31 | * @throws InvalidArgumentException
32 | */
33 | public function __construct(\Traversable $iterator, $sort)
34 | {
35 | $this->iterator = $iterator;
36 |
37 | if (self::SORT_BY_NAME === $sort) {
38 | $this->sort = function (HandlerInterface $a, HandlerInterface $b) {
39 | return strcmp($a->getPath(), $b->getPath());
40 | };
41 | } elseif (self::SORT_BY_TYPE === $sort) {
42 | $this->sort = function (HandlerInterface $a, HandlerInterface $b) {
43 | if ($a->isDir() && $b->isFile()) {
44 | return -1;
45 | } elseif ($a->isFile() && $b->isDir()) {
46 | return 1;
47 | }
48 |
49 | return strcmp($a->getPath(), $b->getPath());
50 | };
51 | } elseif (self::SORT_BY_TIME === $sort) {
52 | $this->sort = function ($a, $b) {
53 | /** @var File|Directory $a */
54 | /** @var File|Directory $b */
55 | return $a->getTimestamp() - $b->getTimestamp();
56 | };
57 | } elseif (is_callable($sort)) {
58 | $this->sort = $sort;
59 | } else {
60 | throw new InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
61 | }
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | public function getIterator()
68 | {
69 | $array = iterator_to_array($this->iterator, true);
70 | uasort($array, $this->sort);
71 |
72 | return new \ArrayIterator($array);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Iterator/ExcludeDirectoryFilterIterator.php:
--------------------------------------------------------------------------------
1 | iterator = $iterator;
25 | $this->isRecursive = $iterator instanceof \RecursiveIterator;
26 | $patterns = [];
27 | foreach ($directories as $directory) {
28 | if (!$this->isRecursive || false !== strpos($directory, '/')) {
29 | $patterns[] = preg_quote($directory, '#');
30 | } else {
31 | $this->excludedDirs[$directory] = true;
32 | }
33 | }
34 | if ($patterns) {
35 | $this->excludedPattern = '#(?:^|/)(?:' . implode('|', $patterns) . ')(?:/|$)#';
36 | }
37 |
38 | parent::__construct($iterator);
39 | }
40 |
41 | /**
42 | * Filters the iterator values.
43 | *
44 | * @return bool true if the value should be kept, false otherwise
45 | */
46 | public function accept()
47 | {
48 | /** @var Directory|File $handler */
49 | $handler = $this->iterator->current();
50 | if ($this->isRecursive && isset($this->excludedDirs[$handler->getFilename()]) && $handler->isDir()) {
51 | return false;
52 | }
53 |
54 | if ($this->excludedPattern) {
55 | $path = $handler->isDir() ? $handler->getPath() : $handler->getDirname();
56 | $path = str_replace('\\', '/', $path);
57 |
58 | return !preg_match($this->excludedPattern, $path);
59 | }
60 |
61 | return true;
62 | }
63 |
64 | public function hasChildren()
65 | {
66 | return $this->isRecursive && $this->iterator->hasChildren();
67 | }
68 |
69 | public function getChildren()
70 | {
71 | $children = new self($this->iterator->getChildren(), []);
72 | $children->excludedDirs = $this->excludedDirs;
73 | $children->excludedPattern = $this->excludedPattern;
74 |
75 | return $children;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Handler/Image/TypeTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class TypeTest extends TestCase
16 | {
17 | /**
18 | * @expectedException \Bolt\Filesystem\Exception\InvalidArgumentException
19 | * @expectedExceptionMessage Given type is not an IMAGETYPE_* constant
20 | */
21 | public function testGetById()
22 | {
23 | $type = Type::getById(IMAGETYPE_JPEG);
24 | $this->assertInstanceOf(TypeInterface::class, $type);
25 |
26 | $type2 = Type::getById(IMAGETYPE_JPEG);
27 | $this->assertSame($type, $type2);
28 |
29 | Type::getById(42);
30 | }
31 |
32 | public function testToId()
33 | {
34 | $type = Type::getById(IMAGETYPE_JPEG);
35 | $this->assertSame(2, $type->getId());
36 | }
37 |
38 | public function testToMimeType()
39 | {
40 | $type = Type::getById(IMAGETYPE_JPEG);
41 | $this->assertSame('image/jpeg', $type->getMimeType());
42 | }
43 |
44 | public function testToExtension()
45 | {
46 | $type = Type::getById(IMAGETYPE_JPEG);
47 | $this->assertSame('.jpeg', $type->getExtension(true));
48 | $this->assertSame('jpeg', $type->getExtension(false));
49 | }
50 |
51 | public function testToString()
52 | {
53 | $type = Type::getById(IMAGETYPE_JPEG);
54 | $this->assertSame('JPEG', $type->toString());
55 | $this->assertSame('JPEG', (string) $type);
56 | }
57 |
58 | public function testSvg()
59 | {
60 | $type = Type::getById(SvgType::ID);
61 | $this->assertEquals(101, $type->getId());
62 | $this->assertEquals('image/svg+xml', $type->getMimeType());
63 | $this->assertEquals('.svg', $type->getExtension());
64 | $this->assertEquals('svg', $type->getExtension(false));
65 | $this->assertEquals('SVG', $type->toString());
66 | $this->assertEquals('SVG', (string) $type);
67 | }
68 |
69 | public function testGetTypes()
70 | {
71 | $types = Type::getTypes();
72 | $this->assertInstanceOf(TypeInterface::class, $types[0]);
73 | }
74 |
75 | public function testGetMimeTypes()
76 | {
77 | $mimeTypes = Type::getMimeTypes();
78 | $this->assertContains('image/jpeg', $mimeTypes);
79 | }
80 |
81 | public function testGetExtensions()
82 | {
83 | $extensions = Type::getExtensions();
84 | $this->assertContains('jpeg', $extensions);
85 | $this->assertContains('jpg', $extensions);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/LazyFilesystem.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class LazyFilesystem implements FilesystemInterface, MountPointAwareInterface
13 | {
14 | use FilesystemWrapperTrait;
15 |
16 | /** @var callable */
17 | protected $factory;
18 | /** @var FilesystemInterface|null */
19 | protected $filesystem;
20 | /** @var string|null */
21 | protected $mountPoint;
22 | /** @var PluginInterface[] */
23 | protected $plugins = [];
24 |
25 | /**
26 | * Constructor.
27 | *
28 | * @param callable $factory This callable must return a FilesystemInterface when called.
29 | */
30 | public function __construct(callable $factory)
31 | {
32 | $this->factory = $factory;
33 | }
34 |
35 | /**
36 | * {@inheritdoc}
37 | */
38 | protected function wrapped()
39 | {
40 | if (!$this->filesystem) {
41 | $this->filesystem = call_user_func($this->factory);
42 | if (!$this->filesystem instanceof FilesystemInterface) {
43 | throw new LogicException('Factory supplied to LazyFilesystem must return an implementation of FilesystemInterface');
44 | }
45 |
46 | if ($this->filesystem instanceof MountPointAwareInterface) {
47 | $this->filesystem->setMountPoint($this->mountPoint);
48 | $this->mountPoint = null;
49 | }
50 |
51 | foreach ($this->plugins as $plugin) {
52 | $this->filesystem->addPlugin($plugin);
53 | }
54 | $this->plugins = [];
55 | }
56 |
57 | return $this->filesystem;
58 | }
59 |
60 | /**
61 | * @inheritdoc
62 | *
63 | * Plugins are added lazily.
64 | */
65 | public function addPlugin(PluginInterface $plugin)
66 | {
67 | if ($this->filesystem) {
68 | $this->filesystem->addPlugin($plugin);
69 | } else {
70 | $this->plugins[] = $plugin;
71 | }
72 | }
73 |
74 | /**
75 | * {@inheritdoc}
76 | */
77 | public function getMountPoint()
78 | {
79 | $filesystem = $this->wrapped();
80 |
81 | if ($filesystem instanceof MountPointAwareInterface) {
82 | return $filesystem->getMountPoint();
83 | }
84 |
85 | return null;
86 | }
87 |
88 | /**
89 | * @inheritdoc
90 | *
91 | * Mount point is set lazily.
92 | */
93 | public function setMountPoint($mountPoint)
94 | {
95 | if ($this->filesystem) {
96 | if ($this->filesystem instanceof MountPointAwareInterface) {
97 | $this->filesystem->setMountPoint($mountPoint);
98 | }
99 | } else {
100 | $this->mountPoint = $mountPoint;
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Handler/FileInterface.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | interface FileInterface extends HandlerInterface
15 | {
16 | /**
17 | * Read the file.
18 | *
19 | * @return string
20 | */
21 | public function read();
22 |
23 | /**
24 | * Read the file as a stream.
25 | *
26 | * @return StreamInterface
27 | */
28 | public function readStream();
29 |
30 | /**
31 | * Write the new file.
32 | *
33 | * @param string $content
34 | */
35 | public function write($content);
36 |
37 | /**
38 | * Write the new file using a stream.
39 | *
40 | * @param StreamInterface|resource $resource
41 | */
42 | public function writeStream($resource);
43 |
44 | /**
45 | * Update the file contents.
46 | *
47 | * @param string $content
48 | */
49 | public function update($content);
50 |
51 | /**
52 | * Update the file contents with a stream.
53 | *
54 | * @param StreamInterface|resource $resource
55 | */
56 | public function updateStream($resource);
57 |
58 | /**
59 | * Create the file or update if exists.
60 | *
61 | * @param string $content
62 | *
63 | * @return void
64 | */
65 | public function put($content);
66 |
67 | /**
68 | * Create the file or update if exists using a stream.
69 | *
70 | * @param StreamInterface|resource $resource
71 | */
72 | public function putStream($resource);
73 |
74 | /**
75 | * Rename the file.
76 | *
77 | * @param string $newPath
78 | */
79 | public function rename($newPath);
80 |
81 | /**
82 | * Get the file's MIME Type.
83 | *
84 | * @return string
85 | */
86 | public function getMimeType();
87 |
88 | /**
89 | * Get the file size.
90 | *
91 | * @return int
92 | */
93 | public function getSize();
94 |
95 | /**
96 | * Get the file size in a human readable format.
97 | *
98 | * @param bool $si Return results according to IEC standards (ie. 4.60 KiB) or SI standards (ie. 4.7 kb)
99 | *
100 | * @return string
101 | */
102 | public function getSizeFormatted($si = false);
103 |
104 | /**
105 | * Load the PHP file.
106 | *
107 | * @param bool $once Whether to include the file only once.
108 | *
109 | * @throws NotSupportedException If the filesystem does not support including PHP files.
110 | * @throws IncludeFileException On failure.
111 | *
112 | * @return mixed Returns the return from the file or true if $once is true and this is a subsequent call.
113 | */
114 | public function includeFile($once = true);
115 | }
116 |
--------------------------------------------------------------------------------
/src/Handler/Image/CoreType.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class CoreType extends Type implements TypeInterface
11 | {
12 | /** @var int */
13 | private $id;
14 | /** @var string */
15 | private $name;
16 |
17 | /**
18 | * Returns a list of all the core image types.
19 | *
20 | * @return TypeInterface[]
21 | */
22 | public static function getTypes()
23 | {
24 | $types = [];
25 | foreach (static::getConstants() as $id => $name) {
26 | $types[] = new static($id, $name);
27 | }
28 |
29 | return $types;
30 | }
31 |
32 | /**
33 | * Constructor.
34 | *
35 | * @param int $id An IMAGETYPE_* constant
36 | * @param string $name String representation based on constant
37 | */
38 | private function __construct($id, $name)
39 | {
40 | $this->id = (int) $id;
41 | $this->name = $name;
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | public function getId()
48 | {
49 | return $this->id;
50 | }
51 |
52 | /**
53 | * {@inheritdoc}
54 | */
55 | public function getMimeType()
56 | {
57 | return image_type_to_mime_type($this->id);
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | public function getExtension($includeDot = true)
64 | {
65 | return image_type_to_extension($this->id, $includeDot);
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | */
71 | public function toString()
72 | {
73 | return $this->name;
74 | }
75 |
76 | /**
77 | * {@inheritdoc}
78 | */
79 | public function __toString()
80 | {
81 | return $this->toString();
82 | }
83 |
84 | /**
85 | * Returns a list of all the image type constants.
86 | *
87 | * @return array [int $id, string $name]
88 | */
89 | private static function getConstants()
90 | {
91 | // Get list of all standard constants
92 | $constants = get_defined_constants(true);
93 | if (defined('HHVM_VERSION')) {
94 | $constants = $constants['Core'];
95 | } else {
96 | $constants = $constants['standard'];
97 | }
98 |
99 | // filter down to image type constants
100 | $types = [];
101 | foreach ($constants as $name => $value) {
102 | if ($value !== IMAGETYPE_COUNT && strpos($name, 'IMAGETYPE_') === 0) {
103 | $types[$name] = $value;
104 | }
105 | }
106 |
107 | // flip these and map them to a humanized string
108 | $types = array_map(
109 | function ($type) {
110 | return str_replace(['IMAGETYPE_', '_'], ['', ' '], $type);
111 | },
112 | array_flip($types)
113 | );
114 |
115 | ksort($types);
116 |
117 | return $types;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/tests/Iterator/FileContentFilterIteratorTest.php:
--------------------------------------------------------------------------------
1 | filesystem = new Filesystem(new Local(__DIR__ . '/../'));
24 | }
25 |
26 | public function testAccept()
27 | {
28 | $inner = new \ArrayIterator();
29 | $inner[] = new File($this->filesystem, 'base.css');
30 | $iterator = new FileContentFilterIterator($inner, [], []);
31 | $this->assertIterator(['base.css'], $iterator);
32 | }
33 |
34 | public function testDirectory()
35 | {
36 | $inner = new \ArrayIterator();
37 | $inner[] = new Directory($this->filesystem, 'fixtures');
38 | $iterator = new FileContentFilterIterator($inner, ['fixtures'], []);
39 | $this->assertIterator([], $iterator);
40 | }
41 |
42 | public function testUnreadableFile()
43 | {
44 | $inner = new \ArrayIterator();
45 | $mock = $this->getMockBuilder(File::class)
46 | ->setConstructorArgs([$this->filesystem, 'fixtures/base.css'])
47 | ->setMethods(['read'])
48 | ->getMock()
49 | ;
50 | $mock->expects($this->atLeastOnce())
51 | ->method('read')
52 | ->will($this->throwException(new IOException('Fake it, until you make it!')))
53 | ;
54 | $inner[] = $mock;
55 | $iterator = new FileContentFilterIterator($inner, ['fixtures/base.css'], []);
56 | $this->assertIterator([], $iterator);
57 | }
58 |
59 | /**
60 | * @dataProvider getTestFilterData
61 | */
62 | public function testFilter(\Iterator $inner, array $matchPatterns, array $noMatchPatterns, array $resultArray)
63 | {
64 | $iterator = new FileContentFilterIterator($inner, $matchPatterns, $noMatchPatterns);
65 | $this->assertIterator($resultArray, $iterator);
66 | }
67 |
68 | public function getTestFilterData()
69 | {
70 | $filesystem = new Filesystem(new Local(__DIR__ . '/../'));
71 | $inner = new \ArrayIterator();
72 |
73 | $inner[] = new File($filesystem, 'fixtures/base.css');
74 | $inner[] = new File($filesystem, 'fixtures/css/style.css');
75 | $inner[] = new File($filesystem, 'fixtures/js/script.js');
76 |
77 | return [
78 | [$inner, ['.'], [], ['fixtures/base.css', 'fixtures/css/style.css', 'fixtures/js/script.js']],
79 | [$inner, ['color'], [], ['fixtures/base.css', 'fixtures/css/style.css']],
80 | [$inner, ['color', 'koala'], ['width', 'shape'], ['fixtures/css/style.css']],
81 | ];
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Handler/DirectoryInterface.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | interface DirectoryInterface extends HandlerInterface
15 | {
16 | /**
17 | * Returns whether this directory is the root directory.
18 | *
19 | * @return bool
20 | */
21 | public function isRoot();
22 |
23 | /**
24 | * Create the directory.
25 | *
26 | * @param array $config
27 | *
28 | * @throws DirectoryCreationException
29 | * @throws IOException
30 | */
31 | public function create($config = []);
32 |
33 | /**
34 | * Mirrors the directory to another.
35 | *
36 | * Note: By default, this will delete files in target if they are not in source.
37 | *
38 | * @param string $targetDir The target directory
39 | * @param array $config Valid options are:
40 | * - delete = Whether to delete files that are not in the source directory (default: true)
41 | * - override = See {@see copyDir}'s $override parameter for details (default: null)
42 | */
43 | public function mirror($targetDir, $config = []);
44 |
45 | /**
46 | * Get a handler for an entree.
47 | *
48 | * @param string $path The path to the file.
49 | * @param HandlerInterface $handler An optional existing handler to populate.
50 | *
51 | * @throws IOException
52 | *
53 | * @return HandlerInterface
54 | */
55 | public function get($path, HandlerInterface $handler = null);
56 |
57 | /**
58 | * Get a file handler.
59 | *
60 | * @param string $path The path to the file.
61 | * @param FileInterface $handler An optional existing file handler to populate.
62 | *
63 | * @throws IOException
64 | *
65 | * @return FileInterface
66 | */
67 | public function getFile($path, FileInterface $handler = null);
68 |
69 | /**
70 | * Get a directory handler.
71 | *
72 | * @param string $path The path to the directory.
73 | *
74 | * @throws IOException
75 | *
76 | * @return DirectoryInterface
77 | */
78 | public function getDir($path);
79 |
80 | /**
81 | * Get an image handler.
82 | *
83 | * @param string $path The path to the image.
84 | *
85 | * @throws IOException
86 | *
87 | * @return ImageInterface
88 | */
89 | public function getImage($path);
90 |
91 | /**
92 | * List the directory contents.
93 | *
94 | * @param bool $recursive
95 | *
96 | * @return HandlerInterface[]
97 | */
98 | public function getContents($recursive = false);
99 |
100 | /**
101 | * Returns a finder instance set to this directory.
102 | *
103 | * @return Finder
104 | */
105 | public function find();
106 | }
107 |
--------------------------------------------------------------------------------
/tests/Iterator/RecursiveDirectoryIteratorTest.php:
--------------------------------------------------------------------------------
1 | filesystem = new Filesystem(new Local(__DIR__ . '/../'));
24 | }
25 |
26 | public function testIteration()
27 | {
28 | $it = new RecursiveDirectoryIterator($this->filesystem, 'fixtures');
29 | $it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
30 |
31 | $expected = [
32 | 'fixtures/base.css',
33 | 'fixtures/css',
34 | 'fixtures/css/old',
35 | 'fixtures/css/old/old_style.css',
36 | 'fixtures/css/reset.css',
37 | 'fixtures/css/style.css',
38 | 'fixtures/images',
39 | 'fixtures/images/1-top-left.jpg',
40 | 'fixtures/images/2-top-right.jpg',
41 | 'fixtures/images/3-bottom-right.jpg',
42 | 'fixtures/images/4-bottom-left.jpg',
43 | 'fixtures/images/5-left-top.jpg',
44 | 'fixtures/images/6-right-top.jpg',
45 | 'fixtures/images/7-right-bottom.jpg',
46 | 'fixtures/images/8-left-bottom.jpg',
47 | 'fixtures/images/nut.svg',
48 | 'fixtures/js',
49 | 'fixtures/js/script.js',
50 | ];
51 | $this->assertIterator($expected, $it);
52 | $this->assertIteratorInForeach($expected, $it);
53 | }
54 |
55 | public function testIterationForGlob()
56 | {
57 | $glob = '/fixtures/**/*.css';
58 | $basePath = Glob::getBasePath($glob);
59 | $it = new RecursiveDirectoryIterator($this->filesystem, $basePath, RecursiveDirectoryIterator::KEY_FOR_GLOB);
60 | $it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
61 | $it = new GlobFilterIterator($glob, $it, GlobFilterIterator::FILTER_KEY | GlobFilterIterator::KEY_AS_KEY);
62 |
63 | $expected = [
64 | 'fixtures/base.css',
65 | 'fixtures/css/old/old_style.css',
66 | 'fixtures/css/reset.css',
67 | 'fixtures/css/style.css',
68 | ];
69 | $this->assertIterator($expected, $it);
70 | $this->assertIteratorInForeach($expected, $it);
71 | }
72 |
73 | public function testSeek()
74 | {
75 | $it = new RecursiveDirectoryIterator($this->filesystem, 'fixtures');
76 |
77 | $it->seek(1);
78 | $this->assertTrue($it->valid(), 'Current iteration is not valid');
79 | $this->assertEquals('fixtures/css', $it->current()->getPath());
80 |
81 | $it->seek(0);
82 | $this->assertTrue($it->valid(), 'Current iteration is not valid');
83 | $this->assertEquals('fixtures/base.css', $it->current()->getPath());
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Iterator/MapIterator.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | abstract class MapIterator implements OuterIterator
18 | {
19 | /** @var Iterator */
20 | private $inner;
21 |
22 | /** @var mixed */
23 | private $key = null;
24 | /** @var mixed */
25 | private $current = null;
26 |
27 | /**
28 | * Constructor.
29 | *
30 | * @param Traversable|array $iterable
31 | */
32 | public function __construct($iterable)
33 | {
34 | if ($iterable instanceof Traversable) {
35 | $this->inner = new IteratorIterator($iterable);
36 | } elseif (is_array($iterable)) {
37 | $this->inner = new ArrayIterator($iterable);
38 | } else {
39 | throw new \InvalidArgumentException('MapIterator must be given an iterable object.');
40 | }
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function getInnerIterator()
47 | {
48 | return $this->inner;
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | public function valid()
55 | {
56 | return $this->inner->valid();
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | public function key()
63 | {
64 | return $this->key;
65 | }
66 |
67 | /**
68 | * {@inheritdoc}
69 | */
70 | public function current()
71 | {
72 | return $this->current;
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | public function next()
79 | {
80 | $this->inner->next();
81 | $this->applyMapping();
82 | }
83 |
84 | /**
85 | * {@inheritdoc}
86 | */
87 | public function rewind()
88 | {
89 | $this->inner->rewind();
90 | $this->applyMapping();
91 | }
92 |
93 | /**
94 | * Return the iterable as an array (with the mapping applied).
95 | *
96 | * @return array
97 | */
98 | public function toArray()
99 | {
100 | return iterator_to_array($this);
101 | }
102 |
103 | /**
104 | * Map the current key and/or value to something else.
105 | *
106 | * @param mixed $value The value.
107 | * @param mixed $key Key is passed by reference so it can be changed as well.
108 | *
109 | * @return mixed The new value.
110 | */
111 | abstract protected function map($value, &$key);
112 |
113 | /**
114 | * Apply mapping functions to key/value if current entry is valid.
115 | */
116 | private function applyMapping()
117 | {
118 | if (!$this->valid()) {
119 | return;
120 | }
121 |
122 | // Store key as local variable since it is passed by reference.
123 | $key = $this->inner->key();
124 | $this->current = $this->map($this->inner->current(), $key);
125 | $this->key = $key;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Plugin/PluggableTrait.php:
--------------------------------------------------------------------------------
1 | plugins[$plugin->getMethod()] = $plugin;
25 | }
26 |
27 | /**
28 | * Register a list of plugins.
29 | *
30 | * @param PluginInterface[] $plugins
31 | *
32 | * @throws InvalidArgumentException
33 | */
34 | public function addPlugins(array $plugins)
35 | {
36 | foreach ($plugins as $plugin) {
37 | if (!$plugin instanceof PluginInterface) {
38 | throw new InvalidArgumentException('Plugin must be instance of Bolt\Filesystem\PluginInterface');
39 | }
40 | $this->addPlugin($plugin);
41 | }
42 | }
43 |
44 | /**
45 | * Find a specific plugin.
46 | *
47 | * @param string $method
48 | *
49 | * @throws LogicException
50 | *
51 | * @return PluginInterface $plugin
52 | */
53 | protected function findPlugin($method)
54 | {
55 | if (!isset($this->plugins[$method])) {
56 | throw new PluginNotFoundException('Plugin not found for method: ' . $method);
57 | }
58 |
59 | if (!method_exists($this->plugins[$method], 'handle')) {
60 | throw new LogicException(get_class($this->plugins[$method]) . ' does not have a handle method.');
61 | }
62 |
63 | return $this->plugins[$method];
64 | }
65 |
66 | /**
67 | * Invoke a plugin by method name.
68 | *
69 | * @param string $method
70 | * @param array $arguments
71 | * @param FilesystemInterface $filesystem
72 | *
73 | * @return mixed
74 | */
75 | protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem)
76 | {
77 | $plugin = $this->findPlugin($method);
78 | $plugin->setFilesystem($filesystem);
79 | $callback = [$plugin, 'handle'];
80 |
81 | return call_user_func_array($callback, $arguments);
82 | }
83 |
84 | /**
85 | * Plugins pass-through.
86 | *
87 | * @param string $method
88 | * @param array $arguments
89 | *
90 | * @throws BadMethodCallException
91 | *
92 | * @return mixed
93 | */
94 | public function __call($method, array $arguments)
95 | {
96 | try {
97 | return $this->invokePlugin($method, $arguments, $this);
98 | } catch (PluginNotFoundException $e) {
99 | throw new BadMethodCallException(
100 | 'Call to undefined method '
101 | . get_class($this)
102 | . '::' . $method
103 | );
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/tests/Iterator/PathFilterIteratorTest.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class PathFilterIteratorTest extends IteratorTestCase
18 | {
19 | /** @var FilesystemInterface */
20 | protected $filesystem;
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function setUp()
26 | {
27 | $this->filesystem = new Filesystem(new Local(__DIR__ . '/../'));
28 | }
29 |
30 | /**
31 | * @dataProvider getTestFilterData
32 | */
33 | public function testFilter(\Iterator $inner, array $matchPatterns, array $noMatchPatterns, array $resultArray)
34 | {
35 | $iterator = new PathFilterIterator($inner, $matchPatterns, $noMatchPatterns);
36 | $this->assertIterator($resultArray, $iterator);
37 | }
38 |
39 | public function getTestFilterData()
40 | {
41 | $inner = new MockFileListIterator();
42 |
43 | $inner[] = new File($this->filesystem, 'A/B/C/abc.dat');
44 | $inner[] = new File($this->filesystem, 'A/B/ab.dat');
45 | $inner[] = new File($this->filesystem, 'A/a.dat');
46 | $inner[] = new File($this->filesystem, 'copy/A/B/C/abc.dat.copy');
47 | $inner[] = new File($this->filesystem, 'copy/A/B/ab.dat.copy');
48 | $inner[] = new File($this->filesystem, 'copy/A/a.dat.copy');
49 |
50 | return [
51 | [$inner, ['/^A/'], [], ['A/B/C/abc.dat', 'A/B/ab.dat', 'A/a.dat']],
52 | [$inner, ['/^A\/B/'], [], ['A/B/C/abc.dat', 'A/B/ab.dat']],
53 | [$inner, ['/^A\/B\/C/'], [], ['A/B/C/abc.dat']],
54 | [$inner, ['/A\/B\/C/'], [], ['A/B/C/abc.dat', 'copy/A/B/C/abc.dat.copy']],
55 |
56 | [$inner, ['A'], [], ['A/B/C/abc.dat', 'A/B/ab.dat', 'A/a.dat', 'copy/A/B/C/abc.dat.copy', 'copy/A/B/ab.dat.copy', 'copy/A/a.dat.copy']],
57 | [$inner, ['A/B'], [], ['A/B/C/abc.dat', 'A/B/ab.dat', 'copy/A/B/C/abc.dat.copy', 'copy/A/B/ab.dat.copy']],
58 | [$inner, ['A/B'], [], ['A/B/C/abc.dat', 'A/B/ab.dat', 'copy/A/B/C/abc.dat.copy', 'copy/A/B/ab.dat.copy']],
59 | [$inner, ['A/B/C'], [], ['A/B/C/abc.dat', 'copy/A/B/C/abc.dat.copy']],
60 |
61 | [$inner, ['copy/A'], [], ['copy/A/B/C/abc.dat.copy', 'copy/A/B/ab.dat.copy', 'copy/A/a.dat.copy']],
62 | [$inner, ['copy/A/B'], [], ['copy/A/B/C/abc.dat.copy', 'copy/A/B/ab.dat.copy']],
63 | [$inner, ['copy/A/B/C'], [], ['copy/A/B/C/abc.dat.copy']],
64 |
65 | [$inner, ['A'], ['/copy/'], ['A/B/C/abc.dat', 'A/B/ab.dat', 'A/a.dat']],
66 | [$inner, ['A/B'], ['/copy/'], ['A/B/C/abc.dat', 'A/B/ab.dat']],
67 | [$inner, ['A/B'], ['/copy/'], ['A/B/C/abc.dat', 'A/B/ab.dat']],
68 | [$inner, ['A/B/C'], ['/copy/'], ['A/B/C/abc.dat']],
69 | ];
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Adapter/LocalTest.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class LocalTest extends FilesystemTestCase
18 | {
19 | /** @var FilesystemInterface */
20 | protected $filesystem;
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function setUp()
26 | {
27 | parent::setUp();
28 | $this->filesystem = new Filesystem(new Local($this->rootDir . '/tests'));
29 | }
30 |
31 | /**
32 | * @expectedException \Bolt\Filesystem\Exception\DirectoryCreationException
33 | * @expectedExceptionMessage Failed to create directory
34 | */
35 | public function testConstruct()
36 | {
37 | if (posix_getuid() === 0) {
38 | $this->fail('Do not run as root user');
39 | }
40 |
41 | $local = new Local($this->tempDir);
42 | $this->assertInstanceOf(Local::class, $local);
43 |
44 | new Local('/bad');
45 | }
46 |
47 | public function testUpdate()
48 | {
49 | $this->filesystem->get('fixtures/base.css')->copy('temp/koala.css');
50 | $local = new Local($this->tempDir);
51 | $config = new Config();
52 |
53 | $update = $local->update('koala.css', '.drop-bear {}', $config);
54 | $this->assertSame('koala.css', $update['path']);
55 | $this->assertSame('.drop-bear {}', $update['contents']);
56 | $this->assertSame('text/css', $update['mimetype']);
57 |
58 | $update = $local->update('koala.css.typo', '.drop-bear {}', $config);
59 | $this->assertFalse($update);
60 | }
61 |
62 | /**
63 | * @expectedException \Bolt\Filesystem\Exception\FileNotFoundException
64 | */
65 | public function testDelete()
66 | {
67 | $this->filesystem->get('fixtures/base.css')->copy('temp/koala.css');
68 | $local = new Local($this->tempDir);
69 | $delete = $local->delete('koala.css');
70 | $this->assertTrue($delete);
71 |
72 | $local->delete('koala.css.typo');
73 | }
74 |
75 | public function testCreateDir()
76 | {
77 | $local = new Local($this->tempDir);
78 | $config = new Config();
79 |
80 | $create = $local->createDir('horse-with-no-name', $config);
81 | $this->assertSame('horse-with-no-name', $create['path']);
82 | $this->assertSame('dir', $create['type']);
83 |
84 | $this->filesystem->get('fixtures/base.css')->copy('temp/horse-with-no-name/koala.css');
85 | $create = $local->createDir('horse-with-no-name/koala.css', $config);
86 | $this->assertFalse($create);
87 | }
88 |
89 | public function testDeleteDir()
90 | {
91 | $local = new Local($this->tempDir);
92 | $config = new Config();
93 |
94 | $local->createDir('horse-with-no-name', $config);
95 | $delete = $local->deleteDir('horse-with-no-name');
96 |
97 | $this->assertTrue($delete);
98 | $this->assertFalse($local->has('horse-with-no-name'));
99 |
100 | $delete = $local->deleteDir('horse-with-no-name');
101 | $this->assertFalse($delete);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Handler/Image/Type.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class Type
15 | {
16 | /** @var TypeInterface[] */
17 | private static $types = [];
18 | /** @var bool */
19 | private static $initialized = false;
20 |
21 | /**
22 | * Register an Image Type.
23 | *
24 | * @param TypeInterface $type
25 | */
26 | public static function register(TypeInterface $type)
27 | {
28 | static::$types[$type->getId()] = $type;
29 | }
30 |
31 | /**
32 | * Returns a Type for the ID.
33 | *
34 | * @param int $id An IMAGETYPE_* constant
35 | *
36 | * @throws InvalidArgumentException If the ID isn't a valid IMAGETYPE_* constant
37 | *
38 | * @return TypeInterface
39 | */
40 | public static function getById($id)
41 | {
42 | $id = (int) $id;
43 | $types = static::getTypes();
44 |
45 | if (!isset($types[$id])) {
46 | throw new InvalidArgumentException('Given type is not an IMAGETYPE_* constant');
47 | }
48 |
49 | return $types[$id];
50 | }
51 |
52 | /**
53 | * Returns a list of all the image types.
54 | *
55 | * @return TypeInterface[]
56 | */
57 | public static function getTypes()
58 | {
59 | static::initialize();
60 |
61 | return static::$types;
62 | }
63 |
64 | /**
65 | * Returns a list of all the MIME Types for images.
66 | *
67 | * @return string[]
68 | */
69 | public static function getMimeTypes()
70 | {
71 | return array_map(
72 | function (TypeInterface $type) {
73 | return $type->getMimeType();
74 | },
75 | static::getTypes()
76 | );
77 | }
78 |
79 | /**
80 | * Returns a list of all the file extensions for images.
81 | *
82 | * @param bool $includeDot Whether to prepend a dot to the extension or not
83 | *
84 | * @return string[]
85 | */
86 | public static function getExtensions($includeDot = false)
87 | {
88 | $extensions = array_filter(
89 | array_map(
90 | function (TypeInterface $type) use ($includeDot) {
91 | return $type->getExtension($includeDot);
92 | },
93 | static::getTypes()
94 | )
95 | );
96 | $extensions[] = ($includeDot ? '.' : '') . 'jpg';
97 |
98 | return $extensions;
99 | }
100 |
101 | /**
102 | * Shortcut for unknown image type.
103 | *
104 | * @return TypeInterface
105 | */
106 | public static function unknown()
107 | {
108 | return static::getById(IMAGETYPE_UNKNOWN);
109 | }
110 |
111 | /**
112 | * Register default types.
113 | */
114 | private static function initialize()
115 | {
116 | if (static::$initialized) {
117 | return;
118 | }
119 | static::$initialized = true;
120 |
121 | foreach (CoreType::getTypes() as $type) {
122 | static::register($type);
123 | }
124 |
125 | static::register(new SvgType());
126 | }
127 |
128 | /**
129 | * Constructor.
130 | */
131 | private function __construct()
132 | {
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/tests/Iterator/DateRangeFilterIteratorTest.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class DateRangeFilterIteratorTest extends IteratorTestCase
19 | {
20 | /** @var FilesystemInterface */
21 | protected $filesystem;
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | protected function setUp()
27 | {
28 | $this->filesystem = new Filesystem(new Local(__DIR__ . '/../'));
29 | }
30 |
31 | /**
32 | * @dataProvider getAcceptData
33 | */
34 | public function testAccept($size, $expected)
35 | {
36 | $it = new RecursiveDirectoryIterator($this->filesystem, 'fixtures');
37 | $it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
38 |
39 | touch(dirname(__DIR__) . '/fixtures/css/old', strtotime('2012-06-14'));
40 | touch(dirname(__DIR__) . '/fixtures/css/old/old_style.css', strtotime('2012-06-14'));
41 |
42 | $iterator = new DateRangeFilterIterator($it, $size);
43 |
44 | $this->assertIterator($expected, $iterator);
45 | }
46 |
47 | public function getAcceptData()
48 | {
49 | $since20YearsAgo = [
50 | 'fixtures/base.css',
51 | 'fixtures/css',
52 | 'fixtures/css/old',
53 | 'fixtures/css/old/old_style.css',
54 | 'fixtures/css/reset.css',
55 | 'fixtures/css/style.css',
56 | 'fixtures/images',
57 | 'fixtures/images/1-top-left.jpg',
58 | 'fixtures/images/2-top-right.jpg',
59 | 'fixtures/images/3-bottom-right.jpg',
60 | 'fixtures/images/4-bottom-left.jpg',
61 | 'fixtures/images/5-left-top.jpg',
62 | 'fixtures/images/6-right-top.jpg',
63 | 'fixtures/images/7-right-bottom.jpg',
64 | 'fixtures/images/8-left-bottom.jpg',
65 | 'fixtures/images/nut.svg',
66 | 'fixtures/js',
67 | 'fixtures/js/script.js',
68 | ];
69 |
70 | $since2MonthsAgo = [
71 | 'fixtures/base.css',
72 | 'fixtures/css',
73 | 'fixtures/css/reset.css',
74 | 'fixtures/css/style.css',
75 | 'fixtures/images',
76 | 'fixtures/images/1-top-left.jpg',
77 | 'fixtures/images/2-top-right.jpg',
78 | 'fixtures/images/3-bottom-right.jpg',
79 | 'fixtures/images/4-bottom-left.jpg',
80 | 'fixtures/images/5-left-top.jpg',
81 | 'fixtures/images/6-right-top.jpg',
82 | 'fixtures/images/7-right-bottom.jpg',
83 | 'fixtures/images/8-left-bottom.jpg',
84 | 'fixtures/images/nut.svg',
85 | 'fixtures/js',
86 | 'fixtures/js/script.js',
87 | ];
88 |
89 | $untilLastMonth = [
90 | 'fixtures/css/old',
91 | 'fixtures/css/old/old_style.css',
92 | ];
93 |
94 | return [
95 | [[new DateComparator('since 20 years ago')], $since20YearsAgo],
96 | [[new DateComparator('since 2 months ago')], $since2MonthsAgo],
97 | [[new DateComparator('until last month')], $untilLastMonth],
98 | ];
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Adapter/Cached.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class Cached extends CachedAdapter implements Capability\ImageInfo, Capability\IncludeFile
19 | {
20 | /** @var CacheInterface */
21 | protected $cache;
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function __construct(AdapterInterface $adapter, CacheInterface $cache)
27 | {
28 | parent::__construct($adapter, $cache);
29 | $this->cache = $cache;
30 | }
31 |
32 | /**
33 | * Flush the cache.
34 | */
35 | public function flush()
36 | {
37 | $this->cache->flush();
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | public function read($path)
44 | {
45 | $result = $this->cache->read($path);
46 |
47 | if ($result !== false && isset($result['contents']) && $result['contents'] !== false) {
48 | return $result;
49 | }
50 |
51 | $result = $this->getAdapter()->read($path);
52 |
53 | if ($result) {
54 | $object = $result + compact('path');
55 | $this->cache->updateObject($path, $object, true);
56 | }
57 |
58 | return $result;
59 | }
60 |
61 | /**
62 | * {@inheritdoc}
63 | */
64 | public function getImageInfo($path)
65 | {
66 | // If cache doesn't support image info, just pass through to adapter.
67 | if (!$this->cache instanceof Capability\ImageInfo) {
68 | return $this->doGetImageInfo($path);
69 | }
70 |
71 | // Get from cache.
72 | $info = $this->cache->getImageInfo($path);
73 | if ($info !== false) {
74 | return is_array($info) ? Image\Info::createFromJson($info) : $info;
75 | }
76 |
77 | // Else from adapter.
78 | $info = $this->doGetImageInfo($path);
79 |
80 | // Save info from adapter.
81 | $object = [
82 | 'path' => $path,
83 | 'image_info' => $info,
84 | ];
85 | $this->cache->updateObject($path, $object, true);
86 |
87 | return $info;
88 | }
89 |
90 | /**
91 | * Get image info from adapter.
92 | *
93 | * @param string $path
94 | *
95 | * @return Image\Info
96 | */
97 | private function doGetImageInfo($path)
98 | {
99 | // Get info from adapter if it's capable.
100 | $adapter = $this->getAdapter();
101 | if ($adapter instanceof Capability\ImageInfo) {
102 | return $adapter->getImageInfo($path);
103 | }
104 |
105 | // Else fallback to reading image contents and creating info from string.
106 | $result = $this->read($path);
107 | if ($result === false || !isset($result['contents'])) {
108 | throw new IOException('Failed to read file', $path);
109 | }
110 |
111 | return Image\Info::createFromString($result['contents'], $path);
112 | }
113 |
114 | /**
115 | * {@inheritdoc}
116 | */
117 | public function includeFile($path, $once = true)
118 | {
119 | $adapter = $this->getAdapter();
120 | if (!$adapter instanceof Capability\IncludeFile) {
121 | throw new NotSupportedException('Filesystem does not support including PHP files.');
122 | }
123 |
124 | return $adapter->includeFile($path, $once);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Handler/YamlFile.php:
--------------------------------------------------------------------------------
1 |
15 | * @author Carson Full
16 | */
17 | class YamlFile extends File implements ParsableInterface
18 | {
19 | /** @var bool Whether symfony/yaml is v3.1+ */
20 | private static $useFlags;
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function parse($options = [])
26 | {
27 | $options += [
28 | 'exceptionsOnInvalidType' => false,
29 | 'objectSupport' => false,
30 | 'objectForMap' => false,
31 | ];
32 |
33 | $contents = $this->read();
34 |
35 | static::checkYamlVersion();
36 |
37 | try {
38 | if (static::$useFlags) {
39 | $flags = $this->optionsToFlags($options);
40 |
41 | return Yaml::parse($contents, $flags);
42 | } else {
43 | return Yaml::parse(
44 | $contents,
45 | $options['exceptionsOnInvalidType'],
46 | $options['objectSupport'],
47 | $options['objectForMap']
48 | );
49 | }
50 | } catch (Symfony\ParseException $e) {
51 | throw ParseException::castFromYaml($e);
52 | }
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public function dump($contents, $options = [])
59 | {
60 | $options += [
61 | 'inline' => 2,
62 | 'indent' => 4,
63 | 'exceptionsOnInvalidType' => false,
64 | 'objectSupport' => false,
65 | ];
66 |
67 | static::checkYamlVersion();
68 |
69 | try {
70 | if (static::$useFlags) {
71 | $flags = $this->optionsToFlags($options);
72 | $contents = Yaml::dump($contents, $options['inline'], $options['indent'], $flags);
73 | } else {
74 | $contents = Yaml::dump(
75 | $contents,
76 | $options['inline'],
77 | $options['indent'],
78 | $options['exceptionsOnInvalidType'],
79 | $options['objectSupport']
80 | );
81 | }
82 | } catch (Symfony\DumpException $e) {
83 | throw new DumpException($e->getMessage(), $e->getCode(), $e);
84 | }
85 | $this->put($contents);
86 | }
87 |
88 | /**
89 | * @deprecated Remove when symfony/yaml 3.1+ is required
90 | *
91 | * @param array $options
92 | *
93 | * @return int
94 | */
95 | private function optionsToFlags(array $options)
96 | {
97 | $flagParams = [
98 | 'exceptionsOnInvalidType' => Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE,
99 | 'objectSupport' => Yaml::PARSE_OBJECT,
100 | 'objectForMap' => Yaml::PARSE_OBJECT_FOR_MAP,
101 | ];
102 |
103 | $flags = 0;
104 | foreach ($flagParams as $optionName => $bit) {
105 | if (isset($options[$optionName]) && $options[$optionName]) {
106 | $flags |= $bit;
107 | }
108 | }
109 |
110 | return $flags;
111 | }
112 |
113 | /**
114 | * @deprecated Remove when symfony/yaml 3.1+ is required
115 | */
116 | private static function checkYamlVersion()
117 | {
118 | if (static::$useFlags === null) {
119 | $ref = new ReflectionMethod(Yaml::class, 'parse');
120 | static::$useFlags = $ref->getNumberOfParameters() === 2;
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/tests/Iterator/ExcludeDirectoryFilterIteratorTest.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class ExcludeDirectoryFilterIteratorTest extends IteratorTestCase
18 | {
19 | /** @var FilesystemInterface */
20 | protected $filesystem;
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function setUp()
26 | {
27 | $this->filesystem = new Filesystem(new Local(__DIR__ . '/../'));
28 | }
29 |
30 | /**
31 | * @dataProvider getAcceptData
32 | */
33 | public function testAccept($directories, $expected)
34 | {
35 | $it = new RecursiveDirectoryIterator($this->filesystem, 'fixtures');
36 | $it = new ExcludeDirectoryFilterIterator($it, $directories);
37 | $it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
38 |
39 | $this->assertIterator($expected, $it);
40 | }
41 |
42 | public function getAcceptData()
43 | {
44 | return [
45 | 'exclude directory name' => [
46 | ['js'],
47 | [
48 | 'fixtures/base.css',
49 | 'fixtures/css',
50 | 'fixtures/css/old',
51 | 'fixtures/css/old/old_style.css',
52 | 'fixtures/css/reset.css',
53 | 'fixtures/css/style.css',
54 | 'fixtures/images',
55 | 'fixtures/images/1-top-left.jpg',
56 | 'fixtures/images/2-top-right.jpg',
57 | 'fixtures/images/3-bottom-right.jpg',
58 | 'fixtures/images/4-bottom-left.jpg',
59 | 'fixtures/images/5-left-top.jpg',
60 | 'fixtures/images/6-right-top.jpg',
61 | 'fixtures/images/7-right-bottom.jpg',
62 | 'fixtures/images/8-left-bottom.jpg',
63 | 'fixtures/images/nut.svg',
64 | ]
65 | ],
66 | 'partial dir names do not count' => [
67 | ['j'],
68 | [
69 | 'fixtures/base.css',
70 | 'fixtures/css',
71 | 'fixtures/css/old',
72 | 'fixtures/css/old/old_style.css',
73 | 'fixtures/css/reset.css',
74 | 'fixtures/css/style.css',
75 | 'fixtures/images',
76 | 'fixtures/images/1-top-left.jpg',
77 | 'fixtures/images/2-top-right.jpg',
78 | 'fixtures/images/3-bottom-right.jpg',
79 | 'fixtures/images/4-bottom-left.jpg',
80 | 'fixtures/images/5-left-top.jpg',
81 | 'fixtures/images/6-right-top.jpg',
82 | 'fixtures/images/7-right-bottom.jpg',
83 | 'fixtures/images/8-left-bottom.jpg',
84 | 'fixtures/images/nut.svg',
85 | 'fixtures/js',
86 | 'fixtures/js/script.js',
87 | ]
88 | ],
89 | 'pattern' => [
90 | ['css/old'],
91 | [
92 | 'fixtures/base.css',
93 | 'fixtures/css',
94 | 'fixtures/css/reset.css',
95 | 'fixtures/css/style.css',
96 | 'fixtures/images',
97 | 'fixtures/images/1-top-left.jpg',
98 | 'fixtures/images/2-top-right.jpg',
99 | 'fixtures/images/3-bottom-right.jpg',
100 | 'fixtures/images/4-bottom-left.jpg',
101 | 'fixtures/images/5-left-top.jpg',
102 | 'fixtures/images/6-right-top.jpg',
103 | 'fixtures/images/7-right-bottom.jpg',
104 | 'fixtures/images/8-left-bottom.jpg',
105 | 'fixtures/images/nut.svg',
106 | 'fixtures/js',
107 | 'fixtures/js/script.js',
108 | ]
109 | ]
110 | ];
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Handler/File.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class File extends BaseHandler implements FileInterface
11 | {
12 | /**
13 | * {@inheritdoc}
14 | */
15 | public function read()
16 | {
17 | return $this->filesystem->read($this->path);
18 | }
19 |
20 | /**
21 | * {@inheritdoc}
22 | */
23 | public function readStream()
24 | {
25 | return $this->filesystem->readStream($this->path);
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | public function includeFile($once = true)
32 | {
33 | return $this->filesystem->includeFile($this->path, $once);
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function write($content)
40 | {
41 | $this->filesystem->write($this->path, $content);
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | public function writeStream($resource)
48 | {
49 | $this->filesystem->writeStream($this->path, $resource);
50 | }
51 |
52 | /**
53 | * {@inheritdoc}
54 | */
55 | public function update($content)
56 | {
57 | $this->filesystem->update($this->path, $content);
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | public function updateStream($resource)
64 | {
65 | $this->filesystem->updateStream($this->path, $resource);
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | */
71 | public function put($content)
72 | {
73 | $this->filesystem->put($this->path, $content);
74 | }
75 |
76 | /**
77 | * {@inheritdoc}
78 | */
79 | public function putStream($resource)
80 | {
81 | $this->filesystem->putStream($this->path, $resource);
82 | }
83 |
84 | /**
85 | * {@inheritdoc}
86 | */
87 | public function rename($newPath)
88 | {
89 | $this->filesystem->rename($this->path, $newPath);
90 | $this->path = $newPath;
91 | }
92 |
93 | /**
94 | * {@inheritdoc}
95 | */
96 | public function copy($target, $override = null)
97 | {
98 | $this->filesystem->copy($this->path, $target, $override);
99 |
100 | return new static($this->filesystem, $target);
101 | }
102 |
103 | /**
104 | * {@inheritdoc}
105 | */
106 | public function delete()
107 | {
108 | $this->filesystem->delete($this->path);
109 | }
110 |
111 | /**
112 | * {@inheritdoc}
113 | */
114 | public function getMimeType()
115 | {
116 | return $this->filesystem->getMimeType($this->path);
117 | }
118 |
119 | /**
120 | * {@inheritdoc}
121 | */
122 | public function getSize()
123 | {
124 | return $this->filesystem->getSize($this->path);
125 | }
126 |
127 | /**
128 | * {@inheritdoc}
129 | */
130 | public function getSizeFormatted($si = false)
131 | {
132 | $size = $this->getSize();
133 |
134 | if ($si) {
135 | return $this->getSizeFormattedSi($size);
136 | } else {
137 | return $this->getSizeFormattedExact($size);
138 | }
139 | }
140 |
141 | /**
142 | * Format a filesize according to IEC standard. For example: '4734 bytes' -> '4.62 KiB'
143 | *
144 | * @param int $size
145 | *
146 | * @return string
147 | */
148 | private function getSizeFormattedExact($size)
149 | {
150 | if ($size > 1024 * 1024) {
151 | return sprintf('%0.2f MiB', ($size / 1024 / 1024));
152 | } elseif ($size > 1024) {
153 | return sprintf('%0.2f KiB', ($size / 1024));
154 | } else {
155 | return $size . ' B';
156 | }
157 | }
158 |
159 | /**
160 | * Format a filesize as 'end user friendly', so this should be seen as something that'd
161 | * be used in a quick glance. For example: '4734 bytes' -> '4.7 kB'
162 | *
163 | * @param int $size
164 | *
165 | * @return string
166 | */
167 | private function getSizeFormattedSi($size)
168 | {
169 | if ($size > 1000 * 1000) {
170 | return sprintf('%0.1f MB', ($size / 1000 / 1000));
171 | } elseif ($size > 1000) {
172 | return sprintf('%0.1f KB', ($size / 1000));
173 | } else {
174 | return $size . ' B';
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/FilesystemWrapperTrait.php:
--------------------------------------------------------------------------------
1 |
12 | */
13 | trait FilesystemWrapperTrait // implements FilesystemInterface
14 | {
15 | /**
16 | * @return FilesystemInterface
17 | */
18 | abstract protected function wrapped();
19 |
20 | public function has($path)
21 | {
22 | return $this->wrapped()->has($path);
23 | }
24 |
25 | public function read($path)
26 | {
27 | return $this->wrapped()->read($path);
28 | }
29 |
30 | public function readStream($path)
31 | {
32 | return $this->wrapped()->readStream($path);
33 | }
34 |
35 | public function write($path, $contents, $config = [])
36 | {
37 | $this->wrapped()->write($path, $contents, $config);
38 | }
39 |
40 | public function writeStream($path, $resource, $config = [])
41 | {
42 | $this->wrapped()->writeStream($path, $resource, $config);
43 | }
44 |
45 | public function update($path, $contents, $config = [])
46 | {
47 | $this->wrapped()->update($path, $contents, $config);
48 | }
49 |
50 | public function updateStream($path, $resource, $config = [])
51 | {
52 | $this->wrapped()->updateStream($path, $resource, $config);
53 | }
54 |
55 | public function put($path, $contents, $config = [])
56 | {
57 | $this->wrapped()->put($path, $contents, $config);
58 | }
59 |
60 | public function putStream($path, $resource, $config = [])
61 | {
62 | $this->wrapped()->putStream($path, $resource, $config);
63 | }
64 |
65 | public function readAndDelete($path)
66 | {
67 | return $this->wrapped()->readAndDelete($path);
68 | }
69 |
70 | public function rename($path, $newPath)
71 | {
72 | $this->wrapped()->rename($path, $newPath);
73 | }
74 |
75 | public function copy($origin, $target, $override = null)
76 | {
77 | $this->wrapped()->copy($origin, $target, $override);
78 | }
79 |
80 | public function delete($path)
81 | {
82 | $this->wrapped()->delete($path);
83 | }
84 |
85 | public function deleteDir($dirname)
86 | {
87 | $this->wrapped()->deleteDir($dirname);
88 | }
89 |
90 | public function createDir($dirname, $config = [])
91 | {
92 | $this->wrapped()->createDir($dirname, $config);
93 | }
94 |
95 | public function copyDir($originDir, $targetDir, $override = null)
96 | {
97 | $this->wrapped()->copyDir($originDir, $targetDir, $override);
98 | }
99 |
100 | public function mirror($originDir, $targetDir, $config = [])
101 | {
102 | $this->wrapped()->mirror($originDir, $targetDir, $config);
103 | }
104 |
105 | public function get($path, HandlerInterface $handler = null)
106 | {
107 | return $this->wrapped()->get($path, $handler);
108 | }
109 |
110 | public function getFile($path, FileInterface $handler = null)
111 | {
112 | return $this->wrapped()->getFile($path, $handler);
113 | }
114 |
115 | public function getDir($path)
116 | {
117 | return $this->wrapped()->getDir($path);
118 | }
119 |
120 | public function getImage($path)
121 | {
122 | return $this->wrapped()->getImage($path);
123 | }
124 |
125 | public function getType($path)
126 | {
127 | return $this->wrapped()->getType($path);
128 | }
129 |
130 | public function getSize($path)
131 | {
132 | return $this->wrapped()->getSize($path);
133 | }
134 |
135 | public function getTimestamp($path)
136 | {
137 | return $this->wrapped()->getTimestamp($path);
138 | }
139 |
140 | public function getCarbon($path)
141 | {
142 | return $this->wrapped()->getCarbon($path);
143 | }
144 |
145 | public function getMimeType($path)
146 | {
147 | return $this->wrapped()->getMimeType($path);
148 | }
149 |
150 | public function getVisibility($path)
151 | {
152 | return $this->wrapped()->getVisibility($path);
153 | }
154 |
155 | public function setVisibility($path, $visibility)
156 | {
157 | $this->wrapped()->setVisibility($path, $visibility);
158 | }
159 |
160 | public function listContents($directory = '', $recursive = false)
161 | {
162 | return $this->wrapped()->listContents($directory, $recursive);
163 | }
164 |
165 | public function find()
166 | {
167 | return $this->wrapped()->find();
168 | }
169 |
170 | public function getImageInfo($path)
171 | {
172 | return $this->wrapped()->getImageInfo($path);
173 | }
174 |
175 | public function includeFile($path, $once = true)
176 | {
177 | return $this->wrapped()->includeFile($path, $once);
178 | }
179 |
180 | public function addPlugin(PluginInterface $plugin)
181 | {
182 | $this->wrapped()->addPlugin($plugin);
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/Handler/HandlerInterface.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | interface HandlerInterface extends MountPointAwareInterface
15 | {
16 | /**
17 | * Set the Filesystem object.
18 | *
19 | * WARNING: Do not call this unless you know what you are doing.
20 | *
21 | * @param FilesystemInterface $filesystem
22 | *
23 | * @internal
24 | */
25 | public function setFilesystem(FilesystemInterface $filesystem);
26 |
27 | /**
28 | * Returns the Filesystem object.
29 | *
30 | * @return FilesystemInterface
31 | */
32 | public function getFilesystem();
33 |
34 | /**
35 | * Set the entree path.
36 | *
37 | * WARNING: Do not call this unless you know what you are doing.
38 | *
39 | * @param string $path
40 | *
41 | * @internal
42 | */
43 | public function setPath($path);
44 |
45 | /**
46 | * Returns the entree path.
47 | *
48 | * @return string path
49 | */
50 | public function getPath();
51 |
52 | /**
53 | * Returns the entree path with the mount point prefixed (if set).
54 | *
55 | * @return string
56 | */
57 | public function getFullPath();
58 |
59 | /**
60 | * Returns the directory for this entree.
61 | *
62 | * Note: If this entree is the root directory, a different
63 | * instance of the same directory is returned.
64 | * This can also be checked with {@see DirectoryInterface::isRoot}
65 | *
66 | * @return DirectoryInterface
67 | */
68 | public function getParent();
69 |
70 | /**
71 | * Returns whether the entree exists.
72 | *
73 | * @return bool
74 | */
75 | public function exists();
76 |
77 | /**
78 | * Delete the entree.
79 | */
80 | public function delete();
81 |
82 | /**
83 | * Copy the file/directory.
84 | *
85 | * By default, if the target already exists, it is only overridden if the source is newer.
86 | *
87 | * @param string $target Path to the target file.
88 | * @param bool|null $override Whether to override an existing file.
89 | * true = always override the target.
90 | * false = never override the target.
91 | * null = only override the target if the source is newer.
92 | */
93 | public function copy($target, $override = null);
94 |
95 | /**
96 | * Returns whether the entree is a directory.
97 | *
98 | * @return bool
99 | */
100 | public function isDir();
101 |
102 | /**
103 | * Returns whether the entree is a file.
104 | *
105 | * @return bool
106 | */
107 | public function isFile();
108 |
109 | /**
110 | * Returns whether the entree is a image.
111 | *
112 | * @return bool
113 | */
114 | public function isImage();
115 |
116 | /**
117 | * Returns whether the entree is a document.
118 | *
119 | * @return bool
120 | */
121 | public function isDocument();
122 |
123 | /**
124 | * Returns the entree's type (file|dir|image|document).
125 | *
126 | * @return string
127 | */
128 | public function getType();
129 |
130 | /**
131 | * Returns the file extension.
132 | *
133 | * @return string
134 | */
135 | public function getExtension();
136 |
137 | /**
138 | * Returns the entree's directory's path.
139 | *
140 | * @return string
141 | */
142 | public function getDirname();
143 |
144 | /**
145 | * Returns the filename.
146 | *
147 | * @param string $suffix If the filename ends in suffix this will also be cut off
148 | *
149 | * @return string
150 | */
151 | public function getFilename($suffix = null);
152 |
153 | /**
154 | * Returns the entree's timestamp.
155 | *
156 | * @return int unix timestamp
157 | */
158 | public function getTimestamp();
159 |
160 | /**
161 | * Returns the entree's timestamp as a Carbon instance.
162 | *
163 | * @return Carbon The Carbon instance.
164 | */
165 | public function getCarbon();
166 |
167 | /**
168 | * Returns whether the entree's visibility is public.
169 | *
170 | * @return bool
171 | */
172 | public function isPublic();
173 |
174 | /**
175 | * Returns whether the entree's visibility is private.
176 | *
177 | * @return bool
178 | */
179 | public function isPrivate();
180 |
181 | /**
182 | * Returns the entree's visibility (public|private).
183 | *
184 | * @return string
185 | */
186 | public function getVisibility();
187 |
188 | /**
189 | * Set the visibility.
190 | *
191 | * @param string $visibility One of 'public' or 'private'.
192 | */
193 | public function setVisibility($visibility);
194 | }
195 |
--------------------------------------------------------------------------------
/src/Adapter/Local.php:
--------------------------------------------------------------------------------
1 | applyPathPrefix($path);
46 | $mimetype = Util::guessMimeType($path, $contents);
47 |
48 | if (!is_writable($location)) {
49 | return false;
50 | }
51 |
52 | if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
53 | return false;
54 | }
55 |
56 | $type = 'file';
57 |
58 | return compact('type', 'path', 'size', 'contents', 'mimetype');
59 | }
60 |
61 | /**
62 | * {@inheritdoc}
63 | */
64 | public function delete($path)
65 | {
66 | $location = $this->applyPathPrefix($path);
67 |
68 | if (!file_exists($location)) {
69 | throw new FileNotFoundException($path);
70 | }
71 |
72 | if (!is_writable($location)) {
73 | throw new IOException('File is not writable', $location);
74 | }
75 |
76 | try {
77 | return Thrower::call('unlink', $location);
78 | } catch (\ErrorException $ex) {
79 | if (strpos($ex->getMessage(), "No such file or directory") !== false) {
80 | throw new FileNotFoundException($path, $ex);
81 | } else {
82 | throw new IOException('Could not remove file', $path);
83 | }
84 | }
85 | }
86 |
87 | /**
88 | * {@inheritdoc}
89 | */
90 | public function createDir($dirname, Config $config)
91 | {
92 | $location = $this->applyPathPrefix($dirname);
93 | $umask = umask(0);
94 | $visibility = $config->get('visibility', 'public');
95 |
96 | if (!is_dir($location) && !@mkdir($location, $this->permissionMap['dir'][$visibility], true)) {
97 | $return = false;
98 | } else {
99 | $return = ['path' => $dirname, 'type' => 'dir'];
100 | }
101 |
102 | umask($umask);
103 |
104 | return $return;
105 | }
106 |
107 | /**
108 | * {@inheritdoc}
109 | */
110 | public function deleteDir($dirname)
111 | {
112 | $location = $this->applyPathPrefix($dirname);
113 | if (!is_dir($location) || !is_writable($location)) {
114 | return false;
115 | }
116 |
117 | return parent::deleteDir($dirname);
118 | }
119 |
120 | /**
121 | * {@inheritdoc}
122 | */
123 | public function getImageInfo($path)
124 | {
125 | $location = $this->applyPathPrefix($path);
126 |
127 | return Image\Info::createFromFile($location);
128 | }
129 |
130 | /**
131 | * {@inheritdoc}
132 | */
133 | public function includeFile($path, $once = true)
134 | {
135 | $location = $this->applyPathPrefix($path);
136 |
137 | try {
138 | return Thrower::call(__NAMESPACE__ . '\includeFile' . ($once ? 'Once' : ''), $location);
139 | } catch (\ErrorException $e) {
140 | throw new IncludeFileException($e->getMessage(), $path, 0, $e);
141 | }
142 | }
143 |
144 | /**
145 | * @inheritdoc
146 | */
147 | public function getMetadata($path)
148 | {
149 | $location = $this->applyPathPrefix($path);
150 |
151 | if (!file_exists($location)) {
152 | throw new FileNotFoundException($path);
153 | }
154 |
155 | $info = new \SplFileInfo($location);
156 |
157 | return $this->normalizeFileInfo($info);
158 | }
159 | }
160 |
161 | /**
162 | * Scope isolated include.
163 | *
164 | * Prevents access to $this/self from included files.
165 | *
166 | * @param string $file
167 | *
168 | * @return mixed
169 | */
170 | function includeFile($file)
171 | {
172 | /** @noinspection PhpIncludeInspection */
173 | return include $file;
174 | }
175 |
176 | /**
177 | * Scope isolated include_once.
178 | *
179 | * Prevents access to $this/self from included files.
180 | *
181 | * @param string $file
182 | *
183 | * @return mixed
184 | */
185 | function includeFileOnce($file)
186 | {
187 | /** @noinspection PhpIncludeInspection */
188 | return include_once $file;
189 | }
190 |
--------------------------------------------------------------------------------
/src/Iterator/RecursiveDirectoryIterator.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class RecursiveDirectoryIterator implements RecursiveIterator, SeekableIterator
18 | {
19 | /**
20 | * This mode sets keys to the format expected for globbing.
21 | *
22 | * Keys will have a leading slash and directories will have a trailing slash.
23 | *
24 | * Normal key:
25 | * foo_dir
26 | * foo_file
27 | *
28 | * Glob key:
29 | * /foo_dir/
30 | * /foo_file
31 | */
32 | const KEY_FOR_GLOB = 1;
33 |
34 | /** @var FilesystemInterface */
35 | protected $filesystem;
36 | /** @var string */
37 | protected $path;
38 | /** @var int */
39 | protected $mode;
40 |
41 | /** @var array */
42 | protected $contents = [];
43 | /** @var bool */
44 | protected $fetched = false;
45 |
46 | /** @var array contents for children */
47 | protected $children = [];
48 |
49 | /** @var int current position */
50 | protected $position = -1;
51 | /** @var string current path */
52 | protected $key = null;
53 | /** @var null|Directory|File */
54 | protected $current = null;
55 |
56 | /**
57 | * Constructor.
58 | *
59 | * @param FilesystemInterface $filesystem
60 | * @param string $path
61 | * @param int $mode
62 | */
63 | public function __construct(FilesystemInterface $filesystem, $path = '/', $mode = null)
64 | {
65 | $this->filesystem = $filesystem;
66 | $this->path = $path;
67 | $this->mode = $mode;
68 | }
69 |
70 | /**
71 | * {@inheritdoc}
72 | */
73 | public function valid()
74 | {
75 | return isset($this->contents[$this->position]);
76 | }
77 |
78 | /**
79 | * {@inheritdoc}
80 | */
81 | public function current()
82 | {
83 | return $this->current;
84 | }
85 |
86 | /**
87 | * {@inheritdoc}
88 | */
89 | public function key()
90 | {
91 | return $this->key;
92 | }
93 |
94 | /**
95 | * {@inheritdoc}
96 | */
97 | public function next()
98 | {
99 | $this->position++;
100 | $this->fetch();
101 | $this->setCurrent();
102 | }
103 |
104 | /**
105 | * {@inheritdoc}
106 | */
107 | public function seek($position)
108 | {
109 | $this->position = $position;
110 | $this->fetch();
111 | $this->setCurrent();
112 | }
113 |
114 | /**
115 | * {@inheritdoc}
116 | */
117 | public function rewind()
118 | {
119 | $this->position = -1;
120 | $this->next();
121 | }
122 |
123 | /**
124 | * {@inheritdoc}
125 | */
126 | public function hasChildren()
127 | {
128 | try {
129 | if (!$this->current || !$this->current->isDir()) {
130 | return false;
131 | }
132 | } catch (FileNotFoundException $e) {
133 | return false;
134 | }
135 |
136 | $path = $this->current->getFullPath();
137 | if (!isset($this->children[$path])) {
138 | $this->children[$path] = $this->doFetch($path);
139 | }
140 |
141 | return count($this->children[$path]) > 0;
142 | }
143 |
144 | /**
145 | * {@inheritdoc}
146 | */
147 | public function getChildren()
148 | {
149 | $path = $this->current->getFullPath();
150 | $it = new static($this->filesystem, $path);
151 |
152 | $it->contents = $this->children[$path];
153 | $it->fetched = true;
154 | $it->mode = $this->mode;
155 |
156 | return $it;
157 | }
158 |
159 | /**
160 | * Fetch contents once.
161 | */
162 | protected function fetch()
163 | {
164 | if (!$this->fetched) {
165 | $this->contents = $this->doFetch($this->path);
166 | $this->fetched = true;
167 | }
168 | }
169 |
170 | /**
171 | * Actually fetch the listing and return it.
172 | *
173 | * @param string $path
174 | *
175 | * @return Directory[]|File[]
176 | */
177 | protected function doFetch($path)
178 | {
179 | return $this->filesystem->listContents($path);
180 | }
181 |
182 | /**
183 | * Sets the current handler and path.
184 | */
185 | protected function setCurrent()
186 | {
187 | if (!isset($this->contents[$this->position])) {
188 | $this->current = null;
189 | $this->key = null;
190 |
191 | return;
192 | }
193 |
194 | $this->current = $this->contents[$this->position];
195 |
196 | $path = $this->current->getFullPath();
197 | if ($this->mode & static::KEY_FOR_GLOB) {
198 | // Glob code requires absolute paths, so prefix path
199 | // with leading slash, but not before mount point
200 | if (strpos($path, '://') > 0) {
201 | $path = str_replace('://', ':///', $path);
202 | } else {
203 | $path = '/' . ltrim($path, '/');
204 | }
205 | }
206 | $this->key = $path;
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/tests/Iterator/SortableIteratorTest.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class SortableIteratorTest extends IteratorTestCase
18 | {
19 | /** @var Filesystem */
20 | protected $filesystem;
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function setUp()
26 | {
27 | $this->filesystem = new Filesystem(new Local(__DIR__ . '/../'));
28 | }
29 |
30 | public function testConstructor()
31 | {
32 | try {
33 | new SortableIterator(new Iterator([]), 'foobar');
34 | $this->fail('__construct() throws an InvalidArgumentException exception if the mode is not valid');
35 | } catch (\Exception $e) {
36 | $this->assertInstanceOf('Bolt\Filesystem\Exception\InvalidArgumentException', $e, '__construct() throws an InvalidArgumentException exception if the mode is not valid');
37 | }
38 | }
39 |
40 | /**
41 | * @dataProvider getAcceptData
42 | */
43 | public function testAccept($mode, array $expected)
44 | {
45 | $filesystem = $this->getMockBuilder(Filesystem::class)
46 | ->setConstructorArgs([$this->filesystem->getAdapter()])
47 | ->setMethods(['getTimestamp'])
48 | ->getMock()
49 | ;
50 | $filesystem->method('getTimestamp')
51 | ->willReturnMap([
52 | ['fixtures/js/script.js', 1],
53 | ['fixtures/css/reset.css', 9],
54 | ['fixtures', 2],
55 | ['fixtures/css/old/old_style.css', 8],
56 | ['fixtures/base.css', 4],
57 | ['fixtures/css/style.css', 7],
58 | ['fixtures/css/old', 5],
59 | ['fixtures/js', 6],
60 | ['fixtures/css', 3],
61 | ])
62 | ;
63 |
64 | $iterator = new \ArrayIterator([
65 | new File($filesystem, 'fixtures/js/script.js'),
66 | new File($filesystem, 'fixtures/css/reset.css'),
67 | new File($filesystem, 'fixtures'),
68 | new File($filesystem, 'fixtures/css/old/old_style.css'),
69 | new File($filesystem, 'fixtures/base.css'),
70 | new File($filesystem, 'fixtures/css/style.css'),
71 | new File($filesystem, 'fixtures/css/old'),
72 | new File($filesystem, 'fixtures/js'),
73 | new File($filesystem, 'fixtures/css'),
74 | ]);
75 |
76 | $iterator = new SortableIterator($iterator, $mode);
77 | $this->assertOrderedIterator($expected, $iterator);
78 | }
79 |
80 | public function getAcceptData()
81 | {
82 | return [
83 | 'sort by name' => [
84 | SortableIterator::SORT_BY_NAME,
85 | [
86 | 'fixtures',
87 | 'fixtures/base.css',
88 | 'fixtures/css',
89 | 'fixtures/css/old',
90 | 'fixtures/css/old/old_style.css',
91 | 'fixtures/css/reset.css',
92 | 'fixtures/css/style.css',
93 | 'fixtures/js',
94 | 'fixtures/js/script.js',
95 | ]
96 | ],
97 | 'sort by type' => [
98 | SortableIterator::SORT_BY_TYPE,
99 | [
100 | 'fixtures',
101 | 'fixtures/css',
102 | 'fixtures/css/old',
103 | 'fixtures/js',
104 | 'fixtures/base.css',
105 | 'fixtures/css/old/old_style.css',
106 | 'fixtures/css/reset.css',
107 | 'fixtures/css/style.css',
108 | 'fixtures/js/script.js',
109 | ]
110 | ],
111 | 'sort by time' => [
112 | SortableIterator::SORT_BY_TIME,
113 | [
114 | 'fixtures/js/script.js',
115 | 'fixtures',
116 | 'fixtures/css',
117 | 'fixtures/base.css',
118 | 'fixtures/css/old',
119 | 'fixtures/js',
120 | 'fixtures/css/style.css',
121 | 'fixtures/css/old/old_style.css',
122 | 'fixtures/css/reset.css',
123 |
124 | ]
125 | ],
126 | 'sort by call' => [
127 | function (HandlerInterface $a, HandlerInterface $b) {
128 | return strcmp($a->getPath(), $b->getPath());
129 | },
130 | [
131 | 'fixtures',
132 | 'fixtures/base.css',
133 | 'fixtures/css',
134 | 'fixtures/css/old',
135 | 'fixtures/css/old/old_style.css',
136 | 'fixtures/css/reset.css',
137 | 'fixtures/css/style.css',
138 | 'fixtures/js',
139 | 'fixtures/js/script.js',
140 | ]
141 | ],
142 | ];
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/Handler/BaseHandler.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | abstract class BaseHandler implements HandlerInterface
18 | {
19 | use MountPointAwareTrait;
20 |
21 | /** @var FilesystemInterface */
22 | protected $filesystem;
23 | /** @var string */
24 | protected $path;
25 |
26 | /**
27 | * Constructor.
28 | *
29 | * @param FilesystemInterface $filesystem
30 | * @param null $path
31 | */
32 | public function __construct(FilesystemInterface $filesystem = null, $path = null)
33 | {
34 | if ($path !== null && !is_string($path)) {
35 | throw new InvalidArgumentException('Path given must be a string.');
36 | }
37 |
38 | $this->filesystem = $filesystem;
39 | $this->path = $path;
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function setFilesystem(FilesystemInterface $filesystem)
46 | {
47 | $this->filesystem = $filesystem;
48 | }
49 |
50 | /**
51 | * {@inheritdoc}
52 | */
53 | public function getFilesystem()
54 | {
55 | return $this->filesystem;
56 | }
57 |
58 | /**
59 | * {@inheritdoc}
60 | */
61 | public function setPath($path)
62 | {
63 | if (!is_string($path)) {
64 | throw new InvalidArgumentException('Path given must be a string.');
65 | }
66 |
67 | $this->path = $path;
68 | }
69 |
70 | /**
71 | * {@inheritdoc}
72 | */
73 | public function getPath()
74 | {
75 | return $this->path;
76 | }
77 |
78 | /**
79 | * {@inheritdoc}
80 | */
81 | public function getFullPath()
82 | {
83 | return (!empty($this->mountPoint) ? $this->mountPoint . '://' : '') . $this->path;
84 | }
85 |
86 | /**
87 | * {@inheritdoc}
88 | */
89 | public function getParent()
90 | {
91 | return $this->filesystem->getDir($this->getDirname());
92 | }
93 |
94 | /**
95 | * {@inheritdoc}
96 | */
97 | public function getExtension()
98 | {
99 | return pathinfo($this->path, PATHINFO_EXTENSION);
100 | }
101 |
102 | /**
103 | * {@inheritdoc}
104 | */
105 | public function getDirname()
106 | {
107 | return Util::dirname($this->path);
108 | }
109 |
110 | /**
111 | * {@inheritdoc}
112 | */
113 | public function getFilename($suffix = null)
114 | {
115 | return basename($this->path, $suffix);
116 | }
117 |
118 | /**
119 | * Returns whether the entry exists.
120 | *
121 | * @return bool
122 | */
123 | public function exists()
124 | {
125 | return $this->filesystem->has($this->path);
126 | }
127 |
128 | /**
129 | * {@inheritdoc}
130 | */
131 | public function isDir()
132 | {
133 | return $this->getType() === 'dir';
134 | }
135 |
136 | /**
137 | * {@inheritdoc}
138 | */
139 | public function isFile()
140 | {
141 | return !$this->isDir();
142 | }
143 |
144 | /**
145 | * {@inheritdoc}
146 | */
147 | public function isImage()
148 | {
149 | return $this->getType() === 'image';
150 | }
151 |
152 | /**
153 | * {@inheritdoc}
154 | */
155 | public function isDocument()
156 | {
157 | return $this->getType() === 'document';
158 | }
159 |
160 | /**
161 | * {@inheritdoc}
162 | */
163 | public function getType()
164 | {
165 | return $this->filesystem->getType($this->path);
166 | }
167 |
168 | /**
169 | * {@inheritdoc}
170 | */
171 | public function getTimestamp()
172 | {
173 | return $this->filesystem->getTimestamp($this->path);
174 | }
175 |
176 | /**
177 | * {@inheritdoc}
178 | */
179 | public function getCarbon()
180 | {
181 | return Carbon::createFromTimestamp($this->getTimestamp());
182 | }
183 |
184 | /**
185 | * @inheritDoc
186 | */
187 | public function isPublic()
188 | {
189 | return $this->getVisibility() === 'public';
190 | }
191 |
192 | /**
193 | * @inheritDoc
194 | */
195 | public function isPrivate()
196 | {
197 | return $this->getVisibility() === 'private';
198 | }
199 |
200 | /**
201 | * {@inheritdoc}
202 | */
203 | public function getVisibility()
204 | {
205 | return $this->filesystem->getVisibility($this->path);
206 | }
207 |
208 | /**
209 | * {@inheritdoc}
210 | */
211 | public function setVisibility($visibility)
212 | {
213 | $this->filesystem->setVisibility($this->path, $visibility);
214 | }
215 |
216 | /**
217 | * Plugins pass-through.
218 | *
219 | * @param string $method
220 | * @param array $arguments
221 | *
222 | * @throws BadMethodCallException
223 | *
224 | * @return mixed
225 | */
226 | public function __call($method, array $arguments)
227 | {
228 | array_unshift($arguments, $this->path);
229 | $callback = [$this->filesystem, $method];
230 |
231 | try {
232 | return call_user_func_array($callback, $arguments);
233 | } catch (\BadMethodCallException $e) {
234 | throw new BadMethodCallException(
235 | 'Call to undefined method '
236 | . get_called_class()
237 | . '::' . $method
238 | );
239 | }
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/tests/Iterator/GlobIteratorTest.php:
--------------------------------------------------------------------------------
1 | filesystem = new Filesystem(new Local($this->tempDir));
23 |
24 | (new Symfony\Filesystem())->mirror($this->rootDir . '/tests/fixtures', $this->tempDir);
25 | }
26 |
27 | public function testIterate()
28 | {
29 | $iterator = new GlobIterator($this->filesystem, '/*.css');
30 |
31 | $this->assertIterator(
32 | [
33 | 'base.css',
34 | ],
35 | $iterator
36 | );
37 | }
38 |
39 | public function testIterateEscaped()
40 | {
41 | if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
42 | $this->markTestSkipped('A "*" in filenames is not supported on Windows.');
43 |
44 | return;
45 | }
46 |
47 | touch($this->tempDir . '/css/style*.css');
48 |
49 | $iterator = new GlobIterator($this->filesystem, '/css/style\\*.css');
50 |
51 | $this->assertIterator(
52 | [
53 | 'css/style*.css',
54 | ],
55 | $iterator
56 | );
57 | }
58 |
59 | public function testIterateSpecialChars()
60 | {
61 | if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
62 | $this->markTestSkipped('A "*" in filenames is not supported on Windows.');
63 |
64 | return;
65 | }
66 |
67 | touch($this->tempDir . '/css/style*.css');
68 |
69 | $iterator = new GlobIterator($this->filesystem, '/css/style*.css');
70 |
71 | $this->assertIterator(
72 | [
73 | 'css/style*.css',
74 | 'css/style.css',
75 | ],
76 | $iterator
77 | );
78 | }
79 |
80 | public function testIterateDoubleWildcard()
81 | {
82 | $iterator = new GlobIterator($this->filesystem, '/**/*.css');
83 |
84 | $this->assertIterator(
85 | [
86 | 'base.css',
87 | 'css/old/old_style.css',
88 | 'css/reset.css',
89 | 'css/style.css',
90 | ],
91 | $iterator
92 | );
93 | }
94 |
95 | public function testIterateSingleDirectory()
96 | {
97 | $iterator = new GlobIterator($this->filesystem, '/css');
98 |
99 | $this->assertIterator(
100 | [
101 | 'css',
102 | ],
103 | $iterator
104 | );
105 | }
106 |
107 | public function testIterateSingleFile()
108 | {
109 | $iterator = new GlobIterator($this->filesystem, '/css/style.css');
110 |
111 | $this->assertIterator(
112 | [
113 | 'css/style.css',
114 | ],
115 | $iterator
116 | );
117 | }
118 |
119 | public function testIterateSingleFileInDirectoryWithUnreadableFiles()
120 | {
121 | $iterator = new GlobIterator($this->filesystem, '');
122 |
123 | $this->assertIterator([''], $iterator);
124 | }
125 |
126 | public function testWildcardMayMatchZeroCharacters()
127 | {
128 | $iterator = new GlobIterator($this->filesystem, '/*css');
129 |
130 | $this->assertIterator(
131 | [
132 | 'base.css',
133 | 'css',
134 | ],
135 | $iterator
136 | );
137 | }
138 |
139 | public function testDoubleWildcardMayMatchZeroCharacters()
140 | {
141 | $iterator = new GlobIterator($this->filesystem, '/**/*css');
142 |
143 | $this->assertIterator(
144 | [
145 | 'base.css',
146 | 'css', // This one
147 | 'css/old/old_style.css',
148 | 'css/reset.css',
149 | 'css/style.css',
150 | ],
151 | $iterator
152 | );
153 | }
154 |
155 | public function testWildcardInRoot()
156 | {
157 | $iterator = new GlobIterator($this->filesystem, '/*');
158 |
159 | $this->assertIterator(
160 | [
161 | 'base.css',
162 | 'css',
163 | 'images',
164 | 'js',
165 | ],
166 | $iterator
167 | );
168 | }
169 |
170 | public function testDoubleWildcardInRoot()
171 | {
172 | $iterator = new GlobIterator($this->filesystem, '/**/*');
173 |
174 | $this->assertIterator(
175 | [
176 | 'base.css',
177 | 'css',
178 | 'css/old',
179 | 'css/old/old_style.css',
180 | 'css/reset.css',
181 | 'css/style.css',
182 | 'images',
183 | 'images/1-top-left.jpg',
184 | 'images/2-top-right.jpg',
185 | 'images/3-bottom-right.jpg',
186 | 'images/4-bottom-left.jpg',
187 | 'images/5-left-top.jpg',
188 | 'images/6-right-top.jpg',
189 | 'images/7-right-bottom.jpg',
190 | 'images/8-left-bottom.jpg',
191 | 'images/nut.svg',
192 | 'js',
193 | 'js/script.js',
194 | ],
195 | $iterator
196 | );
197 | }
198 |
199 | public function testNoMatches()
200 | {
201 | $iterator = new GlobIterator($this->filesystem, '/foo*');
202 |
203 | $this->assertIterator([], $iterator);
204 | }
205 |
206 | public function testNonExistingBaseDirectory()
207 | {
208 | $iterator = new GlobIterator($this->filesystem, '/foo/*');
209 |
210 | $this->assertIterator([], $iterator);
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/Adapter/S3.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class S3 extends AwsS3Adapter
18 | {
19 | /**
20 | * @inheritdoc
21 | *
22 | * Fix to handle bucket for empty paths and directories.
23 | */
24 | public function getMetadata($path)
25 | {
26 | $dirResult = [
27 | 'type' => 'dir',
28 | 'path' => $path,
29 | 'timestamp' => 0,
30 | ];
31 |
32 | $location = $this->applyPathPrefix($path);
33 |
34 | if ($location === '') {
35 | $command = $this->s3Client->getCommand(
36 | 'headBucket',
37 | [
38 | 'Bucket' => $this->bucket,
39 | ]
40 | );
41 | } else {
42 | $command = $this->s3Client->getCommand(
43 | 'headObject',
44 | [
45 | 'Bucket' => $this->bucket,
46 | 'Key' => $location,
47 | ]
48 | );
49 | }
50 |
51 | /* @var Result $result */
52 | try {
53 | $result = $this->s3Client->execute($command);
54 | } catch (S3Exception $exception) {
55 | $response = $exception->getResponse();
56 |
57 | if ($response !== null && $response->getStatusCode() === 404) {
58 | /*
59 | * Path could be a directory. If so, return enough info to treat it as a directory.
60 | * We should really never get here and the path not be a directory, since existence
61 | * has already been verified.
62 | */
63 | if ($this->doesDirectoryExist($location)) {
64 | return $dirResult;
65 | }
66 | }
67 |
68 | throw $exception;
69 | }
70 |
71 | /*
72 | * Root paths may not throw an exception because:
73 | * - headBucket() always has a valid response (since existence has already been verified).
74 | * - Applying prefix to empty path will result in a trailing slash. If an exception is not
75 | * thrown for this object it is a fake directory (see createDir()).
76 | *
77 | * Both of these cases mean the path is a directory. We return that here,
78 | * because empty paths aren't handled correctly by normalizeResponse.
79 | */
80 | if ($path === '') {
81 | return $dirResult;
82 | }
83 |
84 | return $this->normalizeResponse($result->toArray(), $path);
85 | }
86 |
87 | /**
88 | * @inheritdoc
89 | *
90 | * Fix to check if bucket existence for empty paths.
91 | */
92 | public function has($path)
93 | {
94 | $location = $this->applyPathPrefix($path);
95 |
96 | if ($location === '') {
97 | return $this->s3Client->doesBucketExist($this->bucket);
98 | }
99 |
100 | if ($this->s3Client->doesObjectExist($this->bucket, $location)) {
101 | return true;
102 | }
103 |
104 | return $this->doesDirectoryExist($location);
105 | }
106 |
107 | /**
108 | * @inheritdoc
109 | *
110 | * Fix to use "getBucketAcl" if path is empty.
111 | * Also to check if location is directory, and if so
112 | * return "public" since directories don't have ACL.
113 | */
114 | protected function getRawVisibility($path)
115 | {
116 | $location = $this->applyPathPrefix($path);
117 | if ($location === '') {
118 | $command = $this->s3Client->getCommand(
119 | 'getBucketAcl',
120 | [
121 | 'Bucket' => $this->bucket,
122 | ]
123 | );
124 | } else {
125 | $command = $this->s3Client->getCommand(
126 | 'getObjectAcl',
127 | [
128 | 'Bucket' => $this->bucket,
129 | 'Key' => $location,
130 | ]
131 | );
132 | }
133 |
134 | try {
135 | $result = $this->s3Client->execute($command);
136 | } catch (S3Exception $e) {
137 | $response = $e->getResponse();
138 |
139 | if ($response !== null && $response->getStatusCode() === 404) {
140 | /*
141 | * Path could be a directory. If so, return "public" since directories don't have ACL.
142 | * We should really never get here and the path not be a directory, since existence
143 | * has already been verified.
144 | */
145 | if ($this->doesDirectoryExist($location)) {
146 | return AdapterInterface::VISIBILITY_PUBLIC;
147 | }
148 | }
149 |
150 | throw $e;
151 | }
152 |
153 | /*
154 | * See note in getMetadata().
155 | *
156 | * TODO We say buckets are always public since we treat them like directories, which we say are public.
157 | * But buckets actually have visibility. Should we use that instead of assuming it is public?
158 | */
159 | if ($path === '') {
160 | return AdapterInterface::VISIBILITY_PUBLIC;
161 | }
162 |
163 | $visibility = AdapterInterface::VISIBILITY_PRIVATE;
164 |
165 | foreach ($result->get('Grants') as $grant) {
166 | if (
167 | isset($grant['Grantee']['URI'])
168 | && $grant['Grantee']['URI'] === self::PUBLIC_GRANT_URI
169 | && $grant['Permission'] === 'READ'
170 | ) {
171 | $visibility = AdapterInterface::VISIBILITY_PUBLIC;
172 | break;
173 | }
174 | }
175 |
176 | return $visibility;
177 | }
178 |
179 | /**
180 | * @inheritdoc
181 | *
182 | * Fix to return empty string if path and pathPrefix are the same.
183 | */
184 | public function removePathPrefix($path)
185 | {
186 | $pathPrefix = $this->getPathPrefix();
187 |
188 | if ($pathPrefix === null) {
189 | return $path;
190 | }
191 |
192 | if ($path === $pathPrefix) {
193 | return '';
194 | }
195 |
196 | return substr($path, strlen($pathPrefix));
197 | }
198 |
199 | /**
200 | * @inheritdoc
201 | *
202 | * Only call Util::getStreamSize if $body is a resource.
203 | * Fixed prefix not being removed from response.
204 | * Guess mime type even when body is a resource.
205 | */
206 | protected function upload($path, $body, Config $config)
207 | {
208 | $key = $this->applyPathPrefix($path);
209 | $options = $this->getOptionsFromConfig($config);
210 | $acl = isset($options['ACL']) ? $options['ACL'] : 'private';
211 |
212 | if (! isset($options['ContentType'])) {
213 | $options['ContentType'] = Util::guessMimeType($path, $body);
214 | }
215 |
216 | if (! isset($options['ContentLength'])) {
217 | $options['ContentLength'] = is_string($body) ? Util::contentSize($body) : (is_resource($body) ? Util::getStreamSize($body) : null);
218 | }
219 |
220 | if ($options['ContentLength'] === null) {
221 | unset($options['ContentLength']);
222 | }
223 |
224 | $this->s3Client->upload($this->bucket, $key, $body, $acl, ['params' => $options]);
225 |
226 | return $this->normalizeResponse($options, $path);
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/tests/Handler/FileTest.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class FileTest extends FilesystemTestCase
17 | {
18 | /** @var FilesystemInterface */
19 | protected $filesystem;
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | protected function setUp()
25 | {
26 | parent::setUp();
27 | $this->filesystem = new Filesystem(new Local(__DIR__ . '/../'));
28 | }
29 |
30 | public function testConstruct()
31 | {
32 | $file = new File($this->filesystem, 'fixtures/images/2-top-right.jpg');
33 | $this->assertInstanceOf('Bolt\Filesystem\Handler\File', $file);
34 |
35 | $filesystem = new Filesystem(new Local(__DIR__));
36 | $file = new File($filesystem);
37 | $this->assertInstanceOf('Bolt\Filesystem\Handler\File', $file);
38 | }
39 |
40 | public function testSetFilesystem()
41 | {
42 | $file = new File($this->filesystem, 'fixtures/images/2-top-right.jpg');
43 | $filesystem = new Filesystem(new Local(__DIR__));
44 | $file->setFilesystem($filesystem);
45 | $this->assertInstanceOf('Bolt\Filesystem\Filesystem', $file->getFilesystem());
46 | }
47 |
48 | public function testGetMimeType()
49 | {
50 | $file = new File($this->filesystem, 'fixtures/images/2-top-right.jpg');
51 | $this->assertSame('image/jpeg', $file->getMimeType());
52 | }
53 |
54 | public function testGetVisibility()
55 | {
56 | $file = new File($this->filesystem, 'fixtures/images/2-top-right.jpg');
57 | $this->assertSame('public', $file->getVisibility());
58 | }
59 |
60 | public function testGetType()
61 | {
62 | $file = new File($this->filesystem, 'fixtures/images/2-top-right.jpg');
63 | $this->assertSame('image', $file->getType());
64 | }
65 |
66 | public function testGetSize()
67 | {
68 | $file = new File($this->filesystem, 'fixtures/images/2-top-right.jpg');
69 | $this->assertSame(7023, $file->getSize());
70 | }
71 |
72 | public function testGetSizeFormatted()
73 | {
74 | $file = new File($this->filesystem, 'fixtures/images/2-top-right.jpg');
75 | $this->assertSame('6.86 KiB', $file->getSizeFormatted());
76 | $this->assertSame('7.0 KB', $file->getSizeFormatted(true));
77 | }
78 |
79 | public function testReadStream()
80 | {
81 | $file = new File($this->filesystem, 'fixtures/base.css');
82 | $stream = $file->readStream();
83 | $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $stream);
84 | $this->assertRegExp('/koala/', (string) $stream);
85 | $this->assertRegExp('/color: grey;/', (string) $stream);
86 | $this->assertRegExp('/width: 100%/', (string) $stream);
87 | }
88 |
89 | /**
90 | * @expectedException \Bolt\Filesystem\Exception\FileExistsException
91 | * @expectedExceptionMessage File already exists at path: temp/dropbear.log
92 | */
93 | public function testWrite()
94 | {
95 | $text = 'Attack of the drop bear';
96 | $file = new File($this->filesystem, 'temp/dropbear.log');
97 | $file->write($text);
98 |
99 | $newFile = new File($this->filesystem, 'temp/dropbear.log');
100 | $this->assertSame('Attack of the drop bear', $newFile->read());
101 |
102 | $newFile->write('anything');
103 | }
104 |
105 | /**
106 | * @expectedException \Bolt\Filesystem\Exception\FileExistsException
107 | * @expectedExceptionMessage File already exists at path: temp/base.css
108 | */
109 | public function testWriteStream()
110 | {
111 | $file = new File($this->filesystem, 'fixtures/base.css');
112 | $stream = $file->readStream();
113 |
114 | $newFile = new File($this->filesystem, 'temp/base.css');
115 | $newFile->writeStream($stream);
116 |
117 | $this->assertSame($file->read(), $newFile->read());
118 |
119 | $newFile->writeStream($stream);
120 | }
121 |
122 | public function testUpdate()
123 | {
124 | $path = 'temp/Spiderbait.txt';
125 |
126 | $file = new File($this->filesystem, $path);
127 |
128 | $file->write(null);
129 | $file->update('Buy me a pony');
130 |
131 | $newFile = new File($this->filesystem, $path);
132 | $this->assertSame('Buy me a pony', $newFile->read());
133 |
134 | $file = new File($this->filesystem, $path);
135 | $file->update('Calypso');
136 |
137 | $newFile = new File($this->filesystem, $path);
138 | $this->assertSame('Calypso', $newFile->read());
139 | }
140 |
141 | public function testUpdateStream()
142 | {
143 | $file = new File($this->filesystem, 'fixtures/base.css');
144 | $stream = $file->readStream();
145 |
146 | $newFile = new File($this->filesystem, 'temp/koala.css');
147 | $newFile->write(null);
148 | $newFile->updateStream($stream);
149 |
150 | $this->assertSame($file->read(), $newFile->read());
151 | }
152 |
153 | public function testPut()
154 | {
155 | $path = 'temp/SilversunPickups.txt';
156 |
157 | $file = new File($this->filesystem, $path);
158 | $this->assertFalse($file->exists());
159 | $file->write(null);
160 | $file->put('Nightlight');
161 |
162 | $newFile = new File($this->filesystem, $path);
163 | $this->assertSame('Nightlight', $newFile->read());
164 |
165 | $file = new File($this->filesystem, $path);
166 | $this->assertTrue($file->exists());
167 | $file->put("It's nice to know you work alone");
168 |
169 | $newFile = new File($this->filesystem, $path);
170 | $this->assertSame("It's nice to know you work alone", $newFile->read());
171 | }
172 |
173 | public function testPutStream()
174 | {
175 | $file = new File($this->filesystem, 'fixtures/base.css');
176 | $stream = $file->readStream();
177 |
178 | $newFile = new File($this->filesystem, 'temp/koala.css');
179 | $this->assertFalse($newFile->exists());
180 | $newFile->putStream($stream);
181 |
182 | $this->assertSame($file->read(), $newFile->read());
183 | }
184 |
185 | public function testRename()
186 | {
187 | $pathOld = 'temp/the-file-formerly-known-as.txt';
188 | $pathNew = 'temp/the-file.txt';
189 |
190 | $file = new File($this->filesystem, $pathOld);
191 | $this->assertFalse($file->exists());
192 | $file->write('Writing tests is so much fun… everyone should do it!');
193 | $file->rename($pathNew);
194 |
195 | $newFile = new File($this->filesystem, $pathNew);
196 | $this->assertSame('Writing tests is so much fun… everyone should do it!', $newFile->read());
197 | }
198 |
199 | public function testCopy()
200 | {
201 | $file = new File($this->filesystem, 'fixtures/base.css');
202 | $file->copy('temp/drop-the-base.css');
203 |
204 | $newFile = new File($this->filesystem, 'temp/drop-the-base.css');
205 | $this->assertSame($file->read(), $newFile->read());
206 | }
207 |
208 | public function testDelete()
209 | {
210 | $file = new File($this->filesystem, 'fixtures/base.css');
211 | $file->copy('temp/drop-the-base.css');
212 |
213 | $newFile = new File($this->filesystem, 'temp/drop-the-base.css');
214 | $this->assertSame($file->read(), $newFile->read());
215 | $newFile->delete();
216 | $this->assertFalse($newFile->exists());
217 |
218 | $newNewFile = new File($this->filesystem, 'temp/drop-the-base.css');
219 | $this->assertFalse($newNewFile->exists());
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/tests/Handler/Image/InfoTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class InfoTest extends TestCase
16 | {
17 | /** @var Filesystem */
18 | protected $filesystem;
19 |
20 | /**
21 | * {@inheritdoc}
22 | */
23 | protected function setUp()
24 | {
25 | $this->filesystem = new Filesystem(new Local(__DIR__ . '/../../'));
26 | }
27 |
28 | public function testConstruct()
29 | {
30 | $exif = new Image\Exif([]);
31 | $type = Image\Type::getById(IMAGETYPE_JPEG);
32 | new Image\Info(new Image\Dimensions(1024, 768), $type, 2, 7, 'Marcel Marceau', $exif);
33 | }
34 |
35 | public function testCreateFromFile()
36 | {
37 | $file = dirname(dirname(__DIR__)) . '/fixtures/images/1-top-left.jpg';
38 | $info = Image\Info::createFromFile($file);
39 |
40 | $this->assertInstanceOf(Image\Info::class, $info);
41 | $this->assertInstanceOf(Image\TypeInterface::class, $info->getType());
42 | $this->assertInstanceOf(Image\Exif::class, $info->getExif());
43 |
44 | $this->assertSame(400, $info->getWidth());
45 | $this->assertSame(200, $info->getHeight());
46 | $this->assertSame(8, $info->getBits());
47 | $this->assertSame(3, $info->getChannels());
48 | $this->assertSame('image/jpeg', $info->getMime());
49 | $this->assertSame(2, $info->getAspectRatio());
50 |
51 | $this->assertTrue($info->isLandscape());
52 | $this->assertFalse($info->isPortrait());
53 | $this->assertFalse($info->isSquare());
54 | $this->assertTrue($info->isValid());
55 | }
56 |
57 | public function testCreateFromFileEmpty()
58 | {
59 | $info = Image\Info::createFromFile(__DIR__ . '/../../fixtures2/empty.jpg');
60 |
61 | $this->assertSame(0, $info->getWidth());
62 | $this->assertSame(0, $info->getHeight());
63 | $this->assertSame(0, $info->getBits());
64 | $this->assertSame(0, $info->getChannels());
65 | $this->assertSame(null, $info->getMime());
66 | $this->assertSame(0.0, $info->getAspectRatio());
67 | $this->assertFalse($info->isValid());
68 | }
69 |
70 | public function testCreateFromFileInvalid()
71 | {
72 | $info = Image\Info::createFromFile('drop-bear.jpg');
73 |
74 | $this->assertFalse($info->isValid());
75 | }
76 |
77 | public function testCreateFromString()
78 | {
79 | $file = $this->filesystem->getFile('fixtures/images/1-top-left.jpg')->read();
80 | $info = Image\Info::createFromString($file);
81 |
82 | $this->assertInstanceOf(Image\Info::class, $info);
83 | $this->assertInstanceOf(Image\TypeInterface::class, $info->getType());
84 | $this->assertInstanceOf(Image\Exif::class, $info->getExif());
85 |
86 | $this->assertSame(400, $info->getWidth());
87 | $this->assertSame(200, $info->getHeight());
88 | $this->assertSame(8, $info->getBits());
89 | $this->assertSame(3, $info->getChannels());
90 | $this->assertSame('image/jpeg', $info->getMime());
91 | $this->assertSame(2, $info->getAspectRatio());
92 |
93 | $this->assertTrue($info->isLandscape());
94 | $this->assertFalse($info->isPortrait());
95 | $this->assertFalse($info->isSquare());
96 | $this->assertTrue($info->isValid());
97 | }
98 |
99 | public function testCreateFromStringEmpty()
100 | {
101 | $file = $this->filesystem->getFile('fixtures2/empty.jpg');
102 |
103 | $info = Image\Info::createFromString($file->read(), $file->getPath());
104 |
105 | $this->assertSame(0, $info->getWidth());
106 | $this->assertSame(0, $info->getHeight());
107 | $this->assertSame(0, $info->getBits());
108 | $this->assertSame(0, $info->getChannels());
109 | $this->assertSame(null, $info->getMime());
110 | $this->assertSame(0.0, $info->getAspectRatio());
111 | $this->assertFalse($info->isValid());
112 | }
113 |
114 | public function testCreateFromStringInvalid()
115 | {
116 | $info = Image\Info::createFromString('drop-bear.jpg');
117 |
118 | $this->assertFalse($info->isValid());
119 | }
120 |
121 | public function testClone()
122 | {
123 | $file = $this->filesystem->getFile('fixtures/images/1-top-left.jpg')->read();
124 | $info = Image\Info::createFromString($file);
125 | $clone = clone $info;
126 |
127 | $this->assertNotSame($clone->getExif(), $info->getExif());
128 | }
129 |
130 | public function testSerialize()
131 | {
132 | $file = $this->filesystem->getFile('fixtures/images/1-top-left.jpg')->read();
133 | $expected = Image\Info::createFromString($file);
134 | /** @var Image\Info $actual */
135 | $actual = unserialize(serialize($expected));
136 |
137 | $this->assertInstanceOf(Image\Info::class, $actual);
138 | $this->assertEquals($expected->getDimensions(), $actual->getDimensions());
139 | $this->assertSame($expected->getType(), $actual->getType());
140 | $this->assertSame($expected->getBits(), $actual->getBits());
141 | $this->assertSame($expected->getChannels(), $actual->getChannels());
142 | $this->assertSame($expected->getMime(), $actual->getMime());
143 | $this->assertEquals($expected->getExif()->getData(), $actual->getExif()->getData());
144 | $this->assertSame($expected->isValid(), $actual->isValid());
145 | }
146 |
147 | public function testJsonSerialize()
148 | {
149 | $file = $this->filesystem->getFile('fixtures/images/1-top-left.jpg')->read();
150 | $expected = Image\Info::createFromString($file);
151 | $actual = Image\Info::createFromJson(json_decode(json_encode($expected), true));
152 |
153 | $this->assertEquals($expected->getDimensions(), $actual->getDimensions());
154 | $this->assertSame($expected->getType(), $actual->getType());
155 | $this->assertSame($expected->getBits(), $actual->getBits());
156 | $this->assertSame($expected->getChannels(), $actual->getChannels());
157 | $this->assertSame($expected->getMime(), $actual->getMime());
158 | $this->assertEquals($expected->getExif()->getData(), $actual->getExif()->getData());
159 | $this->assertSame($expected->isValid(), $actual->isValid());
160 | }
161 |
162 | public function testSvgFromString()
163 | {
164 | $file = $this->filesystem->getFile('fixtures/images/nut.svg')->read();
165 | $info = Image\Info::createFromString($file);
166 |
167 | $this->assertSame(1000, $info->getWidth());
168 | $this->assertSame(1000, $info->getHeight());
169 | $this->assertSame('image/svg+xml', $info->getMime());
170 | $this->assertTrue($info->isValid());
171 | $this->assertInstanceOf(Image\SvgType::class, $info->getType());
172 | }
173 |
174 | public function testSvgFromFile()
175 | {
176 | $info = Image\Info::createFromFile(__DIR__ . '/../../fixtures/images/nut.svg');
177 |
178 | $this->assertSame(1000, $info->getWidth());
179 | $this->assertSame(1000, $info->getHeight());
180 | $this->assertSame('image/svg+xml', $info->getMime());
181 | $this->assertTrue($info->isValid());
182 | $this->assertInstanceOf(Image\SvgType::class, $info->getType());
183 | }
184 |
185 | public function testSvgWithoutXmlDeclaration()
186 | {
187 | $file = $this->filesystem->getFile('fixtures/images/nut.svg');
188 | $data = $file->read();
189 | $data = substr($data, 39);
190 | $info = Image\Info::createFromString($data);
191 |
192 | $this->assertSame(1000, $info->getWidth());
193 | $this->assertSame(1000, $info->getHeight());
194 | $this->assertSame('image/svg+xml', $info->getMime());
195 | $this->assertTrue($info->isValid());
196 | $this->assertInstanceOf(Image\SvgType::class, $info->getType());
197 | }
198 |
199 | public function testReadExif()
200 | {
201 | $info = Image\Info::createFromFile(__DIR__ . '/../../fixtures2/empty.jpg');
202 |
203 | $m = new \ReflectionMethod(Image\Info::class,'readExif');
204 | $m->setAccessible(true);
205 |
206 | $exif = $m->invoke($info, __DIR__ . '/../../fixtures2/empty.jpg');
207 | $this->assertInstanceOf(Image\Exif::class, $exif);
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/FilesystemInterface.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | interface FilesystemInterface extends Capability\ImageInfo, Capability\IncludeFile
23 | {
24 | /**
25 | * Check whether a file exists.
26 | *
27 | * @param string $path The path to the file.
28 | *
29 | * @return bool
30 | */
31 | public function has($path);
32 |
33 | /**
34 | * Read a file.
35 | *
36 | * @param string $path The path to the file.
37 | *
38 | * @throws FileNotFoundException
39 | * @throws IOException
40 | *
41 | * @return string
42 | */
43 | public function read($path);
44 |
45 | /**
46 | * Retrieves a read-stream for a path.
47 | *
48 | * @param string $path The path to the file.
49 | *
50 | * @throws FileNotFoundException
51 | * @throws IOException
52 | *
53 | * @return StreamInterface
54 | */
55 | public function readStream($path);
56 |
57 | /**
58 | * Write a new file.
59 | *
60 | * @param string $path The path of the new file.
61 | * @param string $contents The file contents.
62 | * @param array $config An optional configuration array.
63 | *
64 | * @throws FileExistsException
65 | * @throws IOException
66 | */
67 | public function write($path, $contents, $config = []);
68 |
69 | /**
70 | * Write a new file using a stream.
71 | *
72 | * @param string $path The path of the new file.
73 | * @param StreamInterface|resource $resource The stream or resource.
74 | * @param array $config An optional configuration array.
75 | *
76 | * @throws InvalidArgumentException If $resource is not a StreamInterface or file handle.
77 | * @throws FileExistsException
78 | * @throws IOException
79 | */
80 | public function writeStream($path, $resource, $config = []);
81 |
82 | /**
83 | * Update an existing file.
84 | *
85 | * @param string $path The path of the existing file.
86 | * @param string $contents The file contents.
87 | * @param array $config An optional configuration array.
88 | *
89 | * @throws FileNotFoundException
90 | * @throws IOException
91 | */
92 | public function update($path, $contents, $config = []);
93 |
94 | /**
95 | * Update an existing file using a stream.
96 | *
97 | * @param string $path The path of the existing file.
98 | * @param StreamInterface|resource $resource The stream or resource.
99 | * @param array $config An optional configuration array.
100 | *
101 | * @throws InvalidArgumentException If $resource is not a StreamInterface or file handle.
102 | * @throws FileNotFoundException
103 | * @throws IOException
104 | */
105 | public function updateStream($path, $resource, $config = []);
106 |
107 | /**
108 | * Create a file or update if exists.
109 | *
110 | * @param string $path The path to the file.
111 | * @param string $contents The file contents.
112 | * @param array $config An optional configuration array.
113 | *
114 | * @throws IOException
115 | */
116 | public function put($path, $contents, $config = []);
117 |
118 | /**
119 | * Create a file or update if exists.
120 | *
121 | * @param string $path The path to the file.
122 | * @param StreamInterface|resource $resource The stream or resource.
123 | * @param array $config An optional configuration array.
124 | *
125 | * @throws InvalidArgumentException If $resource is not a StreamInterface or file handle.
126 | * @throws IOException
127 | */
128 | public function putStream($path, $resource, $config = []);
129 |
130 | /**
131 | * Read and delete a file.
132 | *
133 | * @param string $path The path to the file.
134 | *
135 | * @throws FileNotFoundException
136 | * @throws IOException
137 | *
138 | * @return string
139 | */
140 | public function readAndDelete($path);
141 |
142 | /**
143 | * Rename a file.
144 | *
145 | * @param string $path Path to the existing file.
146 | * @param string $newPath The new path of the file.
147 | *
148 | * @throws FileExistsException Thrown if $newPath exists.
149 | * @throws FileNotFoundException Thrown if $path does not exist.
150 | * @throws IOException
151 | */
152 | public function rename($path, $newPath);
153 |
154 | /**
155 | * Copy a file.
156 | *
157 | * By default, if the target already exists, it is only overridden if the source is newer.
158 | *
159 | * @param string $origin Path to the original file.
160 | * @param string $target Path to the target file.
161 | * @param bool|null $override Whether to override an existing file.
162 | * true = always override the target.
163 | * false = never override the target.
164 | * null = only override the target if the source is newer.
165 | *
166 | * @throws FileNotFoundException Thrown if $path does not exist.
167 | * @throws IOException
168 | */
169 | public function copy($origin, $target, $override = null);
170 |
171 | /**
172 | * Delete a file.
173 | *
174 | * @param string $path
175 | *
176 | * @throws FileNotFoundException
177 | * @throws IOException
178 | */
179 | public function delete($path);
180 |
181 | /**
182 | * Delete a directory.
183 | *
184 | * @param string $dirname
185 | *
186 | * @throws RootViolationException Thrown if $dirname is empty.
187 | * @throws IOException
188 | */
189 | public function deleteDir($dirname);
190 |
191 | /**
192 | * Create a directory.
193 | *
194 | * @param string $dirname The name of the new directory.
195 | * @param array $config An optional configuration array.
196 | *
197 | * @throws IOException
198 | */
199 | public function createDir($dirname, $config = []);
200 |
201 | /**
202 | * Copies a directory and its contents to another.
203 | *
204 | * @param string $originDir The origin directory
205 | * @param string $targetDir The target directory
206 | * @param bool|null $override Whether to override an existing file.
207 | * true = always override the target.
208 | * false = never override the target.
209 | * null = only override the target if the source is newer.
210 | */
211 | public function copyDir($originDir, $targetDir, $override = null);
212 |
213 | /**
214 | * Mirrors a directory to another.
215 | *
216 | * Note: By default, this will delete files in target if they are not in source.
217 | *
218 | * @param string $originDir The origin directory
219 | * @param string $targetDir The target directory
220 | * @param array $config Valid options are:
221 | * - delete = Whether to delete files that are not in the source directory (default: true)
222 | * - override = See {@see copyDir}'s $override parameter for details (default: null)
223 | */
224 | public function mirror($originDir, $targetDir, $config = []);
225 |
226 | /**
227 | * Get a handler.
228 | *
229 | * @param string $path The path to the file.
230 | * @param HandlerInterface $handler An optional existing handler to populate.
231 | *
232 | * @throws IOException
233 | *
234 | * @return HandlerInterface
235 | */
236 | public function get($path, HandlerInterface $handler = null);
237 |
238 | /**
239 | * Get a file handler.
240 | *
241 | * @param string $path The path to the file.
242 | * @param FileInterface $handler An optional existing file handler to populate.
243 | *
244 | * @throws IOException
245 | *
246 | * @return FileInterface
247 | */
248 | public function getFile($path, FileInterface $handler = null);
249 |
250 | /**
251 | * Get a directory handler.
252 | *
253 | * @param string $path The path to the directory.
254 | *
255 | * @throws IOException
256 | *
257 | * @return DirectoryInterface
258 | */
259 | public function getDir($path);
260 |
261 | /**
262 | * Get a image handler.
263 | *
264 | * @param string $path The path to the file.
265 | *
266 | * @throws IOException
267 | *
268 | * @return ImageInterface
269 | */
270 | public function getImage($path);
271 |
272 | /**
273 | * Returns the type of the file.
274 | *
275 | * @param string $path The path to the file.
276 | *
277 | * @return string
278 | */
279 | public function getType($path);
280 |
281 | /**
282 | * Get a file's size.
283 | *
284 | * @param string $path The path to the file.
285 | *
286 | * @throws IOException
287 | *
288 | * @return int
289 | */
290 | public function getSize($path);
291 |
292 | /**
293 | * Get a file's unix timestamp.
294 | *
295 | * @param string $path The path to the file.
296 | *
297 | * @throws FileNotFoundException
298 | * @throws IOException
299 | *
300 | * @return string
301 | */
302 | public function getTimestamp($path);
303 |
304 | /**
305 | * Get a file's timestamp as a Carbon instance.
306 | *
307 | * @param string $path The path to the file.
308 | *
309 | * @throws FileNotFoundException
310 | * @throws IOException
311 | *
312 | * @return Carbon
313 | */
314 | public function getCarbon($path);
315 |
316 | /**
317 | * Get a file's MIME type.
318 | *
319 | * @param string $path The path to the file.
320 | *
321 | * @throws FileNotFoundException
322 | * @throws IOException
323 | *
324 | * @return string
325 | */
326 | public function getMimeType($path);
327 |
328 | /**
329 | * Get a file's visibility (public|private).
330 | *
331 | * @param string $path The path to the file.
332 | *
333 | * @throws FileNotFoundException
334 | * @throws IOException
335 | *
336 | * @return string
337 | */
338 | public function getVisibility($path);
339 |
340 | /**
341 | * Set the visibility for a file.
342 | *
343 | * @param string $path The path to the file.
344 | * @param string $visibility One of 'public' or 'private'.
345 | *
346 | * @throws IOException
347 | */
348 | public function setVisibility($path, $visibility);
349 |
350 | /**
351 | * List contents of a directory.
352 | *
353 | * @param string $directory The directory to list.
354 | * @param bool $recursive Whether to list recursively.
355 | *
356 | * @throws IOException
357 | *
358 | * @return HandlerInterface[]
359 | */
360 | public function listContents($directory = '', $recursive = false);
361 |
362 | /**
363 | * Returns a finder instance. Let's find some files!
364 | *
365 | * @return Finder
366 | */
367 | public function find();
368 |
369 | /**
370 | * Register a plugin.
371 | *
372 | * @param PluginInterface $plugin The plugin to register.
373 | */
374 | public function addPlugin(PluginInterface $plugin);
375 | }
376 |
--------------------------------------------------------------------------------
/src/Handler/Image/Info.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class Info implements JsonSerializable, Serializable
21 | {
22 | /** @var Dimensions */
23 | protected $dimensions;
24 | /** @var TypeInterface */
25 | protected $type;
26 | /** @var int */
27 | protected $bits;
28 | /** @var int */
29 | protected $channels;
30 | /** @var string */
31 | protected $mime;
32 | /** @var Exif */
33 | protected $exif;
34 | /** @var bool */
35 | protected $valid;
36 |
37 | /** @var ReaderInterface */
38 | protected static $exifReader;
39 |
40 | /**
41 | * Constructor.
42 | *
43 | * @param Dimensions $dimensions
44 | * @param TypeInterface $type
45 | * @param int $bits
46 | * @param int $channels
47 | * @param string $mime
48 | * @param Exif $exif
49 | */
50 | public function __construct(Dimensions $dimensions, TypeInterface $type, $bits, $channels, $mime, Exif $exif)
51 | {
52 | $this->dimensions = $dimensions;
53 | $this->type = $type;
54 | $this->bits = (int) $bits;
55 | $this->channels = (int) $channels;
56 | $this->mime = $mime;
57 | $this->exif = $exif;
58 | $this->valid = true;
59 | }
60 |
61 | /**
62 | * Creates an empty Info. Useful for when image does not exists to prevent null checks.
63 | *
64 | * @return Info
65 | *
66 | * @deprecated Use {@see createInvalid} instead.
67 | */
68 | public static function createEmpty()
69 | {
70 | return static::createInvalid();
71 | }
72 |
73 | /**
74 | * Creates an empty, invalid Info. Useful to prevent null checks for non-existent or invalid images.
75 | *
76 | * @return Info
77 | */
78 | public static function createInvalid()
79 | {
80 | $invalid = new static(new Dimensions(0, 0), Type::unknown(), 0, 0, null, new Exif([]));
81 | $invalid->valid = false;
82 |
83 | return $invalid;
84 | }
85 |
86 | /**
87 | * Creates an Info from a file.
88 | *
89 | * @param string $file A filepath
90 | *
91 | * @return Info
92 | */
93 | public static function createFromFile($file)
94 | {
95 | $info = @getimagesize($file);
96 | if ($info === false) {
97 | $data = @file_get_contents($file);
98 | if ($data === '' || !static::isSvg($data, $file)) {
99 | return static::createInvalid();
100 | }
101 |
102 | return static::createSvgFromString($data);
103 | }
104 |
105 | $exif = static::readExif($file);
106 |
107 | return static::createFromArray($info, $exif);
108 | }
109 |
110 | /**
111 | * Creates an Info from a string of image data.
112 | *
113 | * @param string $data A string containing the image data
114 | * @param string|null $filename The filename used for determining the MIME Type.
115 | *
116 | * @return Info
117 | */
118 | public static function createFromString($data, $filename = null)
119 | {
120 | if ($data === '') {
121 | return static::createInvalid();
122 | }
123 |
124 | if (static::isSvg($data, (string) $filename)) {
125 | return static::createSvgFromString($data);
126 | }
127 |
128 | $info = @getimagesizefromstring($data);
129 | if ($info === false) {
130 | return static::createInvalid();
131 | }
132 |
133 | $file = sprintf('data://%s;base64,%s', $info['mime'], base64_encode($data));
134 | $exif = static::readExif($file);
135 |
136 | return static::createFromArray($info, $exif);
137 | }
138 |
139 | /**
140 | * Creates info from a previous json serialized object.
141 | *
142 | * @param array $data
143 | *
144 | * @return Info
145 | */
146 | public static function createFromJson(array $data)
147 | {
148 | return new static(
149 | new Dimensions($data['dims'][0], $data['dims'][1]),
150 | Type::getById($data['type']),
151 | $data['bits'],
152 | $data['channels'],
153 | $data['mime'],
154 | new Exif($data['exif'])
155 | );
156 | }
157 |
158 | /**
159 | * @param array $info
160 | * @param Exif $exif
161 | *
162 | * @return Info
163 | */
164 | protected static function createFromArray(array $info, Exif $exif)
165 | {
166 | // Add defaults to skip isset checks
167 | $info += [
168 | 0 => 0,
169 | 1 => 0,
170 | 2 => 0,
171 | 'bits' => 0,
172 | 'channels' => 0,
173 | 'mime' => '',
174 | ];
175 |
176 | return new static(
177 | new Dimensions($info[0], $info[1]),
178 | Type::getById($info[2]),
179 | $info['bits'],
180 | $info['channels'],
181 | $info['mime'],
182 | $exif
183 | );
184 | }
185 |
186 | /**
187 | * Creates an Info from a string of SVG image data.
188 | *
189 | * @param string $data
190 | *
191 | * @return Info
192 | */
193 | protected static function createSvgFromString($data)
194 | {
195 | if (!class_exists(SvgImagine::class)) {
196 | throw new LogicException('Cannot parse SVG Image Info without "contao/imagine-svg" library.');
197 | }
198 |
199 | try {
200 | $image = (new SvgImagine())->load($data);
201 | } catch (RuntimeException $e) {
202 | throw new IOException('Failed to parse image data from string', null, 0, $e);
203 | }
204 |
205 | $box = $image->getSize();
206 | $dimensions = new Dimensions($box->getWidth(), $box->getHeight());
207 |
208 | return new static(
209 | $dimensions,
210 | Type::getById(SvgType::ID),
211 | 0,
212 | 0,
213 | SvgType::MIME,
214 | new Exif([])
215 | );
216 | }
217 |
218 | /**
219 | * @param string $file
220 | *
221 | * @return Exif
222 | */
223 | protected static function readExif($file)
224 | {
225 | if (static::$exifReader === null) {
226 | static::$exifReader = Reader::factory(Reader::TYPE_NATIVE);
227 | }
228 |
229 | $exif = static::$exifReader->read($file);
230 | if ($exif instanceof \PHPExif\Exif) {
231 | return Exif::cast($exif);
232 | }
233 |
234 | return new Exif();
235 | }
236 |
237 | /**
238 | * Determine data string is an SVG image.
239 | *
240 | * @param string $data
241 | * @param string $filename
242 | *
243 | * @return bool
244 | */
245 | protected static function isSvg($data, $filename)
246 | {
247 | $type = Flysystem\Util::guessMimeType($filename, $data);
248 |
249 | if ($type === SvgType::MIME) {
250 | return true;
251 | }
252 |
253 | // Detect SVG files without the xml declaration (like from Adobe Illustrator)
254 | if (strpos($data, '