linwu 2 年之前
父節點
當前提交
30c9b64e38
共有 100 個文件被更改,包括 8974 次插入0 次删除
  1. 2 0
      vendor/.gitignore
  2. 2 0
      vendor/alchemy/binary-driver/.gitignore
  3. 23 0
      vendor/alchemy/binary-driver/.travis.yml
  4. 64 0
      vendor/alchemy/binary-driver/CHANGELOG.md
  5. 21 0
      vendor/alchemy/binary-driver/LICENSE
  6. 190 0
      vendor/alchemy/binary-driver/README.md
  7. 44 0
      vendor/alchemy/binary-driver/composer.json
  8. 31 0
      vendor/alchemy/binary-driver/phpunit.xml.dist
  9. 220 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/AbstractBinary.php
  10. 76 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryDriverTestCase.php
  11. 67 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryInterface.php
  12. 107 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Configuration.php
  13. 29 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationAwareInterface.php
  14. 58 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationInterface.php
  15. 16 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExceptionInterface.php
  16. 16 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutableNotFoundException.php
  17. 16 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutionFailureException.php
  18. 16 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/InvalidArgumentException.php
  19. 58 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/DebugListener.php
  20. 32 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/ListenerInterface.php
  21. 88 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/Listeners.php
  22. 186 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactory.php
  23. 29 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryAwareInterface.php
  24. 65 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryInterface.php
  25. 104 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunner.php
  26. 29 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerAwareInterface.php
  27. 33 0
      vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerInterface.php
  28. 300 0
      vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractBinaryTest.php
  29. 97 0
      vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractProcessBuilderFactoryTest.php
  30. 78 0
      vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/ConfigurationTest.php
  31. 54 0
      vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/LTSProcessBuilder.php
  32. 28 0
      vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/LTSProcessBuilderFactoryTest.php
  33. 33 0
      vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/DebugListenerTest.php
  34. 92 0
      vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/ListenersTest.php
  35. 15 0
      vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/NONLTSProcessBuilderFactoryTest.php
  36. 208 0
      vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/ProcessRunnerTest.php
  37. 4 0
      vendor/alchemy/binary-driver/tests/bootstrap.php
  38. 7 0
      vendor/autoload.php
  39. 14 0
      vendor/bin/var-dump-server
  40. 445 0
      vendor/composer/ClassLoader.php
  41. 21 0
      vendor/composer/LICENSE
  42. 9 0
      vendor/composer/autoload_classmap.php
  43. 16 0
      vendor/composer/autoload_files.php
  44. 15 0
      vendor/composer/autoload_namespaces.php
  45. 30 0
      vendor/composer/autoload_psr4.php
  46. 70 0
      vendor/composer/autoload_real.php
  47. 209 0
      vendor/composer/autoload_static.php
  48. 1575 0
      vendor/composer/installed.json
  49. 2 0
      vendor/evenement/evenement/.gitignore
  50. 26 0
      vendor/evenement/evenement/.travis.yml
  51. 8 0
      vendor/evenement/evenement/CHANGELOG.md
  52. 19 0
      vendor/evenement/evenement/LICENSE
  53. 83 0
      vendor/evenement/evenement/README.md
  54. 34 0
      vendor/evenement/evenement/composer.json
  55. 28 0
      vendor/evenement/evenement/doc/00-intro.md
  56. 77 0
      vendor/evenement/evenement/doc/01-api.md
  57. 155 0
      vendor/evenement/evenement/doc/02-plugin-system.md
  58. 28 0
      vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php
  59. 30 0
      vendor/evenement/evenement/examples/benchmark-emit-once.php
  60. 28 0
      vendor/evenement/evenement/examples/benchmark-emit-one-argument.php
  61. 28 0
      vendor/evenement/evenement/examples/benchmark-emit.php
  62. 24 0
      vendor/evenement/evenement/phpunit.xml.dist
  63. 17 0
      vendor/evenement/evenement/src/Evenement/EventEmitter.php
  64. 22 0
      vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
  65. 73 0
      vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
  66. 291 0
      vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php
  67. 51 0
      vendor/evenement/evenement/tests/Evenement/Tests/Listener.php
  68. 17 0
      vendor/evenement/evenement/tests/Evenement/Tests/functions.php
  69. 30 0
      vendor/firebase/php-jwt/LICENSE
  70. 200 0
      vendor/firebase/php-jwt/README.md
  71. 29 0
      vendor/firebase/php-jwt/composer.json
  72. 7 0
      vendor/firebase/php-jwt/src/BeforeValidException.php
  73. 7 0
      vendor/firebase/php-jwt/src/ExpiredException.php
  74. 514 0
      vendor/firebase/php-jwt/src/JWT.php
  75. 7 0
      vendor/firebase/php-jwt/src/SignatureInvalidException.php
  76. 10 0
      vendor/league/flysystem-cached-adapter/.editorconfig
  77. 4 0
      vendor/league/flysystem-cached-adapter/.gitignore
  78. 7 0
      vendor/league/flysystem-cached-adapter/.php_cs
  79. 34 0
      vendor/league/flysystem-cached-adapter/.scrutinizer.yml
  80. 29 0
      vendor/league/flysystem-cached-adapter/.travis.yml
  81. 19 0
      vendor/league/flysystem-cached-adapter/LICENSE
  82. 2 0
      vendor/league/flysystem-cached-adapter/clover/.gitignore
  83. 30 0
      vendor/league/flysystem-cached-adapter/composer.json
  84. 6 0
      vendor/league/flysystem-cached-adapter/phpspec.yml
  85. 3 0
      vendor/league/flysystem-cached-adapter/phpunit.php
  86. 29 0
      vendor/league/flysystem-cached-adapter/phpunit.xml
  87. 20 0
      vendor/league/flysystem-cached-adapter/readme.md
  88. 435 0
      vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php
  89. 101 0
      vendor/league/flysystem-cached-adapter/src/CacheInterface.php
  90. 324 0
      vendor/league/flysystem-cached-adapter/src/CachedAdapter.php
  91. 417 0
      vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php
  92. 115 0
      vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php
  93. 59 0
      vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php
  94. 22 0
      vendor/league/flysystem-cached-adapter/src/Storage/Memory.php
  95. 171 0
      vendor/league/flysystem-cached-adapter/src/Storage/Noop.php
  96. 62 0
      vendor/league/flysystem-cached-adapter/src/Storage/PhpRedis.php
  97. 75 0
      vendor/league/flysystem-cached-adapter/src/Storage/Predis.php
  98. 59 0
      vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php
  99. 60 0
      vendor/league/flysystem-cached-adapter/src/Storage/Stash.php
  100. 104 0
      vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php

+ 2 - 0
vendor/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 2 - 0
vendor/alchemy/binary-driver/.gitignore

@@ -0,0 +1,2 @@
+/vendor/
+composer.lock

+ 23 - 0
vendor/alchemy/binary-driver/.travis.yml

@@ -0,0 +1,23 @@
+language: php
+
+sudo: false
+
+php:
+  - 5.5
+  - 5.6
+  - 7.0
+  - 7.1
+
+matrix:
+  include:
+    - php: 5.5
+      env: COMPOSER_OPTIONS="--prefer-lowest --prefer-stable"
+    - php: 7.0
+      env: COMPOSER_REQUIRE="symfony/process:^2.0"
+    - php: 7.0
+      env: COMPOSER_REQUIRE="symfony/process:^3.0"
+
+before_script:
+  - composer self-update
+  - if [ -n "$COMPOSER_REQUIRE" ]; then composer require --no-update $COMPOSER_REQUIRE; fi
+  - composer update $COMPOSER_OPTIONS

+ 64 - 0
vendor/alchemy/binary-driver/CHANGELOG.md

@@ -0,0 +1,64 @@
+CHANGELOG
+---------
+* 1.6.0 (2015-03-02)
+  * BC Break: bump minimum PHP versions 
+  * Allow use of evenement v2.0 (thanks @patkar for the P/R)
+
+* 1.5.0 (2013-06-21)
+
+  * BC Break : ConfigurationInterface::get does not throw exceptions anymore
+    in case the key does not exist. Second argument is a default value to return
+    in case the key does not exist.
+
+* 1.4.1 (2013-05-23)
+
+  * Add third parameter to BinaryInterface::command method to pass a listener or
+    an array of listener that will be registered just the time of the command.
+
+* 1.4.0 (2013-05-11)
+
+  * Extract process run management to ProcessRunner.
+  * Add support for process listeners.
+  * Provides bundled DebugListener.
+  * Add BinaryInterface::command method.
+  * BC break : ProcessRunnerInterface::run now takes an SplObjectStorage containing
+    listeners as second argument.
+  * BC break : BinaryInterface no longer implements LoggerAwareInterface
+    as it is now supported by ProcessRunner.
+
+* 1.3.4 (2013-04-26)
+
+  * Add BinaryDriver::run method.
+
+* 1.3.3 (2013-04-26)
+
+  * Add BinaryDriver::createProcessMock method.
+
+* 1.3.2 (2013-04-26)
+
+  * Add BinaryDriverTestCase for testing BinaryDriver implementations.
+
+* 1.3.1 (2013-04-24)
+
+  * Add timeouts handling
+
+* 1.3.0 (2013-04-24)
+
+  * Add BinaryInterface and AbstractBinary
+
+* 1.2.1 (2013-04-24)
+
+  * Add ConfigurationAwareInterface
+  * Add ProcessBuilderAwareInterface
+
+* 1.2.0 (2013-04-24)
+
+  * Add BinaryDriver\Configuration
+
+* 1.1.0 (2013-04-24)
+
+  * Add support for timeouts via `setTimeout` method
+
+* 1.0.0 (2013-04-23)
+
+  * First stable version.

+ 21 - 0
vendor/alchemy/binary-driver/LICENSE

@@ -0,0 +1,21 @@
+BinaryDriver is released with MIT License :
+
+Copyright (c) 2013 Alchemy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.

+ 190 - 0
vendor/alchemy/binary-driver/README.md

@@ -0,0 +1,190 @@
+# Binary Driver
+
+Binary-Driver is a set of PHP tools to build binary drivers.
+
+[![Build Status](https://travis-ci.org/alchemy-fr/BinaryDriver.png?branch=master)](https://travis-ci.org/alchemy-fr/BinaryDriver)
+
+## Why ?
+
+You may wonder *Why building a library while I can use `exec` or
+[symfony/process](https://github.com/symfony/Process) ?*.
+
+Here is a simple answer :
+
+ - If you use `exec`, `passthru`, `system`, `proc_open` or any low level process
+   handling in PHP, you should have a look to [symfony/process](https://github.com/symfony/Process)
+   component that will provide an OO portable, testable and secure interface to
+   deal with this. It seems easy at first approach, but if you look at this
+   component [unit tests](https://github.com/symfony/Process/tree/master/Tests),
+   you will see that handling process in a simple interface can easily become a
+   nightmare.
+
+ - If you already use symfony/process, and want to build binary drivers, you
+   will always have the same common set of methods and objects to configure, log,
+   debug, and generate processes.
+   This library is a base to implement any binary driver with this common set of
+   needs.
+
+## AbstractBinary
+
+`AbstractBinary` provides an abstract class to build a binary driver. It implements
+`BinaryInterface`.
+
+Implementation example :
+
+```php
+use Alchemy\BinaryDriver\AbstractBinary;
+
+class LsDriver extends AbstractBinary
+{
+    public function getName()
+    {
+        return 'ls driver';
+    }
+}
+
+$parser = new LsParser();
+
+$driver = Driver::load('ls');
+// will return the output of `ls -a -l`
+$parser->parse($driver->command(array('-a', '-l')));
+```
+
+### Binary detection troubleshooting
+
+If you are using Nginx with PHP-fpm, executable detection may not work because of an empty `$_ENV['path']`. 
+To avoid having an empty `PATH` environment variable, add the following line to your `fastcgi_params` 
+config file (replace `/your/current/path/` with the output of `printenv PATH`) :
+
+```
+fastcgi_param    PATH    /your/current/path
+```
+
+## Logging
+
+You can log events with a `Psr\Log\LoggerInterface` by passing it in the load
+method as second argument :
+
+```php
+$logger = new Monolog\Logger('driver');
+$driver = Driver::load('ls', $logger);
+```
+
+## Listeners
+
+You can add custom listeners on processes.
+Listeners are built on top of [Evenement](https://github.com/igorw/evenement)
+and must implement `Alchemy\BinaryDriver\ListenerInterface`.
+
+```php
+use Symfony\Component\Process\Process;
+
+class DebugListener extends EventEmitter implements ListenerInterface
+{
+    public function handle($type, $data)
+    {
+        foreach (explode(PHP_EOL, $data) as $line) {
+            $this->emit($type === Process::ERR ? 'error' : 'out', array($line));
+        }
+    }
+
+    public function forwardedEvents()
+    {
+        // forward 'error' events to the BinaryInterface
+        return array('error');
+    }
+}
+
+$listener = new DebugListener();
+
+$driver = CustomImplementation::load('php');
+
+// adds listener
+$driver->listen($listener);
+
+$driver->on('error', function ($line) {
+    echo '[ERROR] ' . $line . PHP_EOL;
+});
+
+// removes listener
+$driver->unlisten($listener);
+```
+
+### Bundled listeners
+
+The debug listener is a simple listener to catch `stderr` and `stdout` outputs ;
+read the implementation for customization.
+
+```php
+use Alchemy\BinaryDriver\Listeners\DebugListener;
+
+$driver = CustomImplementation::load('php');
+$driver->listen(new DebugListener());
+
+$driver->on('debug', function ($line) {
+    echo $line;
+});
+```
+
+## ProcessBuilderFactory
+
+ProcessBuilderFactory ease spawning processes by generating Symfony [Process]
+(http://symfony.com/doc/master/components/process.html) objects.
+
+```php
+use Alchemy\BinaryDriver\ProcessBuilderFactory;
+
+$factory = new ProcessBuilderFactory('/usr/bin/php');
+
+// return a Symfony\Component\Process\Process
+$process = $factory->create('-v');
+
+// echoes '/usr/bin/php' '-v'
+echo $process->getCommandLine();
+
+$process = $factory->create(array('-r', 'echo "Hello !";'));
+
+// echoes '/usr/bin/php' '-r' 'echo "Hello !";'
+echo $process->getCommandLine();
+```
+
+## Configuration
+
+A simple configuration object, providing an `ArrayAccess` and `IteratorAggregate`
+interface.
+
+```php
+use Alchemy\BinaryDriver\Configuration;
+
+$conf = new Configuration(array('timeout' => 0));
+
+echo $conf->get('timeout');
+
+if ($conf->has('param')) {
+    $conf->remove('param');
+}
+
+$conf->set('timeout', 20);
+
+$conf->all();
+```
+
+Same example using the `ArrayAccess` interface :
+
+```php
+use Alchemy\BinaryDriver\Configuration;
+
+$conf = new Configuration(array('timeout' => 0));
+
+echo $conf['timeout'];
+
+if (isset($conf['param'])) {
+    unset($conf['param']);
+}
+
+$conf['timeout'] = 20;
+```
+
+## License
+
+This project is released under the MIT license.

+ 44 - 0
vendor/alchemy/binary-driver/composer.json

@@ -0,0 +1,44 @@
+{
+    "name": "alchemy/binary-driver",
+    "type": "library",
+    "description": "A set of tools to build binary drivers",
+    "keywords": ["binary", "driver"],
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Nicolas Le Goff",
+            "email": "legoff.n@gmail.com"
+        },
+        {
+            "name": "Romain Neutron",
+            "email": "imprec@gmail.com",
+            "homepage": "http://www.lickmychip.com/"
+        },
+        {
+            "name": "Phraseanet Team",
+            "email": "info@alchemy.fr",
+            "homepage": "http://www.phraseanet.com/"
+        },
+        {
+            "name": "Jens Hausdorf",
+            "email": "mail@jens-hausdorf.de",
+            "homepage": "https://jens-hausdorf.de",
+            "role": "Maintainer"
+        }
+    ],
+    "require": {
+        "php"                 : ">=5.5",
+        "evenement/evenement" : "^2.0|^1.0",
+        "monolog/monolog"     : "^1.3",
+        "psr/log"             : "^1.0",
+        "symfony/process"     : "^2.3|^3.0|^4.0"
+    },
+    "require-dev": {
+        "phpunit/phpunit"     : "^4.0|^5.0"
+    },
+    "autoload": {
+        "psr-0": {
+            "Alchemy": "src"
+        }
+    }
+}

+ 31 - 0
vendor/alchemy/binary-driver/phpunit.xml.dist

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         syntaxCheck="true"
+         verbose="false"
+         bootstrap="tests/bootstrap.php"
+>
+    <php>
+      <ini name="display_errors" value="on"/>
+    </php>
+
+    <testsuites>
+        <testsuite>
+            <directory>tests</directory>
+        </testsuite>
+    </testsuites>
+    <filter>
+      <blacklist>
+        <directory>vendor</directory>
+        <directory>tests</directory>
+      </blacklist>
+    </filter>
+
+</phpunit>
+

+ 220 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/AbstractBinary.php

@@ -0,0 +1,220 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException;
+use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
+use Alchemy\BinaryDriver\Listeners\Listeners;
+use Alchemy\BinaryDriver\Listeners\ListenerInterface;
+use Evenement\EventEmitter;
+use Monolog\Logger;
+use Monolog\Handler\NullHandler;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Process\ExecutableFinder;
+use Symfony\Component\Process\Process;
+
+abstract class AbstractBinary extends EventEmitter implements BinaryInterface
+{
+    /** @var ConfigurationInterface */
+    protected $configuration;
+
+    /** @var ProcessBuilderFactoryInterface */
+    protected $factory;
+
+    /** @var ProcessRunner */
+    private $processRunner;
+
+    /** @var Listeners */
+    private $listenersManager;
+
+    public function __construct(ProcessBuilderFactoryInterface $factory, LoggerInterface $logger, ConfigurationInterface $configuration)
+    {
+        $this->factory = $factory;
+        $this->configuration = $configuration;
+        $this->processRunner = new ProcessRunner($logger, $this->getName());
+        $this->listenersManager = new Listeners();
+        $this->applyProcessConfiguration();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function listen(ListenerInterface $listener)
+    {
+        $this->listenersManager->register($listener, $this);
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function unlisten(ListenerInterface $listener)
+    {
+        $this->listenersManager->unregister($listener, $this);
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getConfiguration()
+    {
+        return $this->configuration;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return BinaryInterface
+     */
+    public function setConfiguration(ConfigurationInterface $configuration)
+    {
+        $this->configuration = $configuration;
+        $this->applyProcessConfiguration();
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getProcessBuilderFactory()
+    {
+        return $this->factory;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return BinaryInterface
+     */
+    public function setProcessBuilderFactory(ProcessBuilderFactoryInterface $factory)
+    {
+        $this->factory = $factory;
+        $this->applyProcessConfiguration();
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getProcessRunner()
+    {
+        return $this->processRunner;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setProcessRunner(ProcessRunnerInterface $runner)
+    {
+        $this->processRunner = $runner;
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function command($command, $bypassErrors = false, $listeners = null)
+    {
+        if (!is_array($command)) {
+            $command = array($command);
+        }
+
+        return $this->run($this->factory->create($command), $bypassErrors, $listeners);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public static function load($binaries, LoggerInterface $logger = null, $configuration = array())
+    {
+        $finder = new ExecutableFinder();
+        $binary = null;
+        $binaries = is_array($binaries) ? $binaries : array($binaries);
+
+        foreach ($binaries as $candidate) {
+            if (file_exists($candidate) && is_executable($candidate)) {
+                $binary = $candidate;
+                break;
+            }
+            if (null !== $binary = $finder->find($candidate)) {
+                break;
+            }
+        }
+
+        if (null === $binary) {
+            throw new ExecutableNotFoundException(sprintf(
+                'Executable not found, proposed : %s', implode(', ', $binaries)
+            ));
+        }
+
+        if (null === $logger) {
+            $logger = new Logger(__NAMESPACE__ . ' logger');
+            $logger->pushHandler(new NullHandler());
+        }
+
+        $configuration = $configuration instanceof ConfigurationInterface ? $configuration : new Configuration($configuration);
+
+        return new static(new ProcessBuilderFactory($binary), $logger, $configuration);
+    }
+
+    /**
+     * Returns the name of the driver
+     *
+     * @return string
+     */
+    abstract public function getName();
+
+    /**
+     * Executes a process, logs events
+     *
+     * @param Process                 $process
+     * @param Boolean                 $bypassErrors Set to true to disable throwing ExecutionFailureExceptions
+     * @param ListenerInterface|array $listeners    A listener or an array of listener to register for this unique run
+     *
+     * @return string The Process output
+     *
+     * @throws ExecutionFailureException in case of process failure.
+     */
+    protected function run(Process $process, $bypassErrors = false, $listeners = null)
+    {
+        if (null !== $listeners) {
+            if (!is_array($listeners)) {
+                $listeners = array($listeners);
+            }
+
+            $listenersManager = clone $this->listenersManager;
+
+            foreach ($listeners as $listener) {
+                $listenersManager->register($listener, $this);
+            }
+        } else {
+            $listenersManager = $this->listenersManager;
+        }
+
+        return $this->processRunner->run($process, $listenersManager->storage, $bypassErrors);
+    }
+
+    private function applyProcessConfiguration()
+    {
+        if ($this->configuration->has('timeout')) {
+            $this->factory->setTimeout($this->configuration->get('timeout'));
+        }
+
+        return $this;
+    }
+}

+ 76 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryDriverTestCase.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace Alchemy\BinaryDriver;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * Convenient PHPUnit methods for testing BinaryDriverInterface implementations.
+ */
+class BinaryDriverTestCase extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @return ProcessBuilderFactoryInterface
+     */
+    public function createProcessBuilderFactoryMock()
+    {
+        return $this->getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface');
+    }
+
+    /**
+     * @param integer $runs        The number of runs expected
+     * @param Boolean $success     True if the process expects to be successfull
+     * @param string  $commandLine The commandline executed
+     * @param string  $output      The process output
+     * @param string  $error       The process error output
+     *
+     * @return Process
+     */
+    public function createProcessMock($runs = 1, $success = true, $commandLine = null, $output = null, $error = null, $callback = false)
+    {
+        $process = $this->getMockBuilder('Symfony\Component\Process\Process')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $builder = $process->expects($this->exactly($runs))
+            ->method('run');
+
+        if (true === $callback) {
+            $builder->with($this->isInstanceOf('Closure'));
+        }
+
+        $process->expects($this->any())
+            ->method('isSuccessful')
+            ->will($this->returnValue($success));
+
+        foreach (array(
+            'getOutput' => $output,
+            'getErrorOutput' => $error,
+            'getCommandLine' => $commandLine,
+        ) as $command => $value) {
+            $process
+                ->expects($this->any())
+                ->method($command)
+                ->will($this->returnValue($value));
+        }
+
+        return $process;
+    }
+
+    /**
+     * @return LoggerInterface
+     */
+    public function createLoggerMock()
+    {
+        return $this->getMock('Psr\Log\LoggerInterface');
+    }
+
+    /**
+     * @return ConfigurationInterface
+     */
+    public function createConfigurationMock()
+    {
+        return $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+    }
+}

+ 67 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/BinaryInterface.php

@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException;
+use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
+use Alchemy\BinaryDriver\Listeners\ListenerInterface;
+use Psr\Log\LoggerInterface;
+use Evenement\EventEmitterInterface;
+
+interface BinaryInterface extends ConfigurationAwareInterface, ProcessBuilderFactoryAwareInterface, ProcessRunnerAwareInterface, EventEmitterInterface
+{
+    /**
+     * Adds a listener to the binary driver
+     *
+     * @param ListenerInterface $listener
+     *
+     * @return BinaryInterface
+     */
+    public function listen(ListenerInterface $listener);
+
+    /**
+     * Removes a listener from the binary driver
+     *
+     * @param ListenerInterface $listener
+     *
+     * @return BinaryInterface
+     */
+    public function unlisten(ListenerInterface $listener);
+
+    /**
+     * Runs a command against the driver.
+     *
+     * Calling this method on a `ls` driver with the command `-a` would run `ls -a`.
+     *
+     * @param array|string            $command      A command or an array of command
+     * @param Boolean                 $bypassErrors If set to true, an erronous process will not throw an exception
+     * @param ListenerInterface|array $listeners    A listener or an array of listeners to register for this unique run
+     *
+     * @return string The command output
+     *
+     * @throws ExecutionFailureException in case of process failure.
+     */
+    public function command($command, $bypassErrors = false, $listeners = null);
+
+    /**
+     * Loads a binary
+     *
+     * @param string|array                 $binaries      A binary name or an array of binary names
+     * @param null|LoggerInterface        $logger        A Logger
+     * @param array|ConfigurationInterface $configuration The configuration
+     *
+     * @throws ExecutableNotFoundException In case none of the binaries were found
+     *
+     * @return BinaryInterface
+     */
+    public static function load($binaries, LoggerInterface $logger = null, $configuration = array());
+}

+ 107 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Configuration.php

@@ -0,0 +1,107 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+class Configuration implements ConfigurationInterface
+{
+    private $data;
+
+    public function __construct(array $data = array())
+    {
+        $this->data = $data;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getIterator()
+    {
+        return new \ArrayIterator($this->data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function get($key, $default = null)
+    {
+        return isset($this->data[$key]) ? $this->data[$key] : $default;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function set($key, $value)
+    {
+        $this->data[$key] = $value;
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($key)
+    {
+        return array_key_exists($key, $this->data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function remove($key)
+    {
+        $value = $this->get($key);
+        unset($this->data[$key]);
+
+        return $value;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function all()
+    {
+        return $this->data;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function offsetExists($offset)
+    {
+        return $this->has($offset);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function offsetGet($offset)
+    {
+        return $this->get($offset);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function offsetSet($offset, $value)
+    {
+        $this->set($offset, $value);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function offsetUnset($offset)
+    {
+        $this->remove($offset);
+    }
+}

+ 29 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationAwareInterface.php

@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+interface ConfigurationAwareInterface
+{
+    /**
+     * Returns the configuration
+     *
+     * @return ConfigurationInterface
+     */
+    public function getConfiguration();
+
+    /**
+     * Set the configuration
+     *
+     * @param ConfigurationInterface $configuration
+     */
+    public function setConfiguration(ConfigurationInterface $configuration);
+}

+ 58 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ConfigurationInterface.php

@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+interface ConfigurationInterface extends \ArrayAccess, \IteratorAggregate
+{
+    /**
+     * Returns the value given a key from configuration
+     *
+     * @param string $key
+     * @param mixed  $default The default value in case the key does not exist
+     *
+     * @return mixed
+     */
+    public function get($key, $default = null);
+
+    /**
+     * Set a value to configuration
+     *
+     * @param string $key   The key
+     * @param mixed  $value The value corresponding to the key
+     */
+    public function set($key, $value);
+
+    /**
+     * Tells if Configuration contains `$key`
+     *
+     * @param string $key
+     *
+     * @return Boolean
+     */
+    public function has($key);
+
+    /**
+     * Removes a value given a key
+     *
+     * @param string $key
+     *
+     * @return mixed The previous value
+     */
+    public function remove($key);
+
+    /**
+     * Returns all values set in the configuration
+     *
+     * @return array
+     */
+    public function all();
+}

+ 16 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExceptionInterface.php

@@ -0,0 +1,16 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver\Exception;
+
+interface ExceptionInterface
+{
+}

+ 16 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutableNotFoundException.php

@@ -0,0 +1,16 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver\Exception;
+
+class ExecutableNotFoundException extends \RuntimeException implements ExceptionInterface
+{
+}

+ 16 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/ExecutionFailureException.php

@@ -0,0 +1,16 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver\Exception;
+
+class ExecutionFailureException extends \RuntimeException implements ExceptionInterface
+{
+}

+ 16 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Exception/InvalidArgumentException.php

@@ -0,0 +1,16 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}

+ 58 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/DebugListener.php

@@ -0,0 +1,58 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver\Listeners;
+
+use Evenement\EventEmitter;
+use Symfony\Component\Process\Process;
+
+class DebugListener extends EventEmitter implements ListenerInterface
+{
+    private $prefixOut;
+    private $prefixErr;
+    private $eventOut;
+    private $eventErr;
+
+    public function __construct($prefixOut = '[OUT] ', $prefixErr = '[ERROR] ', $eventOut = 'debug', $eventErr = 'debug')
+    {
+        $this->prefixOut = $prefixOut;
+        $this->prefixErr = $prefixErr;
+        $this->eventOut = $eventOut;
+        $this->eventErr = $eventErr;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function handle($type, $data)
+    {
+        if (Process::ERR === $type) {
+            $this->emitLines($this->eventErr, $this->prefixErr, $data);
+        } elseif (Process::OUT === $type) {
+            $this->emitLines($this->eventOut, $this->prefixOut, $data);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function forwardedEvents()
+    {
+        return array_unique(array($this->eventErr, $this->eventOut));
+    }
+
+    private function emitLines($event, $prefix, $lines)
+    {
+        foreach (explode("\n", $lines) as $line) {
+            $this->emit($event, array($prefix . $line));
+        }
+    }
+}

+ 32 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/ListenerInterface.php

@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver\Listeners;
+
+use Evenement\EventEmitterInterface;
+
+interface ListenerInterface extends EventEmitterInterface
+{
+    /**
+     * Handle the output of a ProcessRunner
+     *
+     * @param string $type The data type, one of Process::ERR, Process::OUT constants
+     * @param string $data The output
+     */
+    public function handle($type, $data);
+
+    /**
+     * An array of events that should be forwarded to BinaryInterface
+     *
+     * @return array
+     */
+    public function forwardedEvents();
+}

+ 88 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/Listeners/Listeners.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace Alchemy\BinaryDriver\Listeners;
+
+use SplObjectStorage;
+use Evenement\EventEmitter;
+
+class Listeners extends EventEmitter
+{
+    /** @var SplObjectStorage */
+    public $storage;
+
+    public function __construct()
+    {
+        $this->storage = new SplObjectStorage();
+    }
+
+    public function __clone()
+    {
+        $storage = $this->storage;
+        $this->storage = new SplObjectStorage();
+        $this->storage->addAll($storage);
+    }
+
+    /**
+     * Registers a listener, pass the listener events to the target.
+     *
+     * @param ListenerInterface $listener
+     * @param null|EventEmitter $target
+     *
+     * @return ListenersInterface
+     */
+    public function register(ListenerInterface $listener, EventEmitter $target = null)
+    {
+        $EElisteners = array();
+
+        if (null !== $target) {
+            $EElisteners = $this->forwardEvents($listener, $target, $listener->forwardedEvents());
+        }
+
+        $this->storage->attach($listener, $EElisteners);
+
+        return $this;
+    }
+
+    /**
+     * Unregisters a listener, removes the listener events from the target.
+     *
+     * @param ListenerInterface $listener
+     *
+     * @return ListenersInterface
+     *
+     * @throws InvalidArgumentException In case the listener is not registered
+     */
+    public function unregister(ListenerInterface $listener)
+    {
+        if (!isset($this->storage[$listener])) {
+            throw new InvalidArgumentException('Listener is not registered.');
+        }
+
+        foreach ($this->storage[$listener] as $event => $EElistener) {
+            $listener->removeListener($event, $EElistener);
+        }
+
+        $this->storage->detach($listener);
+
+        return $this;
+    }
+
+    private function forwardEvents($source, $target, array $events)
+    {
+        $EElisteners = array();
+
+        foreach ($events as $event) {
+            $listener = $this->createListener($event, $target);
+            $source->on($event, $EElisteners[$event] = $listener);
+        }
+
+        return $EElisteners;
+    }
+
+    private function createListener($event, $target)
+    {
+        return function () use ($event, $target) {
+            $target->emit($event, func_get_args());
+        };
+    }
+}

+ 186 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactory.php

@@ -0,0 +1,186 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+use Alchemy\BinaryDriver\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Process;
+use Symfony\Component\Process\ProcessBuilder;
+
+class ProcessBuilderFactory implements ProcessBuilderFactoryInterface
+{
+    /**
+     * The binary path
+     *
+     * @var String
+     */
+    protected $binary;
+
+    /**
+     * The timeout for the generated processes
+     *
+     * @var integer|float
+     */
+    private $timeout;
+
+    /**
+     * An internal ProcessBuilder.
+     *
+     * Note that this one is used only if Symfony ProcessBuilder has method
+     * setPrefix (2.3)
+     *
+     * @var ProcessBuilder
+     */
+    private $builder;
+
+    /**
+     * Tells whether Symfony LTS ProcessBuilder should be emulated or not.
+     *
+     * This symfony version provided a brand new ::setPrefix method.
+     *
+     * @var Boolean
+     */
+    public static $emulateSfLTS;
+
+    /**
+     * Constructor
+     *
+     * @param String $binary The path to the binary
+     *
+     * @throws InvalidArgumentException In case binary path is invalid
+     */
+    public function __construct($binary)
+    {
+        $this->detectEmulation();
+
+        if (!self::$emulateSfLTS) {
+            $this->builder = new ProcessBuilder();
+        }
+
+        $this->useBinary($binary);
+    }
+
+    /**
+     * Covenient method for unit testing
+     *
+     * @return type
+     */
+    public function getBuilder()
+    {
+        return $this->builder;
+    }
+
+    /**
+     * Covenient method for unit testing
+     *
+     * @param  ProcessBuilder        $builder
+     * @return ProcessBuilderFactory
+     */
+    public function setBuilder(ProcessBuilder $builder)
+    {
+        $this->builder = $builder;
+
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getBinary()
+    {
+        return $this->binary;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function useBinary($binary)
+    {
+        if (!is_executable($binary)) {
+            throw new InvalidArgumentException(sprintf('`%s` is not an executable binary', $binary));
+        }
+
+        $this->binary = $binary;
+
+        if (!static::$emulateSfLTS) {
+            $this->builder->setPrefix($binary);
+        }
+
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setTimeout($timeout)
+    {
+        $this->timeout = $timeout;
+
+        if (!static::$emulateSfLTS) {
+            $this->builder->setTimeout($this->timeout);
+        }
+
+        return $this;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getTimeout()
+    {
+        return $this->timeout;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function create($arguments = array())
+    {
+        if (null === $this->binary) {
+            throw new InvalidArgumentException('No binary set');
+        }
+
+        if (!is_array($arguments)) {
+            $arguments = array($arguments);
+        }
+
+        if (static::$emulateSfLTS) {
+            array_unshift($arguments, $this->binary);
+            if (method_exists('Symfony\Component\Process\ProcessUtils', 'escapeArgument')) {
+                $script = implode(' ', array_map(array('Symfony\Component\Process\ProcessUtils', 'escapeArgument'), $arguments));
+            } else {
+                $script = $arguments;
+            }
+
+            $env = array_replace($_ENV, $_SERVER);
+            $env = array_filter($env, function ($value) {
+                return !is_array($value);
+            });
+
+            return new Process($script, null, $env, null, $this->timeout);
+        } else {
+            return $this->builder
+                ->setArguments($arguments)
+                ->getProcess();
+        }
+    }
+
+    private function detectEmulation()
+    {
+        if (null !== static::$emulateSfLTS) {
+            return $this;
+        }
+
+        static::$emulateSfLTS = !method_exists('Symfony\Component\Process\ProcessBuilder', 'setPrefix');
+
+        return $this;
+    }
+}

+ 29 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryAwareInterface.php

@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+interface ProcessBuilderFactoryAwareInterface
+{
+    /**
+     * Returns the current process builder factory
+     *
+     * @return ProcessBuilderFactoryInterface
+     */
+    public function getProcessBuilderFactory();
+
+    /**
+     * Set a process builder factory
+     *
+     * @param ProcessBuilderFactoryInterface $factory
+     */
+    public function setProcessBuilderFactory(ProcessBuilderFactoryInterface $factory);
+}

+ 65 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessBuilderFactoryInterface.php

@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+use Alchemy\BinaryDriver\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Process;
+
+interface ProcessBuilderFactoryInterface
+{
+    /**
+     * Returns a new instance of Symfony Process
+     *
+     * @param string|array $arguments An argument or an array of arguments
+     *
+     * @return Process
+     *
+     * @throws InvalidArgumentException
+     */
+    public function create($arguments = array());
+
+    /**
+     * Returns the path to the binary that is used
+     *
+     * @return String
+     */
+    public function getBinary();
+
+    /**
+     * Sets the path to the binary
+     *
+     * @param String $binary A path to a binary
+     *
+     * @return ProcessBuilderFactoryInterface
+     *
+     * @throws InvalidArgumentException In case binary is not executable
+     */
+    public function useBinary($binary);
+
+    /**
+     * Set the default timeout to apply on created processes.
+     *
+     * @param integer|float $timeout
+     *
+     * @return ProcessBuilderFactoryInterface
+     *
+     * @throws InvalidArgumentException In case the timeout is not valid
+     */
+    public function setTimeout($timeout);
+
+    /**
+     * Returns the current timeout applied to the created processes.
+     *
+     * @return integer|float
+     */
+    public function getTimeout();
+}

+ 104 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunner.php

@@ -0,0 +1,104 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
+use Psr\Log\LoggerInterface;
+use SplObjectStorage;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Process;
+
+class ProcessRunner implements ProcessRunnerInterface
+{
+    /** @var LoggerInterface */
+    private $logger;
+
+    /** @var string */
+    private $name;
+
+    public function __construct(LoggerInterface $logger, $name)
+    {
+        $this->logger = $logger;
+        $this->name = $name;
+    }
+
+    /**
+     * {@inheritdoc}
+     *
+     * @return ProcessRunner
+     */
+    public function setLogger(LoggerInterface $logger)
+    {
+        $this->logger = $logger;
+
+        return $this;
+    }
+
+    /**
+     * @return LoggerInterface
+     */
+    public function getLogger()
+    {
+        return $this->logger;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function run(Process $process, SplObjectStorage $listeners, $bypassErrors)
+    {
+        $this->logger->info(sprintf(
+            '%s running command %s', $this->name, $process->getCommandLine()
+        ));
+
+        try {
+            $process->run($this->buildCallback($listeners));
+        } catch (RuntimeException $e) {
+            if (!$bypassErrors) {
+                $this->doExecutionFailure($process->getCommandLine(), $e);
+            }
+        }
+
+        if (!$bypassErrors && !$process->isSuccessful()) {
+            $this->doExecutionFailure($process->getCommandLine());
+        } elseif (!$process->isSuccessful()) {
+            $this->logger->error(sprintf(
+                '%s failed to execute command %s: %s', $this->name, $process->getCommandLine(), $process->getErrorOutput()
+            ));
+
+            return;
+        } else {
+            $this->logger->info(sprintf('%s executed command successfully', $this->name));
+
+            return $process->getOutput();
+        }
+    }
+
+    private function buildCallback(SplObjectStorage $listeners)
+    {
+        return function ($type, $data) use ($listeners) {
+            foreach ($listeners as $listener) {
+                $listener->handle($type, $data);
+            }
+        };
+    }
+
+    private function doExecutionFailure($command, \Exception $e = null)
+    {
+        $this->logger->error(sprintf(
+            '%s failed to execute command %s', $this->name, $command
+        ));
+        throw new ExecutionFailureException(sprintf(
+            '%s failed to execute command %s', $this->name, $command
+        ), $e ? $e->getCode() : null, $e ?: null);
+    }
+}

+ 29 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerAwareInterface.php

@@ -0,0 +1,29 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+interface ProcessRunnerAwareInterface
+{
+    /**
+     * Returns the current process runner
+     *
+     * @return ProcessRunnerInterface
+     */
+    public function getProcessRunner();
+
+    /**
+     * Sets a process runner
+     *
+     * @param ProcessRunnerInterface $runner
+     */
+    public function setProcessRunner(ProcessRunnerInterface $runner);
+}

+ 33 - 0
vendor/alchemy/binary-driver/src/Alchemy/BinaryDriver/ProcessRunnerInterface.php

@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\BinaryDriver;
+
+use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
+use Psr\Log\LoggerAwareInterface;
+use SplObjectStorage;
+use Symfony\Component\Process\Process;
+
+interface ProcessRunnerInterface extends LoggerAwareInterface
+{
+    /**
+     * Executes a process, logs events
+     *
+     * @param Process          $process
+     * @param SplObjectStorage $listeners    Some listeners
+     * @param Boolean          $bypassErrors Set to true to disable throwing ExecutionFailureExceptions
+     *
+     * @return string The Process output
+     *
+     * @throws ExecutionFailureException in case of process failure.
+     */
+    public function run(Process $process, SplObjectStorage $listeners, $bypassErrors);
+}

+ 300 - 0
vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractBinaryTest.php

@@ -0,0 +1,300 @@
+<?php
+
+namespace Alchemy\Tests\BinaryDriver;
+
+use Alchemy\BinaryDriver\AbstractBinary;
+use Alchemy\BinaryDriver\BinaryDriverTestCase;
+use Alchemy\BinaryDriver\Configuration;
+use Alchemy\BinaryDriver\Exception\ExecutableNotFoundException;
+use Alchemy\BinaryDriver\Listeners\ListenerInterface;
+use Symfony\Component\Process\ExecutableFinder;
+
+class AbstractBinaryTest extends BinaryDriverTestCase
+{
+    protected function getPhpBinary()
+    {
+        $finder = new ExecutableFinder();
+        $php = $finder->find('php');
+
+        if (null === $php) {
+            $this->markTestSkipped('Unable to find a php binary');
+        }
+
+        return $php;
+    }
+
+    public function testSimpleLoadWithBinaryPath()
+    {
+        $php = $this->getPhpBinary();
+        $imp = Implementation::load($php);
+        $this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp);
+
+        $this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary());
+    }
+
+    public function testMultipleLoadWithBinaryPath()
+    {
+        $php = $this->getPhpBinary();
+        $imp = Implementation::load(array('/zz/path/to/unexisting/command', $php));
+        $this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp);
+
+        $this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary());
+    }
+
+    public function testSimpleLoadWithBinaryName()
+    {
+        $php = $this->getPhpBinary();
+        $imp = Implementation::load('php');
+        $this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp);
+
+        $this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary());
+    }
+
+    public function testMultipleLoadWithBinaryName()
+    {
+        $php = $this->getPhpBinary();
+        $imp = Implementation::load(array('bachibouzouk', 'php'));
+        $this->assertInstanceOf('Alchemy\Tests\BinaryDriver\Implementation', $imp);
+
+        $this->assertEquals($php, $imp->getProcessBuilderFactory()->getBinary());
+    }
+
+    public function testLoadWithMultiplePathExpectingAFailure()
+    {
+        $this->setExpectedException(ExecutableNotFoundException::class);
+
+        Implementation::load(array('bachibouzouk', 'moribon'));
+    }
+
+    public function testLoadWithUniquePathExpectingAFailure()
+    {
+        $this->setExpectedException(ExecutableNotFoundException::class);
+
+        Implementation::load('bachibouzouk');
+    }
+
+    public function testLoadWithCustomLogger()
+    {
+        $logger = $this->getMock('Psr\Log\LoggerInterface');
+        $imp = Implementation::load('php', $logger);
+
+        $this->assertEquals($logger, $imp->getProcessRunner()->getLogger());
+    }
+
+    public function testLoadWithCustomConfigurationAsArray()
+    {
+        $conf = array('timeout' => 200);
+        $imp = Implementation::load('php', null, $conf);
+
+        $this->assertEquals($conf, $imp->getConfiguration()->all());
+    }
+
+    public function testLoadWithCustomConfigurationAsObject()
+    {
+        $conf = $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+        $imp = Implementation::load('php', null, $conf);
+
+        $this->assertEquals($conf, $imp->getConfiguration());
+    }
+
+    public function testProcessBuilderFactoryGetterAndSetters()
+    {
+        $imp = Implementation::load('php');
+        $factory = $this->getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface');
+
+        $imp->setProcessBuilderFactory($factory);
+        $this->assertEquals($factory, $imp->getProcessBuilderFactory());
+    }
+
+    public function testConfigurationGetterAndSetters()
+    {
+        $imp = Implementation::load('php');
+        $conf = $this->getMock('Alchemy\BinaryDriver\ConfigurationInterface');
+
+        $imp->setConfiguration($conf);
+        $this->assertEquals($conf, $imp->getConfiguration());
+    }
+
+    public function testTimeoutIsSetOnConstruction()
+    {
+        $imp = Implementation::load('php', null, array('timeout' => 42));
+        $this->assertEquals(42, $imp->getProcessBuilderFactory()->getTimeout());
+    }
+
+    public function testTimeoutIsSetOnConfigurationSetting()
+    {
+        $imp = Implementation::load('php', null);
+        $imp->setConfiguration(new Configuration(array('timeout' => 42)));
+        $this->assertEquals(42, $imp->getProcessBuilderFactory()->getTimeout());
+    }
+
+    public function testTimeoutIsSetOnProcessBuilderSetting()
+    {
+        $imp = Implementation::load('php', null, array('timeout' => 42));
+
+        $factory = $this->getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface');
+        $factory->expects($this->once())
+            ->method('setTimeout')
+            ->with(42);
+
+        $imp->setProcessBuilderFactory($factory);
+    }
+
+    public function testListenRegistersAListener()
+    {
+        $imp = Implementation::load('php');
+
+        $listeners = $this->getMockBuilder('Alchemy\BinaryDriver\Listeners\Listeners')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $listener = $this->getMock('Alchemy\BinaryDriver\Listeners\ListenerInterface');
+
+        $listeners->expects($this->once())
+            ->method('register')
+            ->with($this->equalTo($listener), $this->equalTo($imp));
+
+        $reflexion = new \ReflectionClass('Alchemy\BinaryDriver\AbstractBinary');
+        $prop = $reflexion->getProperty('listenersManager');
+        $prop->setAccessible(true);
+        $prop->setValue($imp, $listeners);
+
+        $imp->listen($listener);
+    }
+
+    /**
+     * @dataProvider provideCommandParameters
+     */
+    public function testCommandRunsAProcess($parameters, $bypassErrors, $expectedParameters, $output)
+    {
+        $imp = Implementation::load('php');
+        $factory = $this->getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface');
+        $processRunner = $this->getMock('Alchemy\BinaryDriver\ProcessRunnerInterface');
+
+        $process = $this->getMockBuilder('Symfony\Component\Process\Process')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $processRunner->expects($this->once())
+            ->method('run')
+            ->with($this->equalTo($process), $this->isInstanceOf('SplObjectStorage'), $this->equalTo($bypassErrors))
+            ->will($this->returnValue($output));
+
+        $factory->expects($this->once())
+            ->method('create')
+            ->with($expectedParameters)
+            ->will($this->returnValue($process));
+
+        $imp->setProcessBuilderFactory($factory);
+        $imp->setProcessRunner($processRunner);
+
+        $this->assertEquals($output, $imp->command($parameters, $bypassErrors));
+    }
+
+    /**
+     * @dataProvider provideCommandWithListenersParameters
+     */
+    public function testCommandWithTemporaryListeners($parameters, $bypassErrors, $expectedParameters, $output, $count, $listeners)
+    {
+        $imp = Implementation::load('php');
+        $factory = $this->getMock('Alchemy\BinaryDriver\ProcessBuilderFactoryInterface');
+        $processRunner = $this->getMock('Alchemy\BinaryDriver\ProcessRunnerInterface');
+
+        $process = $this->getMockBuilder('Symfony\Component\Process\Process')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $firstStorage = $secondStorage = null;
+
+        $processRunner->expects($this->exactly(2))
+            ->method('run')
+            ->with($this->equalTo($process), $this->isInstanceOf('SplObjectStorage'), $this->equalTo($bypassErrors))
+            ->will($this->returnCallback(function ($process, $storage, $errors) use ($output, &$firstStorage, &$secondStorage) {
+                if (null === $firstStorage) {
+                    $firstStorage = $storage;
+                } else {
+                    $secondStorage = $storage;
+                }
+
+                return $output;
+            }));
+
+        $factory->expects($this->exactly(2))
+            ->method('create')
+            ->with($expectedParameters)
+            ->will($this->returnValue($process));
+
+        $imp->setProcessBuilderFactory($factory);
+        $imp->setProcessRunner($processRunner);
+
+        $this->assertEquals($output, $imp->command($parameters, $bypassErrors, $listeners));
+        $this->assertCount($count, $firstStorage);
+        $this->assertEquals($output, $imp->command($parameters, $bypassErrors));
+        $this->assertCount(0, $secondStorage);
+    }
+
+    public function provideCommandWithListenersParameters()
+    {
+        return array(
+            array('-a', false, array('-a'), 'loubda', 2, array($this->getMockListener(), $this->getMockListener())),
+            array('-a', false, array('-a'), 'loubda', 1, array($this->getMockListener())),
+            array('-a', false, array('-a'), 'loubda', 1, $this->getMockListener()),
+            array('-a', false, array('-a'), 'loubda', 0, array()),
+        );
+    }
+
+    public function provideCommandParameters()
+    {
+        return array(
+            array('-a', false, array('-a'), 'loubda'),
+            array('-a', true, array('-a'), 'loubda'),
+            array('-a -b', false, array('-a -b'), 'loubda'),
+            array(array('-a'), false, array('-a'), 'loubda'),
+            array(array('-a'), true, array('-a'), 'loubda'),
+            array(array('-a', '-b'), false, array('-a', '-b'), 'loubda'),
+        );
+    }
+
+    public function testUnlistenUnregistersAListener()
+    {
+        $imp = Implementation::load('php');
+
+        $listeners = $this->getMockBuilder('Alchemy\BinaryDriver\Listeners\Listeners')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $listener = $this->getMock('Alchemy\BinaryDriver\Listeners\ListenerInterface');
+
+        $listeners->expects($this->once())
+            ->method('unregister')
+            ->with($this->equalTo($listener), $this->equalTo($imp));
+
+        $reflexion = new \ReflectionClass('Alchemy\BinaryDriver\AbstractBinary');
+        $prop = $reflexion->getProperty('listenersManager');
+        $prop->setAccessible(true);
+        $prop->setValue($imp, $listeners);
+
+        $imp->unlisten($listener);
+    }
+
+    /**
+     * @return \PHPUnit_Framework_MockObject_MockObject
+     */
+    private function getMockListener()
+    {
+        $listener = $this->getMock(ListenerInterface::class);
+        $listener->expects($this->any())
+            ->method('forwardedEvents')
+            ->willReturn(array());
+
+        return $listener;
+    }
+}
+
+class Implementation extends AbstractBinary
+{
+    public function getName()
+    {
+        return 'Implementation';
+    }
+}

+ 97 - 0
vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/AbstractProcessBuilderFactoryTest.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace Alchemy\Tests\BinaryDriver;
+
+use Symfony\Component\Process\ExecutableFinder;
+use Alchemy\BinaryDriver\ProcessBuilderFactory;
+
+abstract class AbstractProcessBuilderFactoryTest extends \PHPUnit_Framework_TestCase
+{
+    public static $phpBinary;
+
+    private $original;
+    /**
+     * @return ProcessBuilderFactory
+     */
+    abstract protected function getProcessBuilderFactory($binary);
+
+    public function setUp()
+    {
+        ProcessBuilderFactory::$emulateSfLTS = null;
+        if (null === static::$phpBinary) {
+            $this->markTestSkipped('Unable to detect php binary, skipping');
+        }
+    }
+
+    public static function setUpBeforeClass()
+    {
+        $finder = new ExecutableFinder();
+        static::$phpBinary = $finder->find('php');
+    }
+
+    public function testThatBinaryIsSetOnConstruction()
+    {
+        $factory = $this->getProcessBuilderFactory(static::$phpBinary);
+        $this->assertEquals(static::$phpBinary, $factory->getBinary());
+    }
+
+    public function testGetSetBinary()
+    {
+        $finder = new ExecutableFinder();
+        $phpUnit = $finder->find('phpunit');
+
+        if (null === $phpUnit) {
+            $this->markTestSkipped('Unable to detect phpunit binary, skipping');
+        }
+
+        $factory = $this->getProcessBuilderFactory(static::$phpBinary);
+        $factory->useBinary($phpUnit);
+        $this->assertEquals($phpUnit, $factory->getBinary());
+    }
+
+    /**
+     * @expectedException Alchemy\BinaryDriver\Exception\InvalidArgumentException
+     */
+    public function testUseNonExistantBinary()
+    {
+        $factory = $this->getProcessBuilderFactory(static::$phpBinary);
+        $factory->useBinary('itissureitdoesnotexist');
+    }
+
+    public function testCreateShouldReturnAProcess()
+    {
+        $factory = $this->getProcessBuilderFactory(static::$phpBinary);
+        $process = $factory->create();
+
+        $this->assertInstanceOf('Symfony\Component\Process\Process', $process);
+        $this->assertEquals("'".static::$phpBinary."'", $process->getCommandLine());
+    }
+
+    public function testCreateWithStringArgument()
+    {
+        $factory = $this->getProcessBuilderFactory(static::$phpBinary);
+        $process = $factory->create('-v');
+
+        $this->assertInstanceOf('Symfony\Component\Process\Process', $process);
+        $this->assertEquals("'".static::$phpBinary."' '-v'", $process->getCommandLine());
+    }
+
+    public function testCreateWithArrayArgument()
+    {
+        $factory = $this->getProcessBuilderFactory(static::$phpBinary);
+        $process = $factory->create(array('-r', 'echo "Hello !";'));
+
+        $this->assertInstanceOf('Symfony\Component\Process\Process', $process);
+        $this->assertEquals("'".static::$phpBinary."' '-r' 'echo \"Hello !\";'", $process->getCommandLine());
+    }
+
+    public function testCreateWithTimeout()
+    {
+        $factory = $this->getProcessBuilderFactory(static::$phpBinary);
+        $factory->setTimeout(200);
+        $process = $factory->create(array('-i'));
+
+        $this->assertInstanceOf('Symfony\Component\Process\Process', $process);
+        $this->assertEquals(200, $process->getTimeout());
+    }
+}

+ 78 - 0
vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/ConfigurationTest.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Alchemy\Tests\BinaryDriver;
+
+use Alchemy\BinaryDriver\Configuration;
+
+class ConfigurationTest extends \PHPUnit_Framework_TestCase
+{
+    public function testArrayAccessImplementation()
+    {
+        $configuration = new Configuration(array('key' => 'value'));
+
+        $this->assertTrue(isset($configuration['key']));
+        $this->assertEquals('value', $configuration['key']);
+
+        $this->assertFalse(isset($configuration['key2']));
+        unset($configuration['key']);
+        $this->assertFalse(isset($configuration['key']));
+
+        $configuration['key2'] = 'value2';
+        $this->assertTrue(isset($configuration['key2']));
+        $this->assertEquals('value2', $configuration['key2']);
+    }
+
+    public function testGetOnNonExistentKeyShouldReturnDefaultValue()
+    {
+        $conf = new Configuration();
+        $this->assertEquals('booba', $conf->get('hooba', 'booba'));
+        $this->assertEquals(null, $conf->get('hooba'));
+    }
+
+    public function testSetHasGetRemove()
+    {
+        $configuration = new Configuration(array('key' => 'value'));
+
+        $this->assertTrue($configuration->has('key'));
+        $this->assertEquals('value', $configuration->get('key'));
+
+        $this->assertFalse($configuration->has('key2'));
+        $configuration->remove('key');
+        $this->assertFalse($configuration->has('key'));
+
+        $configuration->set('key2', 'value2');
+        $this->assertTrue($configuration->has('key2'));
+        $this->assertEquals('value2', $configuration->get('key2'));
+    }
+
+    public function testIterator()
+    {
+        $data = array(
+            'key1' => 'value1',
+            'key2' => 'value2',
+            'key3' => 'value3',
+        );
+
+        $captured = array();
+        $conf = new Configuration($data);
+
+        foreach ($conf as $key => $value) {
+            $captured[$key] = $value;
+        }
+
+        $this->assertEquals($data, $captured);
+    }
+
+    public function testAll()
+    {
+        $data = array(
+            'key1' => 'value1',
+            'key2' => 'value2',
+            'key3' => 'value3',
+        );
+
+        $conf = new Configuration($data);
+        $this->assertEquals($data, $conf->all());
+    }
+
+}

+ 54 - 0
vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/LTSProcessBuilder.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace Alchemy\Tests\BinaryDriver;
+
+use Alchemy\BinaryDriver\ProcessBuilderFactory;
+use LogicException;
+use Symfony\Component\Process\Process;
+use Symfony\Component\Process\ProcessBuilder;
+
+class LTSProcessBuilder extends ProcessBuilder
+{
+    private $arguments;
+    private $prefix;
+    private $timeout;
+
+    public function __construct(array $arguments = array())
+    {
+        $this->arguments = $arguments;
+        parent::__construct($arguments);
+    }
+
+    public function setArguments(array $arguments)
+    {
+        $this->arguments = $arguments;
+
+        return $this;
+    }
+
+    public function setPrefix($prefix)
+    {
+        $this->prefix = $prefix;
+
+        return $this;
+    }
+
+    public function setTimeout($timeout)
+    {
+        $this->timeout = $timeout;
+
+        return $this;
+    }
+
+    public function getProcess()
+    {
+        if (!$this->prefix && !count($this->arguments)) {
+            throw new LogicException('You must add() command arguments before calling getProcess().');
+        }
+
+        $args = $this->prefix ? array_merge(array($this->prefix), $this->arguments) : $this->arguments;
+        $script = implode(' ', array_map('escapeshellarg', $args));
+
+        return new Process($script, null, null, null, $this->timeout);
+    }
+}

+ 28 - 0
vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/LTSProcessBuilderFactoryTest.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Alchemy\Tests\BinaryDriver;
+
+use Alchemy\BinaryDriver\ProcessBuilderFactory;
+
+class LTSProcessBuilderFactoryTest extends AbstractProcessBuilderFactoryTest
+{
+    public function setUp()
+    {
+        if (!class_exists('Symfony\Component\Process\ProcessBuilder')) {
+            $this->markTestSkipped('ProcessBuilder is not available.');
+            return;
+        }
+
+        parent::setUp();
+    }
+
+    protected function getProcessBuilderFactory($binary)
+    {
+        $factory = new ProcessBuilderFactory($binary);
+        $factory->setBuilder(new LTSProcessBuilder());
+        ProcessBuilderFactory::$emulateSfLTS = false;
+        $factory->useBinary($binary);
+
+        return $factory;
+    }
+}

+ 33 - 0
vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/DebugListenerTest.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Alchemy\Tests\BinaryDriver\Listeners;
+
+use Alchemy\BinaryDriver\Listeners\DebugListener;
+use Symfony\Component\Process\Process;
+
+class DebugListenerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testHandle()
+    {
+        $listener = new DebugListener();
+
+        $lines = array();
+        $listener->on('debug', function ($line) use (&$lines) {
+            $lines[] = $line;
+        });
+        $listener->handle(Process::ERR, "first line\nsecond line");
+        $listener->handle(Process::OUT, "cool output");
+        $listener->handle('unknown', "lalala");
+        $listener->handle(Process::OUT, "another output\n");
+
+        $expected = array(
+            '[ERROR] first line',
+            '[ERROR] second line',
+            '[OUT] cool output',
+            '[OUT] another output',
+            '[OUT] ',
+        );
+
+        $this->assertEquals($expected, $lines);
+    }
+}

+ 92 - 0
vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/Listeners/ListenersTest.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace Alchemy\Tests\BinaryDriver\Listeners;
+
+use Alchemy\BinaryDriver\Listeners\Listeners;
+use Evenement\EventEmitter;
+use Alchemy\BinaryDriver\Listeners\ListenerInterface;
+
+class ListenersTest extends \PHPUnit_Framework_TestCase
+{
+    public function testRegister()
+    {
+        $listener = new MockListener();
+
+        $listeners = new Listeners();
+        $listeners->register($listener);
+
+        $n = 0;
+        $listener->on('received', function ($type, $data) use (&$n, &$capturedType, &$capturedData) {
+            $n++;
+            $capturedData = $data;
+            $capturedType = $type;
+        });
+
+        $type = 'type';
+        $data = 'data';
+
+        $listener->handle($type, $data);
+        $listener->handle($type, $data);
+
+        $listeners->unregister($listener);
+
+        $listener->handle($type, $data);
+
+        $this->assertEquals(3, $n);
+        $this->assertEquals($type, $capturedType);
+        $this->assertEquals($data, $capturedData);
+    }
+
+    public function testRegisterAndForwardThenUnregister()
+    {
+        $listener = new MockListener();
+        $target = new EventEmitter();
+
+        $n = 0;
+        $target->on('received', function ($type, $data) use (&$n, &$capturedType, &$capturedData) {
+            $n++;
+            $capturedData = $data;
+            $capturedType = $type;
+        });
+
+        $m = 0;
+        $listener->on('received', function ($type, $data) use (&$m, &$capturedType2, &$capturedData2) {
+            $m++;
+            $capturedData2 = $data;
+            $capturedType2 = $type;
+        });
+
+        $listeners = new Listeners();
+        $listeners->register($listener, $target);
+
+        $type = 'type';
+        $data = 'data';
+
+        $listener->handle($type, $data);
+        $listener->handle($type, $data);
+
+        $listeners->unregister($listener, $target);
+
+        $listener->handle($type, $data);
+
+        $this->assertEquals(2, $n);
+        $this->assertEquals(3, $m);
+        $this->assertEquals($type, $capturedType);
+        $this->assertEquals($data, $capturedData);
+        $this->assertEquals($type, $capturedType2);
+        $this->assertEquals($data, $capturedData2);
+    }
+}
+
+class MockListener extends EventEmitter implements ListenerInterface
+{
+    public function handle($type, $data)
+    {
+        $this->emit('received', array($type, $data));
+    }
+
+    public function forwardedEvents()
+    {
+        return array('received');
+    }
+}

+ 15 - 0
vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/NONLTSProcessBuilderFactoryTest.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Alchemy\Tests\BinaryDriver;
+
+use Alchemy\BinaryDriver\ProcessBuilderFactory;
+
+class NONLTSProcessBuilderFactoryTest extends AbstractProcessBuilderFactoryTest
+{
+    protected function getProcessBuilderFactory($binary)
+    {
+        ProcessBuilderFactory::$emulateSfLTS = true;
+
+        return new ProcessBuilderFactory($binary);
+    }
+}

+ 208 - 0
vendor/alchemy/binary-driver/tests/Alchemy/Tests/BinaryDriver/ProcessRunnerTest.php

@@ -0,0 +1,208 @@
+<?php
+
+/*
+ * This file is part of Alchemy\BinaryDriver.
+ *
+ * (c) Alchemy <info@alchemy.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Alchemy\Tests\BinaryDriver;
+
+use Alchemy\BinaryDriver\ProcessRunner;
+use Alchemy\BinaryDriver\BinaryDriverTestCase;
+use Alchemy\BinaryDriver\Exception\ExecutionFailureException;
+use Alchemy\BinaryDriver\Listeners\ListenerInterface;
+use Evenement\EventEmitter;
+use Symfony\Component\Process\Exception\RuntimeException as ProcessRuntimeException;
+
+class ProcessRunnerTest extends BinaryDriverTestCase
+{
+    public function getProcessRunner($logger)
+    {
+        return new ProcessRunner($logger, 'test-runner');
+    }
+
+    public function testRunSuccessFullProcess()
+    {
+        $logger = $this->createLoggerMock();
+        $runner = $this->getProcessRunner($logger);
+
+        $process = $this->createProcessMock(1, true, '--helloworld--', "Kikoo Romain", null, true);
+
+        $logger
+            ->expects($this->never())
+            ->method('error');
+        $logger
+            ->expects($this->exactly(2))
+            ->method('info');
+
+        $this->assertEquals('Kikoo Romain', $runner->run($process, new \SplObjectStorage(), false));
+    }
+
+    public function testRunSuccessFullProcessBypassingErrors()
+    {
+        $logger = $this->createLoggerMock();
+        $runner = $this->getProcessRunner($logger);
+
+        $process = $this->createProcessMock(1, true, '--helloworld--', "Kikoo Romain", null, true);
+
+        $logger
+            ->expects($this->never())
+            ->method('error');
+        $logger
+            ->expects($this->exactly(2))
+            ->method('info');
+
+        $this->assertEquals('Kikoo Romain', $runner->run($process, new \SplObjectStorage(), true));
+    }
+
+    public function testRunFailingProcess()
+    {
+        $logger = $this->createLoggerMock();
+        $runner = $this->getProcessRunner($logger);
+
+        $process = $this->createProcessMock(1, false, '--helloworld--', null, null, true);
+
+        $logger
+            ->expects($this->once())
+            ->method('error');
+        $logger
+            ->expects($this->once())
+            ->method('info');
+
+        try {
+            $runner->run($process, new \SplObjectStorage(), false);
+            $this->fail('An exception should have been raised');
+        } catch (ExecutionFailureException $e) {
+
+        }
+    }
+
+    public function testRunFailingProcessWithException()
+    {
+        $logger = $this->createLoggerMock();
+        $runner = $this->getProcessRunner($logger);
+
+        $exception = new ProcessRuntimeException('Process Failed');
+        $process = $this->getMockBuilder('Symfony\Component\Process\Process')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $process->expects($this->once())
+            ->method('run')
+            ->will($this->throwException($exception));
+
+        $logger
+            ->expects($this->once())
+            ->method('error');
+        $logger
+            ->expects($this->once())
+            ->method('info');
+
+        try {
+            $runner->run($process, new \SplObjectStorage(), false);
+            $this->fail('An exception should have been raised');
+        } catch (ExecutionFailureException $e) {
+            $this->assertEquals($exception, $e->getPrevious());
+        }
+    }
+
+    public function testRunfailingProcessBypassingErrors()
+    {
+        $logger = $this->createLoggerMock();
+        $runner = $this->getProcessRunner($logger);
+
+        $process = $this->createProcessMock(1, false, '--helloworld--', 'Hello output', null, true);
+
+        $logger
+            ->expects($this->once())
+            ->method('error');
+        $logger
+            ->expects($this->once())
+            ->method('info');
+
+        $this->assertNull($runner->run($process, new \SplObjectStorage(), true));
+    }
+
+    public function testRunFailingProcessWithExceptionBypassingErrors()
+    {
+        $logger = $this->createLoggerMock();
+        $runner = $this->getProcessRunner($logger);
+
+        $exception = new ProcessRuntimeException('Process Failed');
+        $process = $this->getMockBuilder('Symfony\Component\Process\Process')
+            ->disableOriginalConstructor()
+            ->getMock();
+        $process->expects($this->once())
+            ->method('run')
+            ->will($this->throwException($exception));
+
+        $logger
+            ->expects($this->once())
+            ->method('error');
+        $logger
+            ->expects($this->once())
+            ->method('info');
+
+        $this->assertNull($runner->run($process, new \SplObjectStorage(), true));
+    }
+
+    public function testRunSuccessFullProcessWithHandlers()
+    {
+        $logger = $this->createLoggerMock();
+        $runner = $this->getProcessRunner($logger);
+
+        $capturedCallback = null;
+
+        $process = $this->createProcessMock(1, true, '--helloworld--', "Kikoo Romain", null, true);
+        $process->expects($this->once())
+            ->method('run')
+            ->with($this->isInstanceOf('Closure'))
+            ->will($this->returnCallback(function ($callback) use (&$capturedCallback) {
+                $capturedCallback = $callback;
+            }));
+
+        $logger
+            ->expects($this->never())
+            ->method('error');
+        $logger
+            ->expects($this->exactly(2))
+            ->method('info');
+
+        $listener = new TestListener();
+        $storage = new \SplObjectStorage();
+        $storage->attach($listener);
+
+        $capturedType = $capturedData = null;
+
+        $listener->on('received', function ($type, $data) use (&$capturedType, &$capturedData) {
+            $capturedData = $data;
+            $capturedType = $type;
+        });
+
+        $this->assertEquals('Kikoo Romain', $runner->run($process, $storage, false));
+
+        $type = 'err';
+        $data = 'data';
+
+        $capturedCallback($type, $data);
+
+        $this->assertEquals($data, $capturedData);
+        $this->assertEquals($type, $capturedType);
+    }
+}
+
+class TestListener extends EventEmitter implements ListenerInterface
+{
+    public function handle($type, $data)
+    {
+        return $this->emit('received', array($type, $data));
+    }
+
+    public function forwardedEvents()
+    {
+        return array();
+    }
+}

+ 4 - 0
vendor/alchemy/binary-driver/tests/bootstrap.php

@@ -0,0 +1,4 @@
+<?php
+
+$loader = require __DIR__.'/../vendor/autoload.php';
+$loader->add('Alchemy\Tests', __DIR__);

+ 7 - 0
vendor/autoload.php

@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInitee83e048b575e24ed3573c8428710905::getLoader();

+ 14 - 0
vendor/bin/var-dump-server

@@ -0,0 +1,14 @@
+#!/usr/bin/env sh
+
+dir=$(cd "${0%[/\\]*}" > /dev/null; cd '../symfony/var-dumper/Resources/bin' && pwd)
+
+if [ -d /proc/cygdrive ]; then
+    case $(which php) in
+        $(readlink -n /proc/cygdrive)/*)
+            # We are in Cygwin using Windows php, so the path must be translated
+            dir=$(cygpath -m "$dir");
+            ;;
+    esac
+fi
+
+"${dir}/var-dump-server" "$@"

+ 445 - 0
vendor/composer/ClassLoader.php

@@ -0,0 +1,445 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    http://www.php-fig.org/psr/psr-0/
+ * @see    http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    // PSR-4
+    private $prefixLengthsPsr4 = array();
+    private $prefixDirsPsr4 = array();
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    private $prefixesPsr0 = array();
+    private $fallbackDirsPsr0 = array();
+
+    private $useIncludePath = false;
+    private $classMap = array();
+    private $classMapAuthoritative = false;
+    private $missingClasses = array();
+    private $apcuPrefix;
+
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', $this->prefixesPsr0);
+        }
+
+        return array();
+    }
+
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array $classMap Class to filename map
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string       $prefix  The prefix
+     * @param array|string $paths   The PSR-0 root directories
+     * @param bool         $prepend Whether to prepend the directories
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    (array) $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string       $prefix  The prefix/namespace, with trailing '\\'
+     * @param array|string $paths   The PSR-4 base directories
+     * @param bool         $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    (array) $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string       $prefix The prefix
+     * @param array|string $paths  The PSR-0 base directories
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string       $prefix The prefix/namespace, with trailing '\\'
+     * @param array|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+    }
+
+    /**
+     * The APCu prefix in use, or null if APCu caching is not enabled.
+     *
+     * @return string|null
+     */
+    public function getApcuPrefix()
+    {
+        return $this->apcuPrefix;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return bool|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            includeFile($file);
+
+            return true;
+        }
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath . '\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
+                        if (file_exists($file = $dir . $pathEnd)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+
+        return false;
+    }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+    include $file;
+}

+ 21 - 0
vendor/composer/LICENSE

@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+

+ 9 - 0
vendor/composer/autoload_classmap.php

@@ -0,0 +1,9 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);

+ 16 - 0
vendor/composer/autoload_files.php

@@ -0,0 +1,16 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
+    '538ca81a9a966a6716601ecf48f4eaef' => $vendorDir . '/opis/closure/functions.php',
+    '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
+    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
+    '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
+    '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
+    '1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php',
+);

+ 15 - 0
vendor/composer/autoload_namespaces.php

@@ -0,0 +1,15 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'PHPExcel' => array($vendorDir . '/phpoffice/phpexcel/Classes'),
+    'Neutron' => array($vendorDir . '/neutron/temporary-filesystem/src'),
+    'FFMpeg\\' => array($vendorDir . '/php-ffmpeg/php-ffmpeg/src'),
+    'Evenement' => array($vendorDir . '/evenement/evenement/src'),
+    'Alchemy' => array($vendorDir . '/alchemy/binary-driver/src'),
+    '' => array($baseDir . '/extend'),
+);

+ 30 - 0
vendor/composer/autoload_psr4.php

@@ -0,0 +1,30 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'think\\view\\driver\\' => array($vendorDir . '/topthink/think-view/src'),
+    'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'),
+    'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'),
+    'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src'),
+    'app\\' => array($baseDir . '/app'),
+    'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
+    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
+    'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
+    'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
+    'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
+    'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
+    'Sabre\\Cache\\' => array($vendorDir . '/sabre/cache/lib'),
+    'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
+    'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
+    'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
+    'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
+    'Opis\\Closure\\' => array($vendorDir . '/opis/closure/src'),
+    'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
+    'League\\Flysystem\\Cached\\' => array($vendorDir . '/league/flysystem-cached-adapter/src'),
+    'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
+    'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
+);

+ 70 - 0
vendor/composer/autoload_real.php

@@ -0,0 +1,70 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInitee83e048b575e24ed3573c8428710905
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        spl_autoload_register(array('ComposerAutoloaderInitee83e048b575e24ed3573c8428710905', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+        spl_autoload_unregister(array('ComposerAutoloaderInitee83e048b575e24ed3573c8428710905', 'loadClassLoader'));
+
+        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+        if ($useStaticLoader) {
+            require_once __DIR__ . '/autoload_static.php';
+
+            call_user_func(\Composer\Autoload\ComposerStaticInitee83e048b575e24ed3573c8428710905::getInitializer($loader));
+        } else {
+            $map = require __DIR__ . '/autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                $loader->set($namespace, $path);
+            }
+
+            $map = require __DIR__ . '/autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                $loader->setPsr4($namespace, $path);
+            }
+
+            $classMap = require __DIR__ . '/autoload_classmap.php';
+            if ($classMap) {
+                $loader->addClassMap($classMap);
+            }
+        }
+
+        $loader->register(true);
+
+        if ($useStaticLoader) {
+            $includeFiles = Composer\Autoload\ComposerStaticInitee83e048b575e24ed3573c8428710905::$files;
+        } else {
+            $includeFiles = require __DIR__ . '/autoload_files.php';
+        }
+        foreach ($includeFiles as $fileIdentifier => $file) {
+            composerRequireee83e048b575e24ed3573c8428710905($fileIdentifier, $file);
+        }
+
+        return $loader;
+    }
+}
+
+function composerRequireee83e048b575e24ed3573c8428710905($fileIdentifier, $file)
+{
+    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+        require $file;
+
+        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+    }
+}

+ 209 - 0
vendor/composer/autoload_static.php

@@ -0,0 +1,209 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInitee83e048b575e24ed3573c8428710905
+{
+    public static $files = array (
+        '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
+        '538ca81a9a966a6716601ecf48f4eaef' => __DIR__ . '/..' . '/opis/closure/functions.php',
+        '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
+        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
+        '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
+        '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
+        '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php',
+    );
+
+    public static $prefixLengthsPsr4 = array (
+        't' => 
+        array (
+            'think\\view\\driver\\' => 18,
+            'think\\captcha\\' => 14,
+            'think\\app\\' => 10,
+            'think\\' => 6,
+        ),
+        'a' => 
+        array (
+            'app\\' => 4,
+        ),
+        'S' => 
+        array (
+            'Symfony\\Polyfill\\Php72\\' => 23,
+            'Symfony\\Polyfill\\Mbstring\\' => 26,
+            'Symfony\\Polyfill\\Ctype\\' => 23,
+            'Symfony\\Component\\VarDumper\\' => 28,
+            'Symfony\\Component\\Process\\' => 26,
+            'Symfony\\Component\\Filesystem\\' => 29,
+            'Sabre\\Cache\\' => 12,
+        ),
+        'P' => 
+        array (
+            'Psr\\SimpleCache\\' => 16,
+            'Psr\\Log\\' => 8,
+            'Psr\\Container\\' => 14,
+            'Psr\\Cache\\' => 10,
+        ),
+        'O' => 
+        array (
+            'Opis\\Closure\\' => 13,
+        ),
+        'M' => 
+        array (
+            'Monolog\\' => 8,
+        ),
+        'L' => 
+        array (
+            'League\\Flysystem\\Cached\\' => 24,
+            'League\\Flysystem\\' => 17,
+        ),
+        'F' => 
+        array (
+            'Firebase\\JWT\\' => 13,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'think\\view\\driver\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/topthink/think-view/src',
+        ),
+        'think\\captcha\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/topthink/think-captcha/src',
+        ),
+        'think\\app\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/topthink/think-multi-app/src',
+        ),
+        'think\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/topthink/framework/src/think',
+            1 => __DIR__ . '/..' . '/topthink/think-helper/src',
+            2 => __DIR__ . '/..' . '/topthink/think-orm/src',
+            3 => __DIR__ . '/..' . '/topthink/think-template/src',
+        ),
+        'app\\' => 
+        array (
+            0 => __DIR__ . '/../..' . '/app',
+        ),
+        'Symfony\\Polyfill\\Php72\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-php72',
+        ),
+        'Symfony\\Polyfill\\Mbstring\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
+        ),
+        'Symfony\\Polyfill\\Ctype\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
+        ),
+        'Symfony\\Component\\VarDumper\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/var-dumper',
+        ),
+        'Symfony\\Component\\Process\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/process',
+        ),
+        'Symfony\\Component\\Filesystem\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/filesystem',
+        ),
+        'Sabre\\Cache\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/sabre/cache/lib',
+        ),
+        'Psr\\SimpleCache\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/simple-cache/src',
+        ),
+        'Psr\\Log\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
+        ),
+        'Psr\\Container\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/container/src',
+        ),
+        'Psr\\Cache\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/cache/src',
+        ),
+        'Opis\\Closure\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/opis/closure/src',
+        ),
+        'Monolog\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
+        ),
+        'League\\Flysystem\\Cached\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src',
+        ),
+        'League\\Flysystem\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/league/flysystem/src',
+        ),
+        'Firebase\\JWT\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
+        ),
+    );
+
+    public static $prefixesPsr0 = array (
+        'P' => 
+        array (
+            'PHPExcel' => 
+            array (
+                0 => __DIR__ . '/..' . '/phpoffice/phpexcel/Classes',
+            ),
+        ),
+        'N' => 
+        array (
+            'Neutron' => 
+            array (
+                0 => __DIR__ . '/..' . '/neutron/temporary-filesystem/src',
+            ),
+        ),
+        'F' => 
+        array (
+            'FFMpeg\\' => 
+            array (
+                0 => __DIR__ . '/..' . '/php-ffmpeg/php-ffmpeg/src',
+            ),
+        ),
+        'E' => 
+        array (
+            'Evenement' => 
+            array (
+                0 => __DIR__ . '/..' . '/evenement/evenement/src',
+            ),
+        ),
+        'A' => 
+        array (
+            'Alchemy' => 
+            array (
+                0 => __DIR__ . '/..' . '/alchemy/binary-driver/src',
+            ),
+        ),
+    );
+
+    public static $fallbackDirsPsr0 = array (
+        0 => __DIR__ . '/../..' . '/extend',
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInitee83e048b575e24ed3573c8428710905::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitee83e048b575e24ed3573c8428710905::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInitee83e048b575e24ed3573c8428710905::$prefixesPsr0;
+            $loader->fallbackDirsPsr0 = ComposerStaticInitee83e048b575e24ed3573c8428710905::$fallbackDirsPsr0;
+
+        }, null, ClassLoader::class);
+    }
+}

+ 1575 - 0
vendor/composer/installed.json

@@ -0,0 +1,1575 @@
+[
+    {
+        "name": "alchemy/binary-driver",
+        "version": "v2.0.0",
+        "version_normalized": "2.0.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/alchemy-fr/BinaryDriver.git",
+            "reference": "6ccde0e19e81e54da77b08da1a176d43e089f3a3"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/alchemy-fr/BinaryDriver/zipball/6ccde0e19e81e54da77b08da1a176d43e089f3a3",
+            "reference": "6ccde0e19e81e54da77b08da1a176d43e089f3a3",
+            "shasum": ""
+        },
+        "require": {
+            "evenement/evenement": "^2.0|^1.0",
+            "monolog/monolog": "^1.3",
+            "php": ">=5.5",
+            "psr/log": "^1.0",
+            "symfony/process": "^2.3|^3.0|^4.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "^4.0|^5.0"
+        },
+        "time": "2018-08-06T10:18:33+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "Alchemy": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Romain Neutron",
+                "email": "imprec@gmail.com",
+                "homepage": "http://www.lickmychip.com/"
+            },
+            {
+                "name": "Phraseanet Team",
+                "email": "info@alchemy.fr",
+                "homepage": "http://www.phraseanet.com/"
+            },
+            {
+                "name": "Nicolas Le Goff",
+                "email": "legoff.n@gmail.com"
+            },
+            {
+                "name": "Jens Hausdorf",
+                "email": "mail@jens-hausdorf.de",
+                "homepage": "https://jens-hausdorf.de",
+                "role": "Maintainer"
+            }
+        ],
+        "description": "A set of tools to build binary drivers",
+        "keywords": [
+            "binary",
+            "driver"
+        ]
+    },
+    {
+        "name": "evenement/evenement",
+        "version": "v2.1.0",
+        "version_normalized": "2.1.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/igorw/evenement.git",
+            "reference": "6ba9a777870ab49f417e703229d53931ed40fd7a"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/igorw/evenement/zipball/6ba9a777870ab49f417e703229d53931ed40fd7a",
+            "reference": "6ba9a777870ab49f417e703229d53931ed40fd7a",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.4.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "^6.0||^5.7||^4.8.35"
+        },
+        "time": "2017-07-17T17:39:19+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "2.0-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "Evenement": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Igor Wiedler",
+                "email": "igor@wiedler.ch"
+            }
+        ],
+        "description": "Événement is a very simple event dispatching library for PHP",
+        "keywords": [
+            "event-dispatcher",
+            "event-emitter"
+        ]
+    },
+    {
+        "name": "firebase/php-jwt",
+        "version": "v5.1.0",
+        "version_normalized": "5.1.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/firebase/php-jwt.git",
+            "reference": "4566062c68f76f43d44f1643f4970fe89757d4c6"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/firebase/php-jwt/zipball/4566062c68f76f43d44f1643f4970fe89757d4c6",
+            "reference": "4566062c68f76f43d44f1643f4970fe89757d4c6",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "^4.8|^5"
+        },
+        "time": "2020-02-24T23:15:03+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Firebase\\JWT\\": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "BSD-3-Clause"
+        ],
+        "authors": [
+            {
+                "name": "Neuman Vong",
+                "email": "neuman+pear@twilio.com",
+                "role": "Developer"
+            },
+            {
+                "name": "Anant Narayanan",
+                "email": "anant@php.net",
+                "role": "Developer"
+            }
+        ],
+        "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+        "homepage": "https://github.com/firebase/php-jwt"
+    },
+    {
+        "name": "league/flysystem",
+        "version": "1.0.46",
+        "version_normalized": "1.0.46.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/thephpleague/flysystem.git",
+            "reference": "f3e0d925c18b92cf3ce84ea5cc58d62a1762a2b2"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3e0d925c18b92cf3ce84ea5cc58d62a1762a2b2",
+            "reference": "f3e0d925c18b92cf3ce84ea5cc58d62a1762a2b2",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.5.9"
+        },
+        "conflict": {
+            "league/flysystem-sftp": "<1.0.6"
+        },
+        "require-dev": {
+            "ext-fileinfo": "*",
+            "phpspec/phpspec": "^3.4",
+            "phpunit/phpunit": "^5.7.10"
+        },
+        "suggest": {
+            "ext-fileinfo": "Required for MimeType",
+            "ext-ftp": "Allows you to use FTP server storage",
+            "ext-openssl": "Allows you to use FTPS server storage",
+            "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+            "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+            "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+            "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+            "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+            "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+            "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+            "league/flysystem-webdav": "Allows you to use WebDAV storage",
+            "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
+            "spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
+            "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
+        },
+        "time": "2018-08-22T07:45:22+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.1-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "League\\Flysystem\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Frank de Jonge",
+                "email": "info@frenky.net"
+            }
+        ],
+        "description": "Filesystem abstraction: Many filesystems, one API.",
+        "keywords": [
+            "Cloud Files",
+            "WebDAV",
+            "abstraction",
+            "aws",
+            "cloud",
+            "copy.com",
+            "dropbox",
+            "file systems",
+            "files",
+            "filesystem",
+            "filesystems",
+            "ftp",
+            "rackspace",
+            "remote",
+            "s3",
+            "sftp",
+            "storage"
+        ]
+    },
+    {
+        "name": "league/flysystem-cached-adapter",
+        "version": "1.0.9",
+        "version_normalized": "1.0.9.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/thephpleague/flysystem-cached-adapter.git",
+            "reference": "08ef74e9be88100807a3b92cc9048a312bf01d6f"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/08ef74e9be88100807a3b92cc9048a312bf01d6f",
+            "reference": "08ef74e9be88100807a3b92cc9048a312bf01d6f",
+            "shasum": ""
+        },
+        "require": {
+            "league/flysystem": "~1.0",
+            "psr/cache": "^1.0.0"
+        },
+        "require-dev": {
+            "mockery/mockery": "~0.9",
+            "phpspec/phpspec": "^3.4",
+            "phpunit/phpunit": "^5.7",
+            "predis/predis": "~1.0",
+            "tedivm/stash": "~0.12"
+        },
+        "suggest": {
+            "ext-phpredis": "Pure C implemented extension for PHP"
+        },
+        "time": "2018-07-09T20:51:04+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "League\\Flysystem\\Cached\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "frankdejonge",
+                "email": "info@frenky.net"
+            }
+        ],
+        "description": "An adapter decorator to enable meta-data caching."
+    },
+    {
+        "name": "monolog/monolog",
+        "version": "1.25.5",
+        "version_normalized": "1.25.5.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/Seldaek/monolog.git",
+            "reference": "1817faadd1846cd08be9a49e905dc68823bc38c0"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1817faadd1846cd08be9a49e905dc68823bc38c0",
+            "reference": "1817faadd1846cd08be9a49e905dc68823bc38c0",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0",
+            "psr/log": "~1.0"
+        },
+        "provide": {
+            "psr/log-implementation": "1.0.0"
+        },
+        "require-dev": {
+            "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+            "doctrine/couchdb": "~1.0@dev",
+            "graylog2/gelf-php": "~1.0",
+            "php-amqplib/php-amqplib": "~2.4",
+            "php-console/php-console": "^3.1.3",
+            "php-parallel-lint/php-parallel-lint": "^1.0",
+            "phpunit/phpunit": "~4.5",
+            "ruflin/elastica": ">=0.90 <3.0",
+            "sentry/sentry": "^0.13",
+            "swiftmailer/swiftmailer": "^5.3|^6.0"
+        },
+        "suggest": {
+            "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+            "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+            "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+            "ext-mongo": "Allow sending log messages to a MongoDB server",
+            "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+            "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+            "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+            "php-console/php-console": "Allow sending log messages to Google Chrome",
+            "rollbar/rollbar": "Allow sending log messages to Rollbar",
+            "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
+            "sentry/sentry": "Allow sending log messages to a Sentry server"
+        },
+        "time": "2020-07-23T08:35:51+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "2.0.x-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Monolog\\": "src/Monolog"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Jordi Boggiano",
+                "email": "j.boggiano@seld.be",
+                "homepage": "http://seld.be"
+            }
+        ],
+        "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+        "homepage": "http://github.com/Seldaek/monolog",
+        "keywords": [
+            "log",
+            "logging",
+            "psr-3"
+        ]
+    },
+    {
+        "name": "neutron/temporary-filesystem",
+        "version": "2.3.0",
+        "version_normalized": "2.3.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/romainneutron/Temporary-Filesystem.git",
+            "reference": "694aa3885f653dd429584e825ffbab79441d285f"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/romainneutron/Temporary-Filesystem/zipball/694aa3885f653dd429584e825ffbab79441d285f",
+            "reference": "694aa3885f653dd429584e825ffbab79441d285f",
+            "shasum": ""
+        },
+        "require": {
+            "php": "^5.6 || ^7.0",
+            "symfony/filesystem": "^2.3 || ^3.0 || ^4.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "^5.0"
+        },
+        "time": "2018-02-07T21:11:57+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "Neutron": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Romain Neutron",
+                "email": "imprec@gmail.com"
+            }
+        ],
+        "description": "Symfony filesystem extension to handle temporary files"
+    },
+    {
+        "name": "opis/closure",
+        "version": "3.5.1",
+        "version_normalized": "3.5.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/opis/closure.git",
+            "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/opis/closure/zipball/93ebc5712cdad8d5f489b500c59d122df2e53969",
+            "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969",
+            "shasum": ""
+        },
+        "require": {
+            "php": "^5.4 || ^7.0"
+        },
+        "require-dev": {
+            "jeremeamia/superclosure": "^2.0",
+            "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+        },
+        "time": "2019-11-29T22:36:02+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "3.5.x-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Opis\\Closure\\": "src/"
+            },
+            "files": [
+                "functions.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Marius Sarca",
+                "email": "marius.sarca@gmail.com"
+            },
+            {
+                "name": "Sorin Sarca",
+                "email": "sarca_sorin@hotmail.com"
+            }
+        ],
+        "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.",
+        "homepage": "https://opis.io/closure",
+        "keywords": [
+            "anonymous functions",
+            "closure",
+            "function",
+            "serializable",
+            "serialization",
+            "serialize"
+        ]
+    },
+    {
+        "name": "php-ffmpeg/php-ffmpeg",
+        "version": "v1.x-dev",
+        "version_normalized": "1.9999999.9999999.9999999-dev",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/PHP-FFMpeg/PHP-FFMpeg.git",
+            "reference": "8006deb04ff9136ec84405c420554093a519c47e"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/8006deb04ff9136ec84405c420554093a519c47e",
+            "reference": "8006deb04ff9136ec84405c420554093a519c47e",
+            "shasum": ""
+        },
+        "require": {
+            "alchemy/binary-driver": "^2.0",
+            "evenement/evenement": "^2.0",
+            "neutron/temporary-filesystem": "2.3",
+            "php": "^7.1",
+            "sabre/cache": "^1.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "^6.5",
+            "squizlabs/php_codesniffer": "^3.3"
+        },
+        "suggest": {
+            "php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg"
+        },
+        "time": "2018-09-15T10:52:25+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "FFMpeg\\": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Romain Neutron",
+                "email": "imprec@gmail.com",
+                "homepage": "http://www.lickmychip.com/"
+            },
+            {
+                "name": "Phraseanet Team",
+                "email": "info@alchemy.fr",
+                "homepage": "http://www.phraseanet.com/"
+            },
+            {
+                "name": "Patrik Karisch",
+                "email": "patrik@karisch.guru",
+                "homepage": "http://www.karisch.guru"
+            },
+            {
+                "name": "Romain Biard",
+                "email": "romain.biard@gmail.com",
+                "homepage": "https://www.strime.io/"
+            },
+            {
+                "name": "Jens Hausdorf",
+                "email": "hello@jens-hausdorf.de",
+                "homepage": "https://jens-hausdorf.de",
+                "role": "Maintainer"
+            }
+        ],
+        "description": "FFMpeg PHP, an Object Oriented library to communicate with AVconv / ffmpeg",
+        "keywords": [
+            "audio",
+            "audio processing",
+            "ffmpeg",
+            "ffprobe",
+            "video",
+            "video processing"
+        ]
+    },
+    {
+        "name": "phpoffice/phpexcel",
+        "version": "1.8.2",
+        "version_normalized": "1.8.2.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/PHPOffice/PHPExcel.git",
+            "reference": "1441011fb7ecdd8cc689878f54f8b58a6805f870"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/PHPOffice/PHPExcel/zipball/1441011fb7ecdd8cc689878f54f8b58a6805f870",
+            "reference": "1441011fb7ecdd8cc689878f54f8b58a6805f870",
+            "shasum": ""
+        },
+        "require": {
+            "ext-mbstring": "*",
+            "ext-xml": "*",
+            "ext-xmlwriter": "*",
+            "php": "^5.2|^7.0"
+        },
+        "require-dev": {
+            "squizlabs/php_codesniffer": "2.*"
+        },
+        "time": "2018-11-22T23:07:24+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-0": {
+                "PHPExcel": "Classes/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "LGPL-2.1"
+        ],
+        "authors": [
+            {
+                "name": "Maarten Balliauw",
+                "homepage": "http://blog.maartenballiauw.be"
+            },
+            {
+                "name": "Erik Tilt"
+            },
+            {
+                "name": "Franck Lefevre",
+                "homepage": "http://rootslabs.net"
+            },
+            {
+                "name": "Mark Baker",
+                "homepage": "http://markbakeruk.net"
+            }
+        ],
+        "description": "PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
+        "homepage": "https://github.com/PHPOffice/PHPExcel",
+        "keywords": [
+            "OpenXML",
+            "excel",
+            "php",
+            "spreadsheet",
+            "xls",
+            "xlsx"
+        ],
+        "abandoned": "phpoffice/phpspreadsheet"
+    },
+    {
+        "name": "psr/cache",
+        "version": "1.0.1",
+        "version_normalized": "1.0.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-fig/cache.git",
+            "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+            "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "time": "2016-08-06T20:24:11+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0.x-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Psr\\Cache\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP-FIG",
+                "homepage": "http://www.php-fig.org/"
+            }
+        ],
+        "description": "Common interface for caching libraries",
+        "keywords": [
+            "cache",
+            "psr",
+            "psr-6"
+        ]
+    },
+    {
+        "name": "psr/container",
+        "version": "1.0.0",
+        "version_normalized": "1.0.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-fig/container.git",
+            "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+            "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "time": "2017-02-14T16:28:37+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0.x-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Psr\\Container\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP-FIG",
+                "homepage": "http://www.php-fig.org/"
+            }
+        ],
+        "description": "Common Container Interface (PHP FIG PSR-11)",
+        "homepage": "https://github.com/php-fig/container",
+        "keywords": [
+            "PSR-11",
+            "container",
+            "container-interface",
+            "container-interop",
+            "psr"
+        ]
+    },
+    {
+        "name": "psr/log",
+        "version": "1.1.2",
+        "version_normalized": "1.1.2.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-fig/log.git",
+            "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801",
+            "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "time": "2019-11-01T11:05:21+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.1.x-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Psr\\Log\\": "Psr/Log/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP-FIG",
+                "homepage": "http://www.php-fig.org/"
+            }
+        ],
+        "description": "Common interface for logging libraries",
+        "homepage": "https://github.com/php-fig/log",
+        "keywords": [
+            "log",
+            "psr",
+            "psr-3"
+        ]
+    },
+    {
+        "name": "psr/simple-cache",
+        "version": "1.0.1",
+        "version_normalized": "1.0.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-fig/simple-cache.git",
+            "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+            "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "time": "2017-10-23T01:57:42+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0.x-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Psr\\SimpleCache\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP-FIG",
+                "homepage": "http://www.php-fig.org/"
+            }
+        ],
+        "description": "Common interfaces for simple caching",
+        "keywords": [
+            "cache",
+            "caching",
+            "psr",
+            "psr-16",
+            "simple-cache"
+        ]
+    },
+    {
+        "name": "sabre/cache",
+        "version": "1.0.1",
+        "version_normalized": "1.0.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/sabre-io/cache.git",
+            "reference": "8da140b1aa2cdb70d053c46e821c0e86a11abd6b"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/sabre-io/cache/zipball/8da140b1aa2cdb70d053c46e821c0e86a11abd6b",
+            "reference": "8da140b1aa2cdb70d053c46e821c0e86a11abd6b",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=7.0",
+            "psr/simple-cache": "^1.0"
+        },
+        "provide": {
+            "psr/simple-cache-implementation": "~1.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": ">=6.2.4",
+            "sabre/cs": "~1.0.0"
+        },
+        "time": "2019-07-19T09:34:00+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Sabre\\Cache\\": "lib/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "BSD-3-Clause"
+        ],
+        "authors": [
+            {
+                "name": "Evert Pot",
+                "email": "me@evertpot.com",
+                "homepage": "https://evertpot.com/",
+                "role": "Developer"
+            }
+        ],
+        "description": "Simple cache abstraction layer implementing PSR-16",
+        "homepage": "http://sabre.io/dav/",
+        "keywords": [
+            "apc",
+            "apcu",
+            "cache",
+            "memcache",
+            "memcached",
+            "psr-16",
+            "sabre",
+            "simple-cache"
+        ]
+    },
+    {
+        "name": "symfony/filesystem",
+        "version": "v4.4.11",
+        "version_normalized": "4.4.11.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/filesystem.git",
+            "reference": "b27f491309db5757816db672b256ea2e03677d30"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/filesystem/zipball/b27f491309db5757816db672b256ea2e03677d30",
+            "reference": "b27f491309db5757816db672b256ea2e03677d30",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=7.1.3",
+            "symfony/polyfill-ctype": "~1.8"
+        },
+        "time": "2020-05-30T18:50:54+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "4.4-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Component\\Filesystem\\": ""
+            },
+            "exclude-from-classmap": [
+                "/Tests/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Fabien Potencier",
+                "email": "fabien@symfony.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony Filesystem Component",
+        "homepage": "https://symfony.com"
+    },
+    {
+        "name": "symfony/polyfill-ctype",
+        "version": "v1.18.1",
+        "version_normalized": "1.18.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/polyfill-ctype.git",
+            "reference": "1c302646f6efc070cd46856e600e5e0684d6b454"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1c302646f6efc070cd46856e600e5e0684d6b454",
+            "reference": "1c302646f6efc070cd46856e600e5e0684d6b454",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.3"
+        },
+        "suggest": {
+            "ext-ctype": "For best performance"
+        },
+        "time": "2020-07-14T12:35:20+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.18-dev"
+            },
+            "thanks": {
+                "name": "symfony/polyfill",
+                "url": "https://github.com/symfony/polyfill"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Polyfill\\Ctype\\": ""
+            },
+            "files": [
+                "bootstrap.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Gert de Pagter",
+                "email": "BackEndTea@gmail.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony polyfill for ctype functions",
+        "homepage": "https://symfony.com",
+        "keywords": [
+            "compatibility",
+            "ctype",
+            "polyfill",
+            "portable"
+        ]
+    },
+    {
+        "name": "symfony/polyfill-mbstring",
+        "version": "v1.14.0",
+        "version_normalized": "1.14.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/polyfill-mbstring.git",
+            "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/34094cfa9abe1f0f14f48f490772db7a775559f2",
+            "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.3"
+        },
+        "suggest": {
+            "ext-mbstring": "For best performance"
+        },
+        "time": "2020-01-13T11:15:53+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.14-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Polyfill\\Mbstring\\": ""
+            },
+            "files": [
+                "bootstrap.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Nicolas Grekas",
+                "email": "p@tchwork.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony polyfill for the Mbstring extension",
+        "homepage": "https://symfony.com",
+        "keywords": [
+            "compatibility",
+            "mbstring",
+            "polyfill",
+            "portable",
+            "shim"
+        ]
+    },
+    {
+        "name": "symfony/polyfill-php72",
+        "version": "v1.14.0",
+        "version_normalized": "1.14.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/polyfill-php72.git",
+            "reference": "46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf",
+            "reference": "46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.3"
+        },
+        "time": "2020-01-13T11:15:53+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.14-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Polyfill\\Php72\\": ""
+            },
+            "files": [
+                "bootstrap.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Nicolas Grekas",
+                "email": "p@tchwork.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+        "homepage": "https://symfony.com",
+        "keywords": [
+            "compatibility",
+            "polyfill",
+            "portable",
+            "shim"
+        ]
+    },
+    {
+        "name": "symfony/process",
+        "version": "v4.4.11",
+        "version_normalized": "4.4.11.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/process.git",
+            "reference": "65e70bab62f3da7089a8d4591fb23fbacacb3479"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/process/zipball/65e70bab62f3da7089a8d4591fb23fbacacb3479",
+            "reference": "65e70bab62f3da7089a8d4591fb23fbacacb3479",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=7.1.3"
+        },
+        "time": "2020-07-23T08:31:43+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "4.4-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Symfony\\Component\\Process\\": ""
+            },
+            "exclude-from-classmap": [
+                "/Tests/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Fabien Potencier",
+                "email": "fabien@symfony.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony Process Component",
+        "homepage": "https://symfony.com"
+    },
+    {
+        "name": "symfony/var-dumper",
+        "version": "v4.4.5",
+        "version_normalized": "4.4.5.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/var-dumper.git",
+            "reference": "2572839911702b0405479410ea7a1334bfab0b96"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2572839911702b0405479410ea7a1334bfab0b96",
+            "reference": "2572839911702b0405479410ea7a1334bfab0b96",
+            "shasum": ""
+        },
+        "require": {
+            "php": "^7.1.3",
+            "symfony/polyfill-mbstring": "~1.0",
+            "symfony/polyfill-php72": "~1.5"
+        },
+        "conflict": {
+            "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+            "symfony/console": "<3.4"
+        },
+        "require-dev": {
+            "ext-iconv": "*",
+            "symfony/console": "^3.4|^4.0|^5.0",
+            "symfony/process": "^4.4|^5.0",
+            "twig/twig": "^1.34|^2.4|^3.0"
+        },
+        "suggest": {
+            "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+            "ext-intl": "To show region name in time zone dump",
+            "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+        },
+        "time": "2020-02-24T13:10:00+00:00",
+        "bin": [
+            "Resources/bin/var-dump-server"
+        ],
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "4.4-dev"
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "files": [
+                "Resources/functions/dump.php"
+            ],
+            "psr-4": {
+                "Symfony\\Component\\VarDumper\\": ""
+            },
+            "exclude-from-classmap": [
+                "/Tests/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Nicolas Grekas",
+                "email": "p@tchwork.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony mechanism for exploring and dumping PHP variables",
+        "homepage": "https://symfony.com",
+        "keywords": [
+            "debug",
+            "dump"
+        ]
+    },
+    {
+        "name": "topthink/framework",
+        "version": "6.0.x-dev",
+        "version_normalized": "6.0.9999999.9999999-dev",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/top-think/framework.git",
+            "reference": "198445bc3e2b082ee8b95db0b94cf38300bfb624"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/top-think/framework/zipball/198445bc3e2b082ee8b95db0b94cf38300bfb624",
+            "reference": "198445bc3e2b082ee8b95db0b94cf38300bfb624",
+            "shasum": ""
+        },
+        "require": {
+            "ext-json": "*",
+            "ext-mbstring": "*",
+            "league/flysystem": "^1.0",
+            "league/flysystem-cached-adapter": "^1.0",
+            "opis/closure": "^3.1",
+            "php": ">=7.1.0",
+            "psr/container": "~1.0",
+            "psr/log": "~1.0",
+            "psr/simple-cache": "^1.0",
+            "topthink/think-helper": "^3.1.1",
+            "topthink/think-orm": "^2.0"
+        },
+        "require-dev": {
+            "mikey179/vfsstream": "^1.6",
+            "mockery/mockery": "^1.2",
+            "phpunit/phpunit": "^7.0"
+        },
+        "time": "2020-03-01T13:33:21+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "files": [],
+            "psr-4": {
+                "think\\": "src/think/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "Apache-2.0"
+        ],
+        "authors": [
+            {
+                "name": "liu21st",
+                "email": "liu21st@gmail.com"
+            },
+            {
+                "name": "yunwuxin",
+                "email": "448901948@qq.com"
+            }
+        ],
+        "description": "The ThinkPHP Framework.",
+        "homepage": "http://thinkphp.cn/",
+        "keywords": [
+            "framework",
+            "orm",
+            "thinkphp"
+        ]
+    },
+    {
+        "name": "topthink/think-captcha",
+        "version": "v3.0.2",
+        "version_normalized": "3.0.2.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/top-think/think-captcha.git",
+            "reference": "0b4305da19e118cefd934007875a8112f9352f01"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/top-think/think-captcha/zipball/0b4305da19e118cefd934007875a8112f9352f01",
+            "reference": "0b4305da19e118cefd934007875a8112f9352f01",
+            "shasum": ""
+        },
+        "require": {
+            "topthink/framework": "^6.0.0"
+        },
+        "time": "2019-10-03T07:45:11+00:00",
+        "type": "library",
+        "extra": {
+            "think": {
+                "services": [
+                    "think\\captcha\\CaptchaService"
+                ],
+                "config": {
+                    "captcha": "src/config.php"
+                }
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "think\\captcha\\": "src/"
+            },
+            "files": [
+                "src/helper.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "Apache-2.0"
+        ],
+        "authors": [
+            {
+                "name": "yunwuxin",
+                "email": "448901948@qq.com"
+            }
+        ],
+        "description": "captcha package for thinkphp"
+    },
+    {
+        "name": "topthink/think-helper",
+        "version": "v3.1.3",
+        "version_normalized": "3.1.3.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/top-think/think-helper.git",
+            "reference": "4d85dfd3778623bbb1de3648f1dcd0c82f4439f4"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/top-think/think-helper/zipball/4d85dfd3778623bbb1de3648f1dcd0c82f4439f4",
+            "reference": "4d85dfd3778623bbb1de3648f1dcd0c82f4439f4",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=7.1.0"
+        },
+        "time": "2019-09-30T02:36:48+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "think\\": "src"
+            },
+            "files": [
+                "src/helper.php"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "Apache-2.0"
+        ],
+        "authors": [
+            {
+                "name": "yunwuxin",
+                "email": "448901948@qq.com"
+            }
+        ],
+        "description": "The ThinkPHP6 Helper Package"
+    },
+    {
+        "name": "topthink/think-multi-app",
+        "version": "v1.0.12",
+        "version_normalized": "1.0.12.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/top-think/think-multi-app.git",
+            "reference": "3c1914d2adc08c8753c7f67228885ca47fb9b3bf"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/top-think/think-multi-app/zipball/3c1914d2adc08c8753c7f67228885ca47fb9b3bf",
+            "reference": "3c1914d2adc08c8753c7f67228885ca47fb9b3bf",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=7.1.0",
+            "topthink/framework": "^6.0.0"
+        },
+        "time": "2020-03-01T13:29:35+00:00",
+        "type": "library",
+        "extra": {
+            "think": {
+                "services": [
+                    "think\\app\\Service"
+                ]
+            }
+        },
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "think\\app\\": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "Apache-2.0"
+        ],
+        "authors": [
+            {
+                "name": "liu21st",
+                "email": "liu21st@gmail.com"
+            }
+        ],
+        "description": "thinkphp6 multi app support"
+    },
+    {
+        "name": "topthink/think-orm",
+        "version": "2.0.x-dev",
+        "version_normalized": "2.0.9999999.9999999-dev",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/top-think/think-orm.git",
+            "reference": "f2007ddb2f4de2fbd0e966a7b52a5a408272c2e2"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/top-think/think-orm/zipball/f2007ddb2f4de2fbd0e966a7b52a5a408272c2e2",
+            "reference": "f2007ddb2f4de2fbd0e966a7b52a5a408272c2e2",
+            "shasum": ""
+        },
+        "require": {
+            "ext-json": "*",
+            "php": ">=7.1.0",
+            "psr/log": "~1.0",
+            "psr/simple-cache": "^1.0",
+            "topthink/think-helper": "^3.1"
+        },
+        "time": "2020-02-09T13:39:32+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "think\\": "src"
+            },
+            "files": []
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "Apache-2.0"
+        ],
+        "authors": [
+            {
+                "name": "liu21st",
+                "email": "liu21st@gmail.com"
+            }
+        ],
+        "description": "think orm",
+        "keywords": [
+            "database",
+            "orm"
+        ]
+    },
+    {
+        "name": "topthink/think-template",
+        "version": "v2.0.7",
+        "version_normalized": "2.0.7.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/top-think/think-template.git",
+            "reference": "e98bdbb4a4c94b442f17dfceba81e0134d4fbd19"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/top-think/think-template/zipball/e98bdbb4a4c94b442f17dfceba81e0134d4fbd19",
+            "reference": "e98bdbb4a4c94b442f17dfceba81e0134d4fbd19",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=7.1.0",
+            "psr/simple-cache": "^1.0"
+        },
+        "time": "2019-09-20T15:31:04+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "think\\": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "Apache-2.0"
+        ],
+        "authors": [
+            {
+                "name": "liu21st",
+                "email": "liu21st@gmail.com"
+            }
+        ],
+        "description": "the php template engine"
+    },
+    {
+        "name": "topthink/think-view",
+        "version": "v1.0.14",
+        "version_normalized": "1.0.14.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/top-think/think-view.git",
+            "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/top-think/think-view/zipball/edce0ae2c9551ab65f9e94a222604b0dead3576d",
+            "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=7.1.0",
+            "topthink/think-template": "^2.0"
+        },
+        "time": "2019-11-06T11:40:13+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "think\\view\\driver\\": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "Apache-2.0"
+        ],
+        "authors": [
+            {
+                "name": "liu21st",
+                "email": "liu21st@gmail.com"
+            }
+        ],
+        "description": "thinkphp template driver"
+    }
+]

+ 2 - 0
vendor/evenement/evenement/.gitignore

@@ -0,0 +1,2 @@
+composer.lock
+vendor

+ 26 - 0
vendor/evenement/evenement/.travis.yml

@@ -0,0 +1,26 @@
+language: php
+
+php:
+  - 5.4
+  - 5.5
+  - 5.6
+  - 7.0
+  - 7.1
+  - hhvm
+  - nightly
+
+matrix:
+    allow_failures:
+        - php: hhvm
+        - php: nightly
+
+before_script:
+  - wget http://getcomposer.org/composer.phar
+  - php composer.phar install
+
+script:
+  - ./vendor/bin/phpunit --coverage-text
+  - php -n examples/benchmark-emit-no-arguments.php
+  - php -n examples/benchmark-emit-one-argument.php
+  - php -n examples/benchmark-emit.php
+  - php -n examples/benchmark-emit-once.php

+ 8 - 0
vendor/evenement/evenement/CHANGELOG.md

@@ -0,0 +1,8 @@
+CHANGELOG
+=========
+
+* 2.0.0 (2012-11-02)
+
+ * Require PHP >=5.4.0
+ * Added EventEmitterTrait
+ * Removed EventEmitter2

+ 19 - 0
vendor/evenement/evenement/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2011 Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 83 - 0
vendor/evenement/evenement/README.md

@@ -0,0 +1,83 @@
+# Événement
+
+Événement is a very simple event dispatching library for PHP.
+
+It has the same design goals as [Silex](http://silex-project.org) and
+[Pimple](http://pimple-project.org), to empower the user while staying concise
+and simple.
+
+It is very strongly inspired by the EventEmitter API found in
+[node.js](http://nodejs.org).
+
+[![Build Status](https://secure.travis-ci.org/igorw/evenement.png?branch=master)](http://travis-ci.org/igorw/evenement)
+
+## Fetch
+
+The recommended way to install Événement is [through composer](http://getcomposer.org).
+
+Just create a composer.json file for your project:
+
+```JSON
+{
+    "require": {
+        "evenement/evenement": "2.0.*"
+    }
+}
+```
+
+**Note:** The `2.0.*` version of Événement requires PHP 5.4. If you are
+using PHP 5.3, please use the `1.0.*` version:
+
+```JSON
+{
+    "require": {
+        "evenement/evenement": "1.0.*"
+    }
+}
+```
+
+And run these two commands to install it:
+
+    $ curl -s http://getcomposer.org/installer | php
+    $ php composer.phar install
+
+Now you can add the autoloader, and you will have access to the library:
+
+```php
+<?php
+require 'vendor/autoload.php';
+```
+
+## Usage
+
+### Creating an Emitter
+
+```php
+<?php
+$emitter = new Evenement\EventEmitter();
+```
+
+### Adding Listeners
+
+```php
+<?php
+$emitter->on('user.created', function (User $user) use ($logger) {
+    $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+});
+```
+
+### Emitting Events
+
+```php
+<?php
+$emitter->emit('user.created', array($user));
+```
+
+Tests
+-----
+
+    $ phpunit
+
+License
+-------
+MIT, see LICENSE.

+ 34 - 0
vendor/evenement/evenement/composer.json

@@ -0,0 +1,34 @@
+{
+    "name": "evenement/evenement",
+    "description": "Événement is a very simple event dispatching library for PHP",
+    "keywords": ["event-dispatcher", "event-emitter"],
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Igor Wiedler",
+            "email": "igor@wiedler.ch"
+        }
+    ],
+    "require": {
+        "php": ">=5.4.0"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^6.0||^5.7||^4.8.35"
+    },
+    "autoload": {
+        "psr-0": {
+            "Evenement": "src"
+        }
+    },
+    "autoload-dev": {
+        "psr-0": {
+            "Evenement": "tests"
+        },
+        "files": ["tests/Evenement/Tests/functions.php"]
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "2.0-dev"
+        }
+    }
+}

+ 28 - 0
vendor/evenement/evenement/doc/00-intro.md

@@ -0,0 +1,28 @@
+# Introduction
+
+Événement is is French and means "event". The événement library aims to
+provide a simple way of subscribing to events and notifying those subscribers
+whenever an event occurs.
+
+The API that it exposes is almost a direct port of the EventEmitter API found
+in node.js. It also includes an "EventEmitter". There are some minor
+differences however.
+
+The EventEmitter is an implementation of the publish-subscribe pattern, which
+is a generalized version of the observer pattern. The observer pattern
+specifies an observable subject, which observers can register themselves to.
+Once something interesting happens, the subject notifies its observers.
+
+Pub/sub takes the same idea but encapsulates the observation logic inside a
+separate object which manages all of its subscribers or listeners. Subscribers
+are bound to an event name, and will only receive notifications of the events
+they subscribed to.
+
+**TLDR: What does evenement do, in short? It provides a mapping from event
+names to a list of listener functions and triggers each listener for a given
+event when it is emitted.**
+
+Why do we do this, you ask? To achieve decoupling.
+
+It allows you to design a system where the core will emit events, and modules
+are able to subscribe to these events. And respond to them.

+ 77 - 0
vendor/evenement/evenement/doc/01-api.md

@@ -0,0 +1,77 @@
+# API
+
+The API that événement exposes is defined by the
+`Evenement\EventEmitterInterface`. The interface is useful if you want to
+define an interface that extends the emitter and implicitly defines certain
+events to be emitted, or if you want to type hint an `EventEmitter` to be
+passed to a method without coupling to the specific implementation.
+
+## on($event, callable $listener)
+
+Allows you to subscribe to an event.
+
+Example:
+
+    $emitter->on('user.created', function (User $user) use ($logger) {
+        $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+    });
+
+Since the listener can be any callable, you could also use an instance method
+instead of the anonymous function:
+
+    $loggerSubscriber = new LoggerSubscriber($logger);
+    $emitter->on('user.created', array($loggerSubscriber, 'onUserCreated'));
+
+This has the benefit that listener does not even need to know that the emitter
+exists.
+
+You can also accept more than one parameter for the listener:
+
+    $emitter->on('numbers_added', function ($result, $a, $b) {});
+
+## once($event, callable $listener)
+
+Convenience method that adds a listener which is guaranteed to only be called
+once.
+
+Example:
+
+    $conn->once('connected', function () use ($conn, $data) {
+        $conn->send($data);
+    });
+
+## emit($event, array $arguments = [])
+
+Emit an event, which will call all listeners.
+
+Example:
+
+    $conn->emit('data', array($data));
+
+The second argument to emit is an array of listener arguments. This is how you
+specify more args:
+
+    $result = $a + $b;
+    $emitter->emit('numbers_added', array($result, $a, $b));
+
+## listeners($event)
+
+Allows you to inspect the listeners attached to an event. Particularly useful
+to check if there are any listeners at all.
+
+Example:
+
+    $e = new \RuntimeException('Everything is broken!');
+    if (0 === count($emitter->listeners('error'))) {
+        throw $e;
+    }
+
+## removeListener($event, callable $listener)
+
+Remove a specific listener for a specific event.
+
+## removeAllListeners($event = null)
+
+Remove all listeners for a specific event or all listeners alltogether. This
+is useful for long-running processes, where you want to remove listeners in
+order to allow them to get garbage collected.

+ 155 - 0
vendor/evenement/evenement/doc/02-plugin-system.md

@@ -0,0 +1,155 @@
+# Example: Plugin system
+
+In this example I will show you how to create a generic plugin system with
+événement where plugins can alter the behaviour of the app. The app is a blog.
+Boring, I know. By using the EventEmitter it will be easy to extend this blog
+with additional functionality without modifying the core system.
+
+The blog is quite basic. Users are able to create blog posts when they log in.
+The users are stored in a static config file, so there is no sign up process.
+Once logged in they get a "new post" link which gives them a form where they
+can create a new blog post with plain HTML. That will store the post in a
+document database. The index lists all blog post titles by date descending.
+Clicking on the post title will take you to the full post.
+
+## Plugin structure
+
+The goal of the plugin system is to allow features to be added to the blog
+without modifying any core files of the blog.
+
+The plugins are managed through a config file, `plugins.json`. This JSON file
+contains a JSON-encoded list of class-names for plugin classes. This allows
+you to enable and disable plugins in a central location. The initial
+`plugins.json` is just an empty array:
+```json
+[]
+```
+
+A plugin class must implement the `PluginInterface`:
+```php
+interface PluginInterface
+{
+    function attachEvents(EventEmitterInterface $emitter);
+}
+```
+
+The `attachEvents` method allows the plugin to attach any events to the
+emitter. For example:
+```php
+class FooPlugin implements PluginInterface
+{
+    public function attachEvents(EventEmitterInterface $emitter)
+    {
+        $emitter->on('foo', function () {
+            echo 'bar!';
+        });
+    }
+}
+```
+
+The blog system creates an emitter instance and loads the plugins:
+```php
+$emitter = new EventEmitter();
+
+$pluginClasses = json_decode(file_get_contents('plugins.json'), true);
+foreach ($pluginClasses as $pluginClass) {
+    $plugin = new $pluginClass();
+    $pluginClass->attachEvents($emitter);
+}
+```
+
+This is the base system. There are no plugins yet, and there are no events yet
+either. That's because I don't know which extension points will be needed. I
+will add them on demand.
+
+## Feature: Markdown
+
+Writing blog posts in HTML sucks! Wouldn't it be great if I could write them
+in a nice format such as markdown, and have that be converted to HTML for me?
+
+This feature will need two extension points. I need to be able to mark posts
+as markdown, and I need to be able to hook into the rendering of the post body
+and convert it from markdown to HTML. So the blog needs two new events:
+`post.create` and `post.render`.
+
+In the code that creates the post, I'll insert the `post.create` event:
+```php
+class PostEvent
+{
+    public $post;
+
+    public function __construct(array $post)
+    {
+        $this->post = $post;
+    }
+}
+
+$post = createPostFromRequest($_POST);
+
+$event = new PostEvent($post);
+$emitter->emit('post.create', array($event));
+$post = $event->post;
+
+$db->save('post', $post);
+```
+
+This shows that you can wrap a value in an event object to make it mutable,
+allowing listeners to change it.
+
+The same thing for the `post.render` event:
+```php
+public function renderPostBody(array $post)
+{
+    $emitter = $this->emitter;
+
+    $event = new PostEvent($post);
+    $emitter->emit('post.render', array($event));
+    $post = $event->post;
+
+    return $post['body'];
+}
+
+<h1><?= $post['title'] %></h1>
+<p><?= renderPostBody($post) %></p>
+```
+
+Ok, the events are in place. It's time to create the first plugin, woohoo! I
+will call this the `MarkdownPlugin`, so here's `plugins.json`:
+```json
+[
+    "MarkdownPlugin"
+]
+```
+
+The `MarkdownPlugin` class will be autoloaded, so I don't have to worry about
+including any files. I just have to worry about implementing the plugin class.
+The `markdown` function represents a markdown to HTML converter.
+```php
+class MarkdownPlugin implements PluginInterface
+{
+    public function attachEvents(EventEmitterInterface $emitter)
+    {
+        $emitter->on('post.create', function (PostEvent $event) {
+            $event->post['format'] = 'markdown';
+        });
+
+        $emitter->on('post.render', function (PostEvent $event) {
+            if (isset($event->post['format']) && 'markdown' === $event->post['format']) {
+                $event->post['body'] = markdown($event->post['body']);
+            }
+        });
+    }
+}
+```
+
+There you go, the blog now renders posts as markdown. But all of the previous
+posts before the addition of the markdown plugin are still rendered correctly
+as raw HTML.
+
+## Feature: Comments
+
+TODO
+
+## Feature: Comment spam control
+
+TODO

+ 28 - 0
vendor/evenement/evenement/examples/benchmark-emit-no-arguments.php

@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function () {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+    $emitter->emit('event');
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;

+ 30 - 0
vendor/evenement/evenement/examples/benchmark-emit-once.php

@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+ini_set('memory_limit', '512M');
+
+const ITERATIONS = 100000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+for ($i = 0; $i < ITERATIONS; $i++) {
+    $emitter->once('event', function ($a, $b, $c) {});
+}
+
+$start = microtime(true);
+$emitter->emit('event', [1, 2, 3]);
+$time = microtime(true) - $start;
+
+echo 'Emitting one event to ', number_format(ITERATIONS), ' once listeners took: ', number_format($time, 2), 's', PHP_EOL;

+ 28 - 0
vendor/evenement/evenement/examples/benchmark-emit-one-argument.php

@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function ($a) {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+    $emitter->emit('event', [1]);
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;

+ 28 - 0
vendor/evenement/evenement/examples/benchmark-emit.php

@@ -0,0 +1,28 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+const ITERATIONS = 10000000;
+
+use Evenement\EventEmitter;
+
+require __DIR__.'/../vendor/autoload.php';
+
+$emitter = new EventEmitter();
+
+$emitter->on('event', function ($a, $b, $c) {});
+
+$start = microtime(true);
+for ($i = 0; $i < ITERATIONS; $i++) {
+    $emitter->emit('event', [1, 2, 3]);
+}
+$time = microtime(true) - $start;
+
+echo 'Emitting ', number_format(ITERATIONS), ' events took: ', number_format($time, 2), 's', PHP_EOL;

+ 24 - 0
vendor/evenement/evenement/phpunit.xml.dist

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         syntaxCheck="false"
+>
+    <testsuites>
+        <testsuite name="Evenement Test Suite">
+            <directory>./tests/Evenement/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory>./src/</directory>
+        </whitelist>
+    </filter>
+</phpunit>

+ 17 - 0
vendor/evenement/evenement/src/Evenement/EventEmitter.php

@@ -0,0 +1,17 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+class EventEmitter implements EventEmitterInterface
+{
+    use EventEmitterTrait;
+}

+ 22 - 0
vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php

@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+interface EventEmitterInterface
+{
+    public function on($event, callable $listener);
+    public function once($event, callable $listener);
+    public function removeListener($event, callable $listener);
+    public function removeAllListeners($event = null);
+    public function listeners($event);
+    public function emit($event, array $arguments = []);
+}

+ 73 - 0
vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php

@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+trait EventEmitterTrait
+{
+    protected $listeners = [];
+
+    public function on($event, callable $listener)
+    {
+        if (!isset($this->listeners[$event])) {
+            $this->listeners[$event] = [];
+        }
+
+        $this->listeners[$event][] = $listener;
+
+        return $this;
+    }
+
+    public function once($event, callable $listener)
+    {
+        $onceListener = function () use (&$onceListener, $event, $listener) {
+            $this->removeListener($event, $onceListener);
+
+            \call_user_func_array($listener, \func_get_args());
+        };
+
+        $this->on($event, $onceListener);
+    }
+
+    public function removeListener($event, callable $listener)
+    {
+        if (isset($this->listeners[$event])) {
+            $index = \array_search($listener, $this->listeners[$event], true);
+            if (false !== $index) {
+                unset($this->listeners[$event][$index]);
+                if (\count($this->listeners[$event]) === 0) {
+                    unset($this->listeners[$event]);
+                }
+            }
+        }
+    }
+
+    public function removeAllListeners($event = null)
+    {
+        if ($event !== null) {
+            unset($this->listeners[$event]);
+        } else {
+            $this->listeners = [];
+        }
+    }
+
+    public function listeners($event)
+    {
+        return isset($this->listeners[$event]) ? $this->listeners[$event] : [];
+    }
+
+    public function emit($event, array $arguments = [])
+    {
+        foreach ($this->listeners($event) as $listener) {
+            \call_user_func_array($listener, $arguments);
+        }
+    }
+}

+ 291 - 0
vendor/evenement/evenement/tests/Evenement/Tests/EventEmitterTest.php

@@ -0,0 +1,291 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+use Evenement\EventEmitter;
+use PHPUnit\Framework\TestCase;
+
+class EventEmitterTest extends TestCase
+{
+    private $emitter;
+
+    public function setUp()
+    {
+        $this->emitter = new EventEmitter();
+    }
+
+    public function testAddListenerWithLambda()
+    {
+        $this->emitter->on('foo', function () {});
+    }
+
+    public function testAddListenerWithMethod()
+    {
+        $listener = new Listener();
+        $this->emitter->on('foo', [$listener, 'onFoo']);
+    }
+
+    public function testAddListenerWithStaticMethod()
+    {
+        $this->emitter->on('bar', ['Evenement\Tests\Listener', 'onBar']);
+    }
+
+    public function testAddListenerWithInvalidListener()
+    {
+        try {
+            $this->emitter->on('foo', 'not a callable');
+            $this->fail();
+        } catch (\Exception $e) {
+        } catch (\TypeError $e) {
+        }
+    }
+
+    public function testOnce()
+    {
+        $listenerCalled = 0;
+
+        $this->emitter->once('foo', function () use (&$listenerCalled) {
+            $listenerCalled++;
+        });
+
+        $this->assertSame(0, $listenerCalled);
+
+        $this->emitter->emit('foo');
+
+        $this->assertSame(1, $listenerCalled);
+
+        $this->emitter->emit('foo');
+
+        $this->assertSame(1, $listenerCalled);
+    }
+
+    public function testOnceWithArguments()
+    {
+        $capturedArgs = [];
+
+        $this->emitter->once('foo', function ($a, $b) use (&$capturedArgs) {
+            $capturedArgs = array($a, $b);
+        });
+
+        $this->emitter->emit('foo', array('a', 'b'));
+
+        $this->assertSame(array('a', 'b'), $capturedArgs);
+    }
+
+    public function testEmitWithoutArguments()
+    {
+        $listenerCalled = false;
+
+        $this->emitter->on('foo', function () use (&$listenerCalled) {
+            $listenerCalled = true;
+        });
+
+        $this->assertSame(false, $listenerCalled);
+        $this->emitter->emit('foo');
+        $this->assertSame(true, $listenerCalled);
+    }
+
+    public function testEmitWithOneArgument()
+    {
+        $test = $this;
+
+        $listenerCalled = false;
+
+        $this->emitter->on('foo', function ($value) use (&$listenerCalled, $test) {
+            $listenerCalled = true;
+
+            $test->assertSame('bar', $value);
+        });
+
+        $this->assertSame(false, $listenerCalled);
+        $this->emitter->emit('foo', ['bar']);
+        $this->assertSame(true, $listenerCalled);
+    }
+
+    public function testEmitWithTwoArguments()
+    {
+        $test = $this;
+
+        $listenerCalled = false;
+
+        $this->emitter->on('foo', function ($arg1, $arg2) use (&$listenerCalled, $test) {
+            $listenerCalled = true;
+
+            $test->assertSame('bar', $arg1);
+            $test->assertSame('baz', $arg2);
+        });
+
+        $this->assertSame(false, $listenerCalled);
+        $this->emitter->emit('foo', ['bar', 'baz']);
+        $this->assertSame(true, $listenerCalled);
+    }
+
+    public function testEmitWithNoListeners()
+    {
+        $this->emitter->emit('foo');
+        $this->emitter->emit('foo', ['bar']);
+        $this->emitter->emit('foo', ['bar', 'baz']);
+    }
+
+    public function testEmitWithTwoListeners()
+    {
+        $listenersCalled = 0;
+
+        $this->emitter->on('foo', function () use (&$listenersCalled) {
+            $listenersCalled++;
+        });
+
+        $this->emitter->on('foo', function () use (&$listenersCalled) {
+            $listenersCalled++;
+        });
+
+        $this->assertSame(0, $listenersCalled);
+        $this->emitter->emit('foo');
+        $this->assertSame(2, $listenersCalled);
+    }
+
+    public function testRemoveListenerMatching()
+    {
+        $listenersCalled = 0;
+
+        $listener = function () use (&$listenersCalled) {
+            $listenersCalled++;
+        };
+
+        $this->emitter->on('foo', $listener);
+        $this->emitter->removeListener('foo', $listener);
+
+        $this->assertSame(0, $listenersCalled);
+        $this->emitter->emit('foo');
+        $this->assertSame(0, $listenersCalled);
+    }
+
+    public function testRemoveListenerNotMatching()
+    {
+        $listenersCalled = 0;
+
+        $listener = function () use (&$listenersCalled) {
+            $listenersCalled++;
+        };
+
+        $this->emitter->on('foo', $listener);
+        $this->emitter->removeListener('bar', $listener);
+
+        $this->assertSame(0, $listenersCalled);
+        $this->emitter->emit('foo');
+        $this->assertSame(1, $listenersCalled);
+    }
+
+    public function testRemoveAllListenersMatching()
+    {
+        $listenersCalled = 0;
+
+        $this->emitter->on('foo', function () use (&$listenersCalled) {
+            $listenersCalled++;
+        });
+
+        $this->emitter->removeAllListeners('foo');
+
+        $this->assertSame(0, $listenersCalled);
+        $this->emitter->emit('foo');
+        $this->assertSame(0, $listenersCalled);
+    }
+
+    public function testRemoveAllListenersNotMatching()
+    {
+        $listenersCalled = 0;
+
+        $this->emitter->on('foo', function () use (&$listenersCalled) {
+            $listenersCalled++;
+        });
+
+        $this->emitter->removeAllListeners('bar');
+
+        $this->assertSame(0, $listenersCalled);
+        $this->emitter->emit('foo');
+        $this->assertSame(1, $listenersCalled);
+    }
+
+    public function testRemoveAllListenersWithoutArguments()
+    {
+        $listenersCalled = 0;
+
+        $this->emitter->on('foo', function () use (&$listenersCalled) {
+            $listenersCalled++;
+        });
+
+        $this->emitter->on('bar', function () use (&$listenersCalled) {
+            $listenersCalled++;
+        });
+
+        $this->emitter->removeAllListeners();
+
+        $this->assertSame(0, $listenersCalled);
+        $this->emitter->emit('foo');
+        $this->emitter->emit('bar');
+        $this->assertSame(0, $listenersCalled);
+    }
+
+    public function testCallablesClosure()
+    {
+        $calledWith = null;
+
+        $this->emitter->on('foo', function ($data) use (&$calledWith) {
+            $calledWith = $data;
+        });
+
+        $this->emitter->emit('foo', ['bar']);
+
+        self::assertSame('bar', $calledWith);
+    }
+
+    public function testCallablesClass()
+    {
+        $listener = new Listener();
+        $this->emitter->on('foo', [$listener, 'onFoo']);
+
+        $this->emitter->emit('foo', ['bar']);
+
+        self::assertSame(['bar'], $listener->getData());
+    }
+
+
+    public function testCallablesClassInvoke()
+    {
+        $listener = new Listener();
+        $this->emitter->on('foo', $listener);
+
+        $this->emitter->emit('foo', ['bar']);
+
+        self::assertSame(['bar'], $listener->getMagicData());
+    }
+
+    public function testCallablesStaticClass()
+    {
+        $this->emitter->on('foo', '\Evenement\Tests\Listener::onBar');
+
+        $this->emitter->emit('foo', ['bar']);
+
+        self::assertSame(['bar'], Listener::getStaticData());
+    }
+
+    public function testCallablesFunction()
+    {
+        $this->emitter->on('foo', '\Evenement\Tests\setGlobalTestData');
+
+        $this->emitter->emit('foo', ['bar']);
+
+        self::assertSame('bar', $GLOBALS['evenement-evenement-test-data']);
+
+        unset($GLOBALS['evenement-evenement-test-data']);
+    }
+}

+ 51 - 0
vendor/evenement/evenement/tests/Evenement/Tests/Listener.php

@@ -0,0 +1,51 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+class Listener
+{
+    private $data = [];
+
+    private $magicData = [];
+
+    private static $staticData = [];
+
+    public function onFoo($data)
+    {
+        $this->data[] = $data;
+    }
+
+    public function __invoke($data)
+    {
+        $this->magicData[] = $data;
+    }
+
+    public static function onBar($data)
+    {
+        self::$staticData[] = $data;
+    }
+
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    public function getMagicData()
+    {
+        return $this->magicData;
+    }
+
+    public static function getStaticData()
+    {
+        return self::$staticData;
+    }
+}

+ 17 - 0
vendor/evenement/evenement/tests/Evenement/Tests/functions.php

@@ -0,0 +1,17 @@
+<?php
+
+/*
+ * This file is part of Evenement.
+ *
+ * (c) Igor Wiedler <igor@wiedler.ch>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement\Tests;
+
+function setGlobalTestData($data)
+{
+    $GLOBALS['evenement-evenement-test-data'] = $data;
+}

+ 30 - 0
vendor/firebase/php-jwt/LICENSE

@@ -0,0 +1,30 @@
+Copyright (c) 2011, Neuman Vong
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Neuman Vong nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 200 - 0
vendor/firebase/php-jwt/README.md

@@ -0,0 +1,200 @@
+[![Build Status](https://travis-ci.org/firebase/php-jwt.png?branch=master)](https://travis-ci.org/firebase/php-jwt)
+[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt)
+[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt)
+[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt)
+
+PHP-JWT
+=======
+A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519).
+
+Installation
+------------
+
+Use composer to manage your dependencies and download PHP-JWT:
+
+```bash
+composer require firebase/php-jwt
+```
+
+Example
+-------
+```php
+<?php
+use \Firebase\JWT\JWT;
+
+$key = "example_key";
+$payload = array(
+    "iss" => "http://example.org",
+    "aud" => "http://example.com",
+    "iat" => 1356999524,
+    "nbf" => 1357000000
+);
+
+/**
+ * IMPORTANT:
+ * You must specify supported algorithms for your application. See
+ * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
+ * for a list of spec-compliant algorithms.
+ */
+$jwt = JWT::encode($payload, $key);
+$decoded = JWT::decode($jwt, $key, array('HS256'));
+
+print_r($decoded);
+
+/*
+ NOTE: This will now be an object instead of an associative array. To get
+ an associative array, you will need to cast it as such:
+*/
+
+$decoded_array = (array) $decoded;
+
+/**
+ * You can add a leeway to account for when there is a clock skew times between
+ * the signing and verifying servers. It is recommended that this leeway should
+ * not be bigger than a few minutes.
+ *
+ * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef
+ */
+JWT::$leeway = 60; // $leeway in seconds
+$decoded = JWT::decode($jwt, $key, array('HS256'));
+
+?>
+```
+Example with RS256 (openssl)
+----------------------------
+```php
+<?php
+use \Firebase\JWT\JWT;
+
+$privateKey = <<<EOD
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn
+vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9
+5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB
+AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz
+bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J
+Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1
+cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5
+5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck
+ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe
+k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb
+qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k
+eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm
+B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM=
+-----END RSA PRIVATE KEY-----
+EOD;
+
+$publicKey = <<<EOD
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8kGa1pSjbSYZVebtTRBLxBz5H
+4i2p/llLCrEeQhta5kaQu/RnvuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t
+0tyazyZ8JXw+KgXTxldMPEL95+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4
+ehde/zUxo6UvS7UrBQIDAQAB
+-----END PUBLIC KEY-----
+EOD;
+
+$payload = array(
+    "iss" => "example.org",
+    "aud" => "example.com",
+    "iat" => 1356999524,
+    "nbf" => 1357000000
+);
+
+$jwt = JWT::encode($payload, $privateKey, 'RS256');
+echo "Encode:\n" . print_r($jwt, true) . "\n";
+
+$decoded = JWT::decode($jwt, $publicKey, array('RS256'));
+
+/*
+ NOTE: This will now be an object instead of an associative array. To get
+ an associative array, you will need to cast it as such:
+*/
+
+$decoded_array = (array) $decoded;
+echo "Decode:\n" . print_r($decoded_array, true) . "\n";
+?>
+```
+
+Changelog
+---------
+
+#### 5.0.0 / 2017-06-26
+- Support RS384 and RS512.
+  See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)!
+- Add an example for RS256 openssl.
+  See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)!
+- Detect invalid Base64 encoding in signature.
+  See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)!
+- Update `JWT::verify` to handle OpenSSL errors.
+  See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)!
+- Add `array` type hinting to `decode` method
+  See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)!
+- Add all JSON error types.
+  See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)!
+- Bugfix 'kid' not in given key list.
+  See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)!
+- Miscellaneous cleanup, documentation and test fixes.
+  See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115),
+  [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and
+  [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman),
+  [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)!
+
+#### 4.0.0 / 2016-07-17
+- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)!
+- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)!
+- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)!
+- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)!
+
+#### 3.0.0 / 2015-07-22
+- Minimum PHP version updated from `5.2.0` to `5.3.0`.
+- Add `\Firebase\JWT` namespace. See
+[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to
+[@Dashron](https://github.com/Dashron)!
+- Require a non-empty key to decode and verify a JWT. See
+[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to
+[@sjones608](https://github.com/sjones608)!
+- Cleaner documentation blocks in the code. See
+[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to
+[@johanderuijter](https://github.com/johanderuijter)!
+
+#### 2.2.0 / 2015-06-22
+- Add support for adding custom, optional JWT headers to `JWT::encode()`. See
+[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to
+[@mcocaro](https://github.com/mcocaro)!
+
+#### 2.1.0 / 2015-05-20
+- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew
+between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)!
+- Add support for passing an object implementing the `ArrayAccess` interface for
+`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)!
+
+#### 2.0.0 / 2015-04-01
+- **Note**: It is strongly recommended that you update to > v2.0.0 to address
+  known security vulnerabilities in prior versions when both symmetric and
+  asymmetric keys are used together.
+- Update signature for `JWT::decode(...)` to require an array of supported
+  algorithms to use when verifying token signatures.
+
+
+Tests
+-----
+Run the tests using phpunit:
+
+```bash
+$ pear install PHPUnit
+$ phpunit --configuration phpunit.xml.dist
+PHPUnit 3.7.10 by Sebastian Bergmann.
+.....
+Time: 0 seconds, Memory: 2.50Mb
+OK (5 tests, 5 assertions)
+```
+
+New Lines in private keys
+-----
+
+If your private key contains `\n` characters, be sure to wrap it in double quotes `""`
+and not single quotes `''` in order to properly interpret the escaped characters.
+
+License
+-------
+[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause).

+ 29 - 0
vendor/firebase/php-jwt/composer.json

@@ -0,0 +1,29 @@
+{
+    "name": "firebase/php-jwt",
+    "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+    "homepage": "https://github.com/firebase/php-jwt",
+    "authors": [
+        {
+            "name": "Neuman Vong",
+            "email": "neuman+pear@twilio.com",
+            "role": "Developer"
+        },
+        {
+            "name": "Anant Narayanan",
+            "email": "anant@php.net",
+            "role": "Developer"
+        }
+    ],
+    "license": "BSD-3-Clause",
+    "require": {
+        "php": ">=5.3.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Firebase\\JWT\\": "src"
+        }
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^4.8|^5"
+    }
+}

+ 7 - 0
vendor/firebase/php-jwt/src/BeforeValidException.php

@@ -0,0 +1,7 @@
+<?php
+namespace Firebase\JWT;
+
+class BeforeValidException extends \UnexpectedValueException
+{
+
+}

+ 7 - 0
vendor/firebase/php-jwt/src/ExpiredException.php

@@ -0,0 +1,7 @@
+<?php
+namespace Firebase\JWT;
+
+class ExpiredException extends \UnexpectedValueException
+{
+
+}

+ 514 - 0
vendor/firebase/php-jwt/src/JWT.php

@@ -0,0 +1,514 @@
+<?php
+
+namespace Firebase\JWT;
+
+use \DomainException;
+use \InvalidArgumentException;
+use \UnexpectedValueException;
+use \DateTime;
+
+/**
+ * JSON Web Token implementation, based on this spec:
+ * https://tools.ietf.org/html/rfc7519
+ *
+ * PHP version 5
+ *
+ * @category Authentication
+ * @package  Authentication_JWT
+ * @author   Neuman Vong <neuman@twilio.com>
+ * @author   Anant Narayanan <anant@php.net>
+ * @license  http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
+ * @link     https://github.com/firebase/php-jwt
+ */
+class JWT
+{
+    const ASN1_INTEGER = 0x02;
+    const ASN1_SEQUENCE = 0x10;
+    const ASN1_BIT_STRING = 0x03;
+
+    /**
+     * When checking nbf, iat or expiration times,
+     * we want to provide some extra leeway time to
+     * account for clock skew.
+     */
+    public static $leeway = 0;
+
+    /**
+     * Allow the current timestamp to be specified.
+     * Useful for fixing a value within unit testing.
+     *
+     * Will default to PHP time() value if null.
+     */
+    public static $timestamp = null;
+
+    public static $supported_algs = array(
+        'ES256' => array('openssl', 'SHA256'),
+        'HS256' => array('hash_hmac', 'SHA256'),
+        'HS384' => array('hash_hmac', 'SHA384'),
+        'HS512' => array('hash_hmac', 'SHA512'),
+        'RS256' => array('openssl', 'SHA256'),
+        'RS384' => array('openssl', 'SHA384'),
+        'RS512' => array('openssl', 'SHA512'),
+    );
+
+    /**
+     * Decodes a JWT string into a PHP object.
+     *
+     * @param string                    $jwt            The JWT
+     * @param string|array|resource     $key            The key, or map of keys.
+     *                                                  If the algorithm used is asymmetric, this is the public key
+     * @param array                     $allowed_algs   List of supported verification algorithms
+     *                                                  Supported algorithms are 'ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
+     *
+     * @return object The JWT's payload as a PHP object
+     *
+     * @throws UnexpectedValueException     Provided JWT was invalid
+     * @throws SignatureInvalidException    Provided JWT was invalid because the signature verification failed
+     * @throws BeforeValidException         Provided JWT is trying to be used before it's eligible as defined by 'nbf'
+     * @throws BeforeValidException         Provided JWT is trying to be used before it's been created as defined by 'iat'
+     * @throws ExpiredException             Provided JWT has since expired, as defined by the 'exp' claim
+     *
+     * @uses jsonDecode
+     * @uses urlsafeB64Decode
+     */
+    public static function decode($jwt, $key, array $allowed_algs = array())
+    {
+        $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;
+
+        if (empty($key)) {
+            throw new InvalidArgumentException('Key may not be empty');
+        }
+        $tks = explode('.', $jwt);
+        if (count($tks) != 3) {
+            throw new UnexpectedValueException('Wrong number of segments');
+        }
+        list($headb64, $bodyb64, $cryptob64) = $tks;
+        if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
+            throw new UnexpectedValueException('Invalid header encoding');
+        }
+        if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
+            throw new UnexpectedValueException('Invalid claims encoding');
+        }
+        if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
+            throw new UnexpectedValueException('Invalid signature encoding');
+        }
+        if (empty($header->alg)) {
+            throw new UnexpectedValueException('Empty algorithm');
+        }
+        if (empty(static::$supported_algs[$header->alg])) {
+            throw new UnexpectedValueException('Algorithm not supported');
+        }
+        if (!in_array($header->alg, $allowed_algs)) {
+            throw new UnexpectedValueException('Algorithm not allowed');
+        }
+        if ($header->alg === 'ES256') {
+            // OpenSSL expects an ASN.1 DER sequence for ES256 signatures
+            $sig = self::signatureToDER($sig);
+        }
+
+        if (is_array($key) || $key instanceof \ArrayAccess) {
+            if (isset($header->kid)) {
+                if (!isset($key[$header->kid])) {
+                    throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
+                }
+                $key = $key[$header->kid];
+            } else {
+                throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
+            }
+        }
+
+        // Check the signature
+        if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
+            throw new SignatureInvalidException('Signature verification failed');
+        }
+
+        // Check the nbf if it is defined. This is the time that the
+        // token can actually be used. If it's not yet that time, abort.
+        if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
+            throw new BeforeValidException(
+                'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
+            );
+        }
+
+        // Check that this token has been created before 'now'. This prevents
+        // using tokens that have been created for later use (and haven't
+        // correctly used the nbf claim).
+        if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
+            throw new BeforeValidException(
+                'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
+            );
+        }
+
+        // Check if this token has expired.
+        if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
+            throw new ExpiredException('Expired token');
+        }
+
+        return $payload;
+    }
+
+    /**
+     * Converts and signs a PHP object or array into a JWT string.
+     *
+     * @param object|array  $payload    PHP object or array
+     * @param string        $key        The secret key.
+     *                                  If the algorithm used is asymmetric, this is the private key
+     * @param string        $alg        The signing algorithm.
+     *                                  Supported algorithms are 'ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
+     * @param mixed         $keyId
+     * @param array         $head       An array with header elements to attach
+     *
+     * @return string A signed JWT
+     *
+     * @uses jsonEncode
+     * @uses urlsafeB64Encode
+     */
+    public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
+    {
+        $header = array('typ' => 'JWT', 'alg' => $alg);
+        if ($keyId !== null) {
+            $header['kid'] = $keyId;
+        }
+        if (isset($head) && is_array($head)) {
+            $header = array_merge($head, $header);
+        }
+        $segments = array();
+        $segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
+        $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
+        $signing_input = implode('.', $segments);
+
+        $signature = static::sign($signing_input, $key, $alg);
+        $segments[] = static::urlsafeB64Encode($signature);
+
+        return implode('.', $segments);
+    }
+
+    /**
+     * Sign a string with a given key and algorithm.
+     *
+     * @param string            $msg    The message to sign
+     * @param string|resource   $key    The secret key
+     * @param string            $alg    The signing algorithm.
+     *                                  Supported algorithms are 'ES256', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
+     *
+     * @return string An encrypted message
+     *
+     * @throws DomainException Unsupported algorithm was specified
+     */
+    public static function sign($msg, $key, $alg = 'HS256')
+    {
+        if (empty(static::$supported_algs[$alg])) {
+            throw new DomainException('Algorithm not supported');
+        }
+        list($function, $algorithm) = static::$supported_algs[$alg];
+        switch ($function) {
+            case 'hash_hmac':
+                return hash_hmac($algorithm, $msg, $key, true);
+            case 'openssl':
+                $signature = '';
+                $success = openssl_sign($msg, $signature, $key, $algorithm);
+                if (!$success) {
+                    throw new DomainException("OpenSSL unable to sign data");
+                } else {
+                    if ($alg === 'ES256') {
+                        $signature = self::signatureFromDER($signature, 256);
+                    }
+                    return $signature;
+                }
+        }
+    }
+
+    /**
+     * Verify a signature with the message, key and method. Not all methods
+     * are symmetric, so we must have a separate verify and sign method.
+     *
+     * @param string            $msg        The original message (header and body)
+     * @param string            $signature  The original signature
+     * @param string|resource   $key        For HS*, a string key works. for RS*, must be a resource of an openssl public key
+     * @param string            $alg        The algorithm
+     *
+     * @return bool
+     *
+     * @throws DomainException Invalid Algorithm or OpenSSL failure
+     */
+    private static function verify($msg, $signature, $key, $alg)
+    {
+        if (empty(static::$supported_algs[$alg])) {
+            throw new DomainException('Algorithm not supported');
+        }
+
+        list($function, $algorithm) = static::$supported_algs[$alg];
+        switch ($function) {
+            case 'openssl':
+                $success = openssl_verify($msg, $signature, $key, $algorithm);
+                if ($success === 1) {
+                    return true;
+                } elseif ($success === 0) {
+                    return false;
+                }
+                // returns 1 on success, 0 on failure, -1 on error.
+                throw new DomainException(
+                    'OpenSSL error: ' . openssl_error_string()
+                );
+            case 'hash_hmac':
+            default:
+                $hash = hash_hmac($algorithm, $msg, $key, true);
+                if (function_exists('hash_equals')) {
+                    return hash_equals($signature, $hash);
+                }
+                $len = min(static::safeStrlen($signature), static::safeStrlen($hash));
+
+                $status = 0;
+                for ($i = 0; $i < $len; $i++) {
+                    $status |= (ord($signature[$i]) ^ ord($hash[$i]));
+                }
+                $status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash));
+
+                return ($status === 0);
+        }
+    }
+
+    /**
+     * Decode a JSON string into a PHP object.
+     *
+     * @param string $input JSON string
+     *
+     * @return object Object representation of JSON string
+     *
+     * @throws DomainException Provided string was invalid JSON
+     */
+    public static function jsonDecode($input)
+    {
+        if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
+            /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
+             * to specify that large ints (like Steam Transaction IDs) should be treated as
+             * strings, rather than the PHP default behaviour of converting them to floats.
+             */
+            $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
+        } else {
+            /** Not all servers will support that, however, so for older versions we must
+             * manually detect large ints in the JSON string and quote them (thus converting
+             *them to strings) before decoding, hence the preg_replace() call.
+             */
+            $max_int_length = strlen((string) PHP_INT_MAX) - 1;
+            $json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
+            $obj = json_decode($json_without_bigints);
+        }
+
+        if ($errno = json_last_error()) {
+            static::handleJsonError($errno);
+        } elseif ($obj === null && $input !== 'null') {
+            throw new DomainException('Null result with non-null input');
+        }
+        return $obj;
+    }
+
+    /**
+     * Encode a PHP object into a JSON string.
+     *
+     * @param object|array $input A PHP object or array
+     *
+     * @return string JSON representation of the PHP object or array
+     *
+     * @throws DomainException Provided object could not be encoded to valid JSON
+     */
+    public static function jsonEncode($input)
+    {
+        $json = json_encode($input);
+        if ($errno = json_last_error()) {
+            static::handleJsonError($errno);
+        } elseif ($json === 'null' && $input !== null) {
+            throw new DomainException('Null result with non-null input');
+        }
+        return $json;
+    }
+
+    /**
+     * Decode a string with URL-safe Base64.
+     *
+     * @param string $input A Base64 encoded string
+     *
+     * @return string A decoded string
+     */
+    public static function urlsafeB64Decode($input)
+    {
+        $remainder = strlen($input) % 4;
+        if ($remainder) {
+            $padlen = 4 - $remainder;
+            $input .= str_repeat('=', $padlen);
+        }
+        return base64_decode(strtr($input, '-_', '+/'));
+    }
+
+    /**
+     * Encode a string with URL-safe Base64.
+     *
+     * @param string $input The string you want encoded
+     *
+     * @return string The base64 encode of what you passed in
+     */
+    public static function urlsafeB64Encode($input)
+    {
+        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
+    }
+
+    /**
+     * Helper method to create a JSON error.
+     *
+     * @param int $errno An error number from json_last_error()
+     *
+     * @return void
+     */
+    private static function handleJsonError($errno)
+    {
+        $messages = array(
+            JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
+            JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
+            JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
+            JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
+            JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
+        );
+        throw new DomainException(
+            isset($messages[$errno])
+            ? $messages[$errno]
+            : 'Unknown JSON error: ' . $errno
+        );
+    }
+
+    /**
+     * Get the number of bytes in cryptographic strings.
+     *
+     * @param string $str
+     *
+     * @return int
+     */
+    private static function safeStrlen($str)
+    {
+        if (function_exists('mb_strlen')) {
+            return mb_strlen($str, '8bit');
+        }
+        return strlen($str);
+    }
+
+    /**
+     * Convert an ECDSA signature to an ASN.1 DER sequence
+     *
+     * @param   string $sig The ECDSA signature to convert
+     * @return  string The encoded DER object
+     */
+    private static function signatureToDER($sig)
+    {
+        // Separate the signature into r-value and s-value
+        list($r, $s) = str_split($sig, (int) (strlen($sig) / 2));
+
+        // Trim leading zeros
+        $r = ltrim($r, "\x00");
+        $s = ltrim($s, "\x00");
+
+        // Convert r-value and s-value from unsigned big-endian integers to
+        // signed two's complement
+        if (ord($r[0]) > 0x7f) {
+            $r = "\x00" . $r;
+        }
+        if (ord($s[0]) > 0x7f) {
+            $s = "\x00" . $s;
+        }
+
+        return self::encodeDER(
+            self::ASN1_SEQUENCE,
+            self::encodeDER(self::ASN1_INTEGER, $r) .
+            self::encodeDER(self::ASN1_INTEGER, $s)
+        );
+    }
+
+    /**
+     * Encodes a value into a DER object.
+     *
+     * @param   int     $type DER tag
+     * @param   string  $value the value to encode
+     * @return  string  the encoded object
+     */
+    private static function encodeDER($type, $value)
+    {
+        $tag_header = 0;
+        if ($type === self::ASN1_SEQUENCE) {
+            $tag_header |= 0x20;
+        }
+
+        // Type
+        $der = chr($tag_header | $type);
+
+        // Length
+        $der .= chr(strlen($value));
+
+        return $der . $value;
+    }
+
+    /**
+     * Encodes signature from a DER object.
+     *
+     * @param   string  $der binary signature in DER format
+     * @param   int     $keySize the nubmer of bits in the key
+     * @return  string  the signature
+     */
+    private static function signatureFromDER($der, $keySize)
+    {
+        // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
+        list($offset, $_) = self::readDER($der);
+        list($offset, $r) = self::readDER($der, $offset);
+        list($offset, $s) = self::readDER($der, $offset);
+
+        // Convert r-value and s-value from signed two's compliment to unsigned
+        // big-endian integers
+        $r = ltrim($r, "\x00");
+        $s = ltrim($s, "\x00");
+
+        // Pad out r and s so that they are $keySize bits long
+        $r = str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT);
+        $s = str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT);
+
+        return $r . $s;
+    }
+
+    /**
+     * Reads binary DER-encoded data and decodes into a single object
+     *
+     * @param string $der the binary data in DER format
+     * @param int $offset the offset of the data stream containing the object
+     * to decode
+     * @return array [$offset, $data] the new offset and the decoded object
+     */
+    private static function readDER($der, $offset = 0)
+    {
+        $pos = $offset;
+        $size = strlen($der);
+        $constructed = (ord($der[$pos]) >> 5) & 0x01;
+        $type = ord($der[$pos++]) & 0x1f;
+
+        // Length
+        $len = ord($der[$pos++]);
+        if ($len & 0x80) {
+            $n = $len & 0x1f;
+            $len = 0;
+            while ($n-- && $pos < $size) {
+                $len = ($len << 8) | ord($der[$pos++]);
+            }
+        }
+
+        // Value
+        if ($type == self::ASN1_BIT_STRING) {
+            $pos++; // Skip the first contents octet (padding indicator)
+            $data = substr($der, $pos, $len - 1);
+            if (!$ignore_bit_strings) {
+                $pos += $len - 1;
+            }
+        } elseif (!$constructed) {
+            $data = substr($der, $pos, $len);
+            $pos += $len;
+        } else {
+            $data = null;
+        }
+
+        return array($pos, $data);
+    }
+}

+ 7 - 0
vendor/firebase/php-jwt/src/SignatureInvalidException.php

@@ -0,0 +1,7 @@
+<?php
+namespace Firebase\JWT;
+
+class SignatureInvalidException extends \UnexpectedValueException
+{
+
+}

+ 10 - 0
vendor/league/flysystem-cached-adapter/.editorconfig

@@ -0,0 +1,10 @@
+; top-most EditorConfig file
+root = true
+
+; Unix-style newlines
+[*]
+end_of_line = LF
+
+[*.php]
+indent_style = space
+indent_size = 4

+ 4 - 0
vendor/league/flysystem-cached-adapter/.gitignore

@@ -0,0 +1,4 @@
+coverage
+coverage.xml
+composer.lock
+vendor

+ 7 - 0
vendor/league/flysystem-cached-adapter/.php_cs

@@ -0,0 +1,7 @@
+<?php
+
+return Symfony\CS\Config\Config::create()
+    ->level(Symfony\CS\FixerInterface::PSR2_LEVEL)
+    ->fixers(['-yoda_conditions', 'ordered_use', 'short_array_syntax'])
+    ->finder(Symfony\CS\Finder\DefaultFinder::create()
+        ->in(__DIR__.'/src/'));

+ 34 - 0
vendor/league/flysystem-cached-adapter/.scrutinizer.yml

@@ -0,0 +1,34 @@
+filter:
+    paths: [src/*]
+checks:
+    php:
+        code_rating: true
+        remove_extra_empty_lines: true
+        remove_php_closing_tag: true
+        remove_trailing_whitespace: true
+        fix_use_statements:
+            remove_unused: true
+            preserve_multiple: false
+            preserve_blanklines: true
+            order_alphabetically: true
+        fix_php_opening_tag: true
+        fix_linefeed: true
+        fix_line_ending: true
+        fix_identation_4spaces: true
+        fix_doc_comments: true
+tools:
+    external_code_coverage:
+        timeout: 900
+        runs: 6
+    php_code_coverage: false
+    php_code_sniffer:
+        config:
+            standard: PSR2
+        filter:
+            paths: ['src']
+    php_loc:
+        enabled: true
+        excluded_dirs: [vendor, spec, stubs]
+    php_cpd:
+        enabled: true
+        excluded_dirs: [vendor, spec, stubs]

+ 29 - 0
vendor/league/flysystem-cached-adapter/.travis.yml

@@ -0,0 +1,29 @@
+language: php
+
+php:
+  - 5.5
+  - 5.6
+  - 7.0
+  - 7.1
+  - 7.2
+
+matrix:
+  allow_failures:
+  - php: 5.5
+
+env:
+  - COMPOSER_OPTS=""
+  - COMPOSER_OPTS="--prefer-lowest"
+
+install:
+  - if [[ "${TRAVIS_PHP_VERSION}" == "5.5" ]]; then composer require phpunit/phpunit:^4.8.36 phpspec/phpspec:^2 --prefer-dist --update-with-dependencies; fi
+  - if [[ "${TRAVIS_PHP_VERSION}" == "7.2" ]]; then composer require phpunit/phpunit:^6.0 --prefer-dist --update-with-dependencies; fi
+  - travis_retry composer update --prefer-dist $COMPOSER_OPTS
+
+script:
+  - vendor/bin/phpspec run
+  - vendor/bin/phpunit
+
+after_script:
+  - wget https://scrutinizer-ci.com/ocular.phar'
+  - php ocular.phar code-coverage:upload --format=php-clover ./clover/phpunit.xml'

+ 19 - 0
vendor/league/flysystem-cached-adapter/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2015 Frank de Jonge
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 2 - 0
vendor/league/flysystem-cached-adapter/clover/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 30 - 0
vendor/league/flysystem-cached-adapter/composer.json

@@ -0,0 +1,30 @@
+{
+    "name": "league/flysystem-cached-adapter",
+    "description": "An adapter decorator to enable meta-data caching.",
+    "autoload": {
+        "psr-4": {
+            "League\\Flysystem\\Cached\\": "src/"
+        }
+    },
+    "require": {
+        "league/flysystem": "~1.0",
+        "psr/cache": "^1.0.0"
+    },
+    "require-dev": {
+        "phpspec/phpspec": "^3.4",
+        "phpunit/phpunit": "^5.7",
+        "mockery/mockery": "~0.9",
+        "predis/predis": "~1.0",
+        "tedivm/stash": "~0.12"
+    },
+    "suggest": {
+      "ext-phpredis": "Pure C implemented extension for PHP"
+    },
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "frankdejonge",
+            "email": "info@frenky.net"
+        }
+    ]
+}

+ 6 - 0
vendor/league/flysystem-cached-adapter/phpspec.yml

@@ -0,0 +1,6 @@
+---
+suites:
+      cached_adapter_suite:
+        namespace: League\Flysystem\Cached
+        psr4_prefix: League\Flysystem\Cached
+formatter.name: pretty

+ 3 - 0
vendor/league/flysystem-cached-adapter/phpunit.php

@@ -0,0 +1,3 @@
+<?php
+
+include __DIR__.'/vendor/autoload.php';

+ 29 - 0
vendor/league/flysystem-cached-adapter/phpunit.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         bootstrap="./phpunit.php"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         syntaxCheck="true"
+         verbose="true"
+>
+    <testsuites>
+        <testsuite name="flysystem/tests">
+            <directory suffix=".php">./tests/</directory>
+        </testsuite>
+    </testsuites>
+    <filter>
+        <whitelist>
+            <directory suffix=".php">./src/</directory>
+        </whitelist>
+    </filter>
+    <logging>
+        <log type="coverage-text" target="php://stdout" showUncoveredFiles="true"/>
+        <log type="coverage-html" target="coverage" showUncoveredFiles="true"/>
+        <log type="coverage-clover" target="clover/phpunit.xml" showUncoveredFiles="true"/>
+    </logging>
+</phpunit>

+ 20 - 0
vendor/league/flysystem-cached-adapter/readme.md

@@ -0,0 +1,20 @@
+# Flysystem Cached CachedAdapter
+
+[![Author](http://img.shields.io/badge/author-@frankdejonge-blue.svg?style=flat-square)](https://twitter.com/frankdejonge)
+[![Build Status](https://img.shields.io/travis/thephpleague/flysystem-cached-adapter/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/flysystem-cached-adapter)
+[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/flysystem-cached-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-cached-adapter/code-structure)
+[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/flysystem-cached-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-cached-adapter)
+[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
+[![Packagist Version](https://img.shields.io/packagist/v/league/flysystem-cached-adapter.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-cached-adapter)
+[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem-cached-adapter.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-cached-adapter)
+
+
+The adapter decorator caches metadata and directory listings.
+
+```bash
+composer require league/flysystem-cached-adapter
+```
+
+## Usage
+
+[Check out the docs.](https://flysystem.thephpleague.com/docs/advanced/caching/)

+ 435 - 0
vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php

@@ -0,0 +1,435 @@
+<?php
+
+namespace spec\League\Flysystem\Cached;
+
+use League\Flysystem\AdapterInterface;
+use League\Flysystem\Cached\CacheInterface;
+use League\Flysystem\Config;
+use PhpSpec\ObjectBehavior;
+
+class CachedAdapterSpec extends ObjectBehavior
+{
+    /**
+     * @var AdapterInterface
+     */
+    private $adapter;
+
+    /**
+     * @var CacheInterface
+     */
+    private $cache;
+
+    public function let(AdapterInterface $adapter, CacheInterface $cache)
+    {
+        $this->adapter = $adapter;
+        $this->cache = $cache;
+        $this->cache->load()->shouldBeCalled();
+        $this->beConstructedWith($adapter, $cache);
+    }
+
+    public function it_is_initializable()
+    {
+        $this->shouldHaveType('League\Flysystem\Cached\CachedAdapter');
+        $this->shouldHaveType('League\Flysystem\AdapterInterface');
+    }
+
+    public function it_should_forward_read_streams()
+    {
+        $path = 'path.txt';
+        $response = ['path' => $path];
+        $this->adapter->readStream($path)->willReturn($response);
+        $this->readStream($path)->shouldbe($response);
+    }
+
+    public function it_should_cache_writes()
+    {
+        $type = 'file';
+        $path = 'path.txt';
+        $contents = 'contents';
+        $config = new Config();
+        $response = compact('path', 'contents', 'type');
+        $this->adapter->write($path, $contents, $config)->willReturn($response);
+        $this->cache->updateObject($path, $response, true)->shouldBeCalled();
+        $this->write($path, $contents, $config)->shouldBe($response);
+    }
+
+    public function it_should_cache_streamed_writes()
+    {
+        $type = 'file';
+        $path = 'path.txt';
+        $stream = tmpfile();
+        $config = new Config();
+        $response = compact('path', 'stream', 'type');
+        $this->adapter->writeStream($path, $stream, $config)->willReturn($response);
+        $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled();
+        $this->writeStream($path, $stream, $config)->shouldBe($response);
+        fclose($stream);
+    }
+
+    public function it_should_cache_streamed_updates()
+    {
+        $type = 'file';
+        $path = 'path.txt';
+        $stream = tmpfile();
+        $config = new Config();
+        $response = compact('path', 'stream', 'type');
+        $this->adapter->updateStream($path, $stream, $config)->willReturn($response);
+        $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled();
+        $this->updateStream($path, $stream, $config)->shouldBe($response);
+        fclose($stream);
+    }
+
+    public function it_should_ignore_failed_writes()
+    {
+        $path = 'path.txt';
+        $contents = 'contents';
+        $config = new Config();
+        $this->adapter->write($path, $contents, $config)->willReturn(false);
+        $this->write($path, $contents, $config)->shouldBe(false);
+    }
+
+    public function it_should_ignore_failed_streamed_writes()
+    {
+        $path = 'path.txt';
+        $contents = tmpfile();
+        $config = new Config();
+        $this->adapter->writeStream($path, $contents, $config)->willReturn(false);
+        $this->writeStream($path, $contents, $config)->shouldBe(false);
+        fclose($contents);
+    }
+
+    public function it_should_cache_updated()
+    {
+        $type = 'file';
+        $path = 'path.txt';
+        $contents = 'contents';
+        $config = new Config();
+        $response = compact('path', 'contents', 'type');
+        $this->adapter->update($path, $contents, $config)->willReturn($response);
+        $this->cache->updateObject($path, $response, true)->shouldBeCalled();
+        $this->update($path, $contents, $config)->shouldBe($response);
+    }
+
+    public function it_should_ignore_failed_updates()
+    {
+        $path = 'path.txt';
+        $contents = 'contents';
+        $config = new Config();
+        $this->adapter->update($path, $contents, $config)->willReturn(false);
+        $this->update($path, $contents, $config)->shouldBe(false);
+    }
+
+    public function it_should_ignore_failed_streamed_updates()
+    {
+        $path = 'path.txt';
+        $contents = tmpfile();
+        $config = new Config();
+        $this->adapter->updateStream($path, $contents, $config)->willReturn(false);
+        $this->updateStream($path, $contents, $config)->shouldBe(false);
+        fclose($contents);
+    }
+
+    public function it_should_cache_renames()
+    {
+        $old = 'old.txt';
+        $new = 'new.txt';
+        $this->adapter->rename($old, $new)->willReturn(true);
+        $this->cache->rename($old, $new)->shouldBeCalled();
+        $this->rename($old, $new)->shouldBe(true);
+    }
+
+    public function it_should_ignore_rename_fails()
+    {
+        $old = 'old.txt';
+        $new = 'new.txt';
+        $this->adapter->rename($old, $new)->willReturn(false);
+        $this->rename($old, $new)->shouldBe(false);
+    }
+
+    public function it_should_cache_copies()
+    {
+        $old = 'old.txt';
+        $new = 'new.txt';
+        $this->adapter->copy($old, $new)->willReturn(true);
+        $this->cache->copy($old, $new)->shouldBeCalled();
+        $this->copy($old, $new)->shouldBe(true);
+    }
+
+    public function it_should_ignore_copy_fails()
+    {
+        $old = 'old.txt';
+        $new = 'new.txt';
+        $this->adapter->copy($old, $new)->willReturn(false);
+        $this->copy($old, $new)->shouldBe(false);
+    }
+
+    public function it_should_cache_deletes()
+    {
+        $delete = 'delete.txt';
+        $this->adapter->delete($delete)->willReturn(true);
+        $this->cache->delete($delete)->shouldBeCalled();
+        $this->delete($delete)->shouldBe(true);
+    }
+
+    public function it_should_ignore_delete_fails()
+    {
+        $delete = 'delete.txt';
+        $this->adapter->delete($delete)->willReturn(false);
+        $this->delete($delete)->shouldBe(false);
+    }
+
+    public function it_should_cache_dir_deletes()
+    {
+        $delete = 'delete';
+        $this->adapter->deleteDir($delete)->willReturn(true);
+        $this->cache->deleteDir($delete)->shouldBeCalled();
+        $this->deleteDir($delete)->shouldBe(true);
+    }
+
+    public function it_should_ignore_delete_dir_fails()
+    {
+        $delete = 'delete';
+        $this->adapter->deleteDir($delete)->willReturn(false);
+        $this->deleteDir($delete)->shouldBe(false);
+    }
+
+    public function it_should_cache_dir_creates()
+    {
+        $dirname = 'dirname';
+        $config = new Config();
+        $response = ['path' => $dirname, 'type' => 'dir'];
+        $this->adapter->createDir($dirname, $config)->willReturn($response);
+        $this->cache->updateObject($dirname, $response, true)->shouldBeCalled();
+        $this->createDir($dirname, $config)->shouldBe($response);
+    }
+
+    public function it_should_ignore_create_dir_fails()
+    {
+        $dirname = 'dirname';
+        $config = new Config();
+        $this->adapter->createDir($dirname, $config)->willReturn(false);
+        $this->createDir($dirname, $config)->shouldBe(false);
+    }
+
+    public function it_should_cache_set_visibility()
+    {
+        $path = 'path.txt';
+        $visibility = AdapterInterface::VISIBILITY_PUBLIC;
+        $this->adapter->setVisibility($path, $visibility)->willReturn(true);
+        $this->cache->updateObject($path, ['path' => $path, 'visibility' => $visibility], true)->shouldBeCalled();
+        $this->setVisibility($path, $visibility)->shouldBe(true);
+    }
+
+    public function it_should_ignore_set_visibility_fails()
+    {
+        $dirname = 'delete';
+        $visibility = AdapterInterface::VISIBILITY_PUBLIC;
+        $this->adapter->setVisibility($dirname, $visibility)->willReturn(false);
+        $this->setVisibility($dirname, $visibility)->shouldBe(false);
+    }
+
+    public function it_should_indicate_missing_files()
+    {
+        $this->cache->has($path = 'path.txt')->willReturn(false);
+        $this->has($path)->shouldBe(false);
+    }
+
+    public function it_should_indicate_file_existance()
+    {
+        $this->cache->has($path = 'path.txt')->willReturn(true);
+        $this->has($path)->shouldBe(true);
+    }
+
+    public function it_should_cache_missing_files()
+    {
+        $this->cache->has($path = 'path.txt')->willReturn(null);
+        $this->adapter->has($path)->willReturn(false);
+        $this->cache->storeMiss($path)->shouldBeCalled();
+        $this->has($path)->shouldBe(false);
+    }
+
+    public function it_should_delete_when_metadata_is_missing()
+    {
+        $path = 'path.txt';
+        $this->cache->has($path)->willReturn(true);
+        $this->cache->getSize($path)->willReturn(['path' => $path]);
+        $this->adapter->getSize($path)->willReturn($response = ['path' => $path, 'size' => 1024]);
+        $this->cache->updateObject($path, $response, true)->shouldBeCalled();
+        $this->getSize($path)->shouldBe($response);
+    }
+
+    public function it_should_cache_has()
+    {
+        $this->cache->has($path = 'path.txt')->willReturn(null);
+        $this->adapter->has($path)->willReturn(true);
+        $this->cache->updateObject($path, compact('path'), true)->shouldBeCalled();
+        $this->has($path)->shouldBe(true);
+    }
+
+    public function it_should_list_cached_contents()
+    {
+        $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(true);
+        $response = [['path' => 'path.txt']];
+        $this->cache->listContents($dirname, $recursive)->willReturn($response);
+        $this->listContents($dirname, $recursive)->shouldBe($response);
+    }
+
+    public function it_should_ignore_failed_list_contents()
+    {
+        $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false);
+        $this->adapter->listContents($dirname, $recursive)->willReturn(false);
+        $this->listContents($dirname, $recursive)->shouldBe(false);
+    }
+
+    public function it_should_cache_contents_listings()
+    {
+        $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false);
+        $response = [['path' => 'path.txt']];
+        $this->adapter->listContents($dirname, $recursive)->willReturn($response);
+        $this->cache->storeContents($dirname, $response, $recursive)->shouldBeCalled();
+        $this->listContents($dirname, $recursive)->shouldBe($response);
+    }
+
+    public function it_should_use_cached_visibility()
+    {
+        $this->make_it_use_getter_cache('getVisibility', 'path.txt', [
+            'path' => 'path.txt',
+            'visibility' => AdapterInterface::VISIBILITY_PUBLIC,
+        ]);
+    }
+
+    public function it_should_cache_get_visibility()
+    {
+        $path = 'path.txt';
+        $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path];
+        $this->make_it_cache_getter('getVisibility', $path, $response);
+    }
+
+    public function it_should_ignore_failed_get_visibility()
+    {
+        $path = 'path.txt';
+        $this->make_it_ignore_failed_getter('getVisibility', $path);
+    }
+
+    public function it_should_use_cached_timestamp()
+    {
+        $this->make_it_use_getter_cache('getTimestamp', 'path.txt', [
+            'path' => 'path.txt',
+            'timestamp' => 1234,
+        ]);
+    }
+
+    public function it_should_cache_timestamps()
+    {
+        $this->make_it_cache_getter('getTimestamp', 'path.txt', [
+            'path' => 'path.txt',
+            'timestamp' => 1234,
+        ]);
+    }
+
+    public function it_should_ignore_failed_get_timestamps()
+    {
+        $this->make_it_ignore_failed_getter('getTimestamp', 'path.txt');
+    }
+
+    public function it_should_cache_get_metadata()
+    {
+        $path = 'path.txt';
+        $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path];
+        $this->make_it_cache_getter('getMetadata', $path, $response);
+    }
+
+    public function it_should_use_cached_metadata()
+    {
+        $this->make_it_use_getter_cache('getMetadata', 'path.txt', [
+            'path' => 'path.txt',
+            'timestamp' => 1234,
+        ]);
+    }
+
+    public function it_should_ignore_failed_get_metadata()
+    {
+        $this->make_it_ignore_failed_getter('getMetadata', 'path.txt');
+    }
+
+    public function it_should_cache_get_size()
+    {
+        $path = 'path.txt';
+        $response = ['size' => 1234, 'path' => $path];
+        $this->make_it_cache_getter('getSize', $path, $response);
+    }
+
+    public function it_should_use_cached_size()
+    {
+        $this->make_it_use_getter_cache('getSize', 'path.txt', [
+            'path' => 'path.txt',
+            'size' => 1234,
+        ]);
+    }
+
+    public function it_should_ignore_failed_get_size()
+    {
+        $this->make_it_ignore_failed_getter('getSize', 'path.txt');
+    }
+
+    public function it_should_cache_get_mimetype()
+    {
+        $path = 'path.txt';
+        $response = ['mimetype' => 'text/plain', 'path' => $path];
+        $this->make_it_cache_getter('getMimetype', $path, $response);
+    }
+
+    public function it_should_use_cached_mimetype()
+    {
+        $this->make_it_use_getter_cache('getMimetype', 'path.txt', [
+            'path' => 'path.txt',
+            'mimetype' => 'text/plain',
+        ]);
+    }
+
+    public function it_should_ignore_failed_get_mimetype()
+    {
+        $this->make_it_ignore_failed_getter('getMimetype', 'path.txt');
+    }
+
+    public function it_should_cache_reads()
+    {
+        $path = 'path.txt';
+        $response = ['path' => $path, 'contents' => 'contents'];
+        $this->make_it_cache_getter('read', $path, $response);
+    }
+
+    public function it_should_use_cached_file_contents()
+    {
+        $this->make_it_use_getter_cache('read', 'path.txt', [
+            'path' => 'path.txt',
+            'contents' => 'contents'
+        ]);
+    }
+
+    public function it_should_ignore_failed_reads()
+    {
+        $this->make_it_ignore_failed_getter('read', 'path.txt');
+    }
+
+    protected function make_it_use_getter_cache($method, $path, $response)
+    {
+        $this->cache->{$method}($path)->willReturn($response);
+        $this->{$method}($path)->shouldBe($response);
+    }
+
+    protected function make_it_cache_getter($method, $path, $response)
+    {
+        $this->cache->{$method}($path)->willReturn(false);
+        $this->adapter->{$method}($path)->willReturn($response);
+        $this->cache->updateObject($path, $response, true)->shouldBeCalled();
+        $this->{$method}($path)->shouldBe($response);
+    }
+
+    protected function make_it_ignore_failed_getter($method, $path)
+    {
+        $this->cache->{$method}($path)->willReturn(false);
+        $this->adapter->{$method}($path)->willReturn(false);
+        $this->{$method}($path)->shouldBe(false);
+    }
+}

+ 101 - 0
vendor/league/flysystem-cached-adapter/src/CacheInterface.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace League\Flysystem\Cached;
+
+use League\Flysystem\ReadInterface;
+
+interface CacheInterface extends ReadInterface
+{
+    /**
+     * Check whether the directory listing of a given directory is complete.
+     *
+     * @param string $dirname
+     * @param bool   $recursive
+     *
+     * @return bool
+     */
+    public function isComplete($dirname, $recursive);
+
+    /**
+     * Set a directory to completely listed.
+     *
+     * @param string $dirname
+     * @param bool   $recursive
+     */
+    public function setComplete($dirname, $recursive);
+
+    /**
+     * Store the contents of a directory.
+     *
+     * @param string $directory
+     * @param array  $contents
+     * @param bool   $recursive
+     */
+    public function storeContents($directory, array $contents, $recursive);
+
+    /**
+     * Flush the cache.
+     */
+    public function flush();
+
+    /**
+     * Autosave trigger.
+     */
+    public function autosave();
+
+    /**
+     * Store the cache.
+     */
+    public function save();
+
+    /**
+     * Load the cache.
+     */
+    public function load();
+
+    /**
+     * Rename a file.
+     *
+     * @param string $path
+     * @param string $newpath
+     */
+    public function rename($path, $newpath);
+
+    /**
+     * Copy a file.
+     *
+     * @param string $path
+     * @param string $newpath
+     */
+    public function copy($path, $newpath);
+
+    /**
+     * Delete an object from cache.
+     *
+     * @param string $path object path
+     */
+    public function delete($path);
+
+    /**
+     * Delete all objects from from a directory.
+     *
+     * @param string $dirname directory path
+     */
+    public function deleteDir($dirname);
+
+    /**
+     * Update the metadata for an object.
+     *
+     * @param string $path     object path
+     * @param array  $object   object metadata
+     * @param bool   $autosave whether to trigger the autosave routine
+     */
+    public function updateObject($path, array $object, $autosave = false);
+
+    /**
+     * Store object hit miss.
+     *
+     * @param string $path
+     */
+    public function storeMiss($path);
+}

+ 324 - 0
vendor/league/flysystem-cached-adapter/src/CachedAdapter.php

@@ -0,0 +1,324 @@
+<?php
+
+namespace League\Flysystem\Cached;
+
+use League\Flysystem\AdapterInterface;
+use League\Flysystem\Config;
+
+class CachedAdapter implements AdapterInterface
+{
+    /**
+     * @var AdapterInterface
+     */
+    private $adapter;
+
+    /**
+     * @var CacheInterface
+     */
+    private $cache;
+
+    /**
+     * Constructor.
+     *
+     * @param AdapterInterface $adapter
+     * @param CacheInterface   $cache
+     */
+    public function __construct(AdapterInterface $adapter, CacheInterface $cache)
+    {
+        $this->adapter = $adapter;
+        $this->cache = $cache;
+        $this->cache->load();
+    }
+
+    /**
+     * Get the underlying Adapter implementation.
+     *
+     * @return AdapterInterface
+     */
+    public function getAdapter()
+    {
+        return $this->adapter;
+    }
+
+    /**
+     * Get the used Cache implementation.
+     *
+     * @return CacheInterface
+     */
+    public function getCache()
+    {
+        return $this->cache;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function write($path, $contents, Config $config)
+    {
+        $result = $this->adapter->write($path, $contents, $config);
+
+        if ($result !== false) {
+            $result['type'] = 'file';
+            $this->cache->updateObject($path, $result + compact('path', 'contents'), true);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function writeStream($path, $resource, Config $config)
+    {
+        $result = $this->adapter->writeStream($path, $resource, $config);
+
+        if ($result !== false) {
+            $result['type'] = 'file';
+            $contents = false;
+            $this->cache->updateObject($path, $result + compact('path', 'contents'), true);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update($path, $contents, Config $config)
+    {
+        $result = $this->adapter->update($path, $contents, $config);
+
+        if ($result !== false) {
+            $result['type'] = 'file';
+            $this->cache->updateObject($path, $result + compact('path', 'contents'), true);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateStream($path, $resource, Config $config)
+    {
+        $result = $this->adapter->updateStream($path, $resource, $config);
+
+        if ($result !== false) {
+            $result['type'] = 'file';
+            $contents = false;
+            $this->cache->updateObject($path, $result + compact('path', 'contents'), true);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rename($path, $newPath)
+    {
+        $result = $this->adapter->rename($path, $newPath);
+
+        if ($result !== false) {
+            $this->cache->rename($path, $newPath);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function copy($path, $newpath)
+    {
+        $result = $this->adapter->copy($path, $newpath);
+
+        if ($result !== false) {
+            $this->cache->copy($path, $newpath);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete($path)
+    {
+        $result = $this->adapter->delete($path);
+
+        if ($result !== false) {
+            $this->cache->delete($path);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteDir($dirname)
+    {
+        $result = $this->adapter->deleteDir($dirname);
+
+        if ($result !== false) {
+            $this->cache->deleteDir($dirname);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function createDir($dirname, Config $config)
+    {
+        $result = $this->adapter->createDir($dirname, $config);
+
+        if ($result !== false) {
+            $type = 'dir';
+            $path = $dirname;
+            $this->cache->updateObject($dirname, compact('path', 'type'), true);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setVisibility($path, $visibility)
+    {
+        $result = $this->adapter->setVisibility($path, $visibility);
+
+        if ($result !== false) {
+            $this->cache->updateObject($path, compact('path', 'visibility'), true);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($path)
+    {
+        $cacheHas = $this->cache->has($path);
+
+        if ($cacheHas !== null) {
+            return $cacheHas;
+        }
+
+        $adapterResponse = $this->adapter->has($path);
+
+        if (! $adapterResponse) {
+            $this->cache->storeMiss($path);
+        } else {
+            $cacheEntry = is_array($adapterResponse) ? $adapterResponse : compact('path');
+            $this->cache->updateObject($path, $cacheEntry, true);
+        }
+
+        return $adapterResponse;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($path)
+    {
+        return $this->callWithFallback('contents', $path, 'read');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function readStream($path)
+    {
+        return $this->adapter->readStream($path);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function listContents($directory = '', $recursive = false)
+    {
+        if ($this->cache->isComplete($directory, $recursive)) {
+            return $this->cache->listContents($directory, $recursive);
+        }
+
+        $result = $this->adapter->listContents($directory, $recursive);
+
+        if ($result !== false) {
+            $this->cache->storeContents($directory, $result, $recursive);
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMetadata($path)
+    {
+        return $this->callWithFallback(null, $path, 'getMetadata');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSize($path)
+    {
+        return $this->callWithFallback('size', $path, 'getSize');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMimetype($path)
+    {
+        return $this->callWithFallback('mimetype', $path, 'getMimetype');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getTimestamp($path)
+    {
+        return $this->callWithFallback('timestamp', $path, 'getTimestamp');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getVisibility($path)
+    {
+        return $this->callWithFallback('visibility', $path, 'getVisibility');
+    }
+
+    /**
+     * Call a method and cache the response.
+     *
+     * @param string $property
+     * @param string $path
+     * @param string $method
+     *
+     * @return mixed
+     */
+    protected function callWithFallback($property, $path, $method)
+    {
+        $result = $this->cache->{$method}($path);
+
+        if ($result !== false && ($property === null || array_key_exists($property, $result))) {
+            return $result;
+        }
+
+        $result = $this->adapter->{$method}($path);
+
+        if ($result) {
+            $object = $result + compact('path');
+            $this->cache->updateObject($path, $object, true);
+        }
+
+        return $result;
+    }
+}

+ 417 - 0
vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php

@@ -0,0 +1,417 @@
+<?php
+
+namespace League\Flysystem\Cached\Storage;
+
+use League\Flysystem\Cached\CacheInterface;
+use League\Flysystem\Util;
+
+abstract class AbstractCache implements CacheInterface
+{
+    /**
+     * @var bool
+     */
+    protected $autosave = true;
+
+    /**
+     * @var array
+     */
+    protected $cache = [];
+
+    /**
+     * @var array
+     */
+    protected $complete = [];
+
+    /**
+     * Destructor.
+     */
+    public function __destruct()
+    {
+        if (! $this->autosave) {
+            $this->save();
+        }
+    }
+
+    /**
+     * Get the autosave setting.
+     *
+     * @return bool autosave
+     */
+    public function getAutosave()
+    {
+        return $this->autosave;
+    }
+
+    /**
+     * Get the autosave setting.
+     *
+     * @param bool $autosave
+     */
+    public function setAutosave($autosave)
+    {
+        $this->autosave = $autosave;
+    }
+
+    /**
+     * Store the contents listing.
+     *
+     * @param string $directory
+     * @param array  $contents
+     * @param bool   $recursive
+     *
+     * @return array contents listing
+     */
+    public function storeContents($directory, array $contents, $recursive = false)
+    {
+        $directories = [$directory];
+
+        foreach ($contents as $object) {
+            $this->updateObject($object['path'], $object);
+            $object = $this->cache[$object['path']];
+
+            if ($recursive && $this->pathIsInDirectory($directory, $object['path'])) {
+                $directories[] = $object['dirname'];
+            }
+        }
+
+        foreach (array_unique($directories) as $directory) {
+            $this->setComplete($directory, $recursive);
+        }
+
+        $this->autosave();
+    }
+
+    /**
+     * Update the metadata for an object.
+     *
+     * @param string $path     object path
+     * @param array  $object   object metadata
+     * @param bool   $autosave whether to trigger the autosave routine
+     */
+    public function updateObject($path, array $object, $autosave = false)
+    {
+        if (! $this->has($path)) {
+            $this->cache[$path] = Util::pathinfo($path);
+        }
+
+        $this->cache[$path] = array_merge($this->cache[$path], $object);
+
+        if ($autosave) {
+            $this->autosave();
+        }
+
+        $this->ensureParentDirectories($path);
+    }
+
+    /**
+     * Store object hit miss.
+     *
+     * @param string $path
+     */
+    public function storeMiss($path)
+    {
+        $this->cache[$path] = false;
+        $this->autosave();
+    }
+
+    /**
+     * Get the contents listing.
+     *
+     * @param string $dirname
+     * @param bool   $recursive
+     *
+     * @return array contents listing
+     */
+    public function listContents($dirname = '', $recursive = false)
+    {
+        $result = [];
+
+        foreach ($this->cache as $object) {
+            if ($object === false) {
+                continue;
+            }
+            if ($object['dirname'] === $dirname) {
+                $result[] = $object;
+            } elseif ($recursive && $this->pathIsInDirectory($dirname, $object['path'])) {
+                $result[] = $object;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($path)
+    {
+        if ($path !== false && array_key_exists($path, $this->cache)) {
+            return $this->cache[$path] !== false;
+        }
+
+        if ($this->isComplete(Util::dirname($path), false)) {
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($path)
+    {
+        if (isset($this->cache[$path]['contents']) && $this->cache[$path]['contents'] !== false) {
+            return $this->cache[$path];
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function readStream($path)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rename($path, $newpath)
+    {
+        if ($this->has($path)) {
+            $object = $this->cache[$path];
+            unset($this->cache[$path]);
+            $object['path'] = $newpath;
+            $object = array_merge($object, Util::pathinfo($newpath));
+            $this->cache[$newpath] = $object;
+            $this->autosave();
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function copy($path, $newpath)
+    {
+        if ($this->has($path)) {
+            $object = $this->cache[$path];
+            $object = array_merge($object, Util::pathinfo($newpath));
+            $this->updateObject($newpath, $object, true);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete($path)
+    {
+        $this->storeMiss($path);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function deleteDir($dirname)
+    {
+        foreach ($this->cache as $path => $object) {
+            if ($this->pathIsInDirectory($dirname, $path) || $path === $dirname) {
+                unset($this->cache[$path]);
+            }
+        }
+
+        unset($this->complete[$dirname]);
+
+        $this->autosave();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMimetype($path)
+    {
+        if (isset($this->cache[$path]['mimetype'])) {
+            return $this->cache[$path];
+        }
+
+        if (! $result = $this->read($path)) {
+            return false;
+        }
+
+        $mimetype = Util::guessMimeType($path, $result['contents']);
+        $this->cache[$path]['mimetype'] = $mimetype;
+
+        return $this->cache[$path];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSize($path)
+    {
+        if (isset($this->cache[$path]['size'])) {
+            return $this->cache[$path];
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getTimestamp($path)
+    {
+        if (isset($this->cache[$path]['timestamp'])) {
+            return $this->cache[$path];
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getVisibility($path)
+    {
+        if (isset($this->cache[$path]['visibility'])) {
+            return $this->cache[$path];
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMetadata($path)
+    {
+        if (isset($this->cache[$path]['type'])) {
+            return $this->cache[$path];
+        }
+
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isComplete($dirname, $recursive)
+    {
+        if (! array_key_exists($dirname, $this->complete)) {
+            return false;
+        }
+
+        if ($recursive && $this->complete[$dirname] !== 'recursive') {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setComplete($dirname, $recursive)
+    {
+        $this->complete[$dirname] = $recursive ? 'recursive' : true;
+    }
+
+    /**
+     * Filter the contents from a listing.
+     *
+     * @param array $contents object listing
+     *
+     * @return array filtered contents
+     */
+    public function cleanContents(array $contents)
+    {
+        $cachedProperties = array_flip([
+            'path', 'dirname', 'basename', 'extension', 'filename',
+            'size', 'mimetype', 'visibility', 'timestamp', 'type',
+        ]);
+
+        foreach ($contents as $path => $object) {
+            if (is_array($object)) {
+                $contents[$path] = array_intersect_key($object, $cachedProperties);
+            }
+        }
+
+        return $contents;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function flush()
+    {
+        $this->cache = [];
+        $this->complete = [];
+        $this->autosave();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function autosave()
+    {
+        if ($this->autosave) {
+            $this->save();
+        }
+    }
+
+    /**
+     * Retrieve serialized cache data.
+     *
+     * @return string serialized data
+     */
+    public function getForStorage()
+    {
+        $cleaned = $this->cleanContents($this->cache);
+
+        return json_encode([$cleaned, $this->complete]);
+    }
+
+    /**
+     * Load from serialized cache data.
+     *
+     * @param string $json
+     */
+    public function setFromStorage($json)
+    {
+        list($cache, $complete) = json_decode($json, true);
+
+        if (json_last_error() === JSON_ERROR_NONE && is_array($cache) && is_array($complete)) {
+            $this->cache = $cache;
+            $this->complete = $complete;
+        }
+    }
+
+    /**
+     * Ensure parent directories of an object.
+     *
+     * @param string $path object path
+     */
+    public function ensureParentDirectories($path)
+    {
+        $object = $this->cache[$path];
+
+        while ($object['dirname'] !== '' && ! isset($this->cache[$object['dirname']])) {
+            $object = Util::pathinfo($object['dirname']);
+            $object['type'] = 'dir';
+            $this->cache[$object['path']] = $object;
+        }
+    }
+
+    /**
+     * Determines if the path is inside the directory.
+     *
+     * @param string $directory
+     * @param string $path
+     *
+     * @return bool
+     */
+    protected function pathIsInDirectory($directory, $path)
+    {
+        return $directory === '' || strpos($path, $directory . '/') === 0;
+    }
+}

+ 115 - 0
vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php

@@ -0,0 +1,115 @@
+<?php
+
+namespace League\Flysystem\Cached\Storage;
+
+use League\Flysystem\AdapterInterface;
+use League\Flysystem\Config;
+
+class Adapter extends AbstractCache
+{
+    /**
+     * @var AdapterInterface An adapter
+     */
+    protected $adapter;
+
+    /**
+     * @var string the file to cache to
+     */
+    protected $file;
+
+    /**
+     * @var int|null seconds until cache expiration
+     */
+    protected $expire = null;
+
+    /**
+     * Constructor.
+     *
+     * @param AdapterInterface $adapter adapter
+     * @param string           $file    the file to cache to
+     * @param int|null         $expire  seconds until cache expiration
+     */
+    public function __construct(AdapterInterface $adapter, $file, $expire = null)
+    {
+        $this->adapter = $adapter;
+        $this->file = $file;
+        $this->setExpire($expire);
+    }
+
+    /**
+     * Set the expiration time in seconds.
+     *
+     * @param int $expire relative expiration time
+     */
+    protected function setExpire($expire)
+    {
+        if ($expire) {
+            $this->expire = $this->getTime($expire);
+        }
+    }
+
+    /**
+     * Get expiration time in seconds.
+     *
+     * @param int $time relative expiration time
+     *
+     * @return int actual expiration time
+     */
+    protected function getTime($time = 0)
+    {
+        return intval(microtime(true)) + $time;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setFromStorage($json)
+    {
+        list($cache, $complete, $expire) = json_decode($json, true);
+
+        if (! $expire || $expire > $this->getTime()) {
+            $this->cache = $cache;
+            $this->complete = $complete;
+        } else {
+            $this->adapter->delete($this->file);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function load()
+    {
+        if ($this->adapter->has($this->file)) {
+            $file = $this->adapter->read($this->file);
+            if ($file && !empty($file['contents'])) {
+                $this->setFromStorage($file['contents']);
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getForStorage()
+    {
+        $cleaned = $this->cleanContents($this->cache);
+
+        return json_encode([$cleaned, $this->complete, $this->expire]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        $config = new Config();
+        $contents = $this->getForStorage();
+
+        if ($this->adapter->has($this->file)) {
+            $this->adapter->update($this->file, $contents, $config);
+        } else {
+            $this->adapter->write($this->file, $contents, $config);
+        }
+    }
+}

+ 59 - 0
vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace League\Flysystem\Cached\Storage;
+
+use Memcached as NativeMemcached;
+
+class Memcached extends AbstractCache
+{
+    /**
+     * @var string storage key
+     */
+    protected $key;
+
+    /**
+     * @var int|null seconds until cache expiration
+     */
+    protected $expire;
+
+    /**
+     * @var \Memcached Memcached instance
+     */
+    protected $memcached;
+
+    /**
+     * Constructor.
+     *
+     * @param \Memcached $memcached
+     * @param string     $key       storage key
+     * @param int|null   $expire    seconds until cache expiration
+     */
+    public function __construct(NativeMemcached $memcached, $key = 'flysystem', $expire = null)
+    {
+        $this->key = $key;
+        $this->expire = $expire;
+        $this->memcached = $memcached;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function load()
+    {
+        $contents = $this->memcached->get($this->key);
+
+        if ($contents !== false) {
+            $this->setFromStorage($contents);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        $contents = $this->getForStorage();
+        $expiration = $this->expire === null ? 0 : time() + $this->expire;
+        $this->memcached->set($this->key, $contents, $expiration);
+    }
+}

+ 22 - 0
vendor/league/flysystem-cached-adapter/src/Storage/Memory.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace League\Flysystem\Cached\Storage;
+
+class Memory extends AbstractCache
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        // There is nothing to save
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function load()
+    {
+        // There is nothing to load
+    }
+}

+ 171 - 0
vendor/league/flysystem-cached-adapter/src/Storage/Noop.php

@@ -0,0 +1,171 @@
+<?php
+
+namespace League\Flysystem\Cached\Storage;
+
+class Noop extends AbstractCache
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected $autosave = false;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateObject($path, array $object, $autosave = false)
+    {
+        return $object;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function isComplete($dirname, $recursive)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setComplete($dirname, $recursive)
+    {
+        //
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function copy($path, $newpath)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rename($path, $newpath)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function storeContents($directory, array $contents, $recursive = false)
+    {
+        return $contents;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function storeMiss($path)
+    {
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function flush()
+    {
+        //
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function autosave()
+    {
+        //
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        //
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function load()
+    {
+        //
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function has($path)
+    {
+        return;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function read($path)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function readStream($path)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function listContents($directory = '', $recursive = false)
+    {
+        return [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMetadata($path)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getSize($path)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getMimetype($path)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getTimestamp($path)
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getVisibility($path)
+    {
+        return false;
+    }
+}

+ 62 - 0
vendor/league/flysystem-cached-adapter/src/Storage/PhpRedis.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace League\Flysystem\Cached\Storage;
+
+use Redis;
+
+class PhpRedis extends AbstractCache
+{
+    /**
+     * @var Redis PhpRedis Client
+     */
+    protected $client;
+
+    /**
+     * @var string storage key
+     */
+    protected $key;
+
+    /**
+     * @var int|null seconds until cache expiration
+     */
+    protected $expire;
+
+    /**
+     * Constructor.
+     *
+     * @param Redis|null $client phpredis client
+     * @param string     $key    storage key
+     * @param int|null   $expire seconds until cache expiration
+     */
+    public function __construct(Redis $client = null, $key = 'flysystem', $expire = null)
+    {
+        $this->client = $client ?: new Redis();
+        $this->key = $key;
+        $this->expire = $expire;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function load()
+    {
+        $contents = $this->client->get($this->key);
+
+        if ($contents !== false) {
+            $this->setFromStorage($contents);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        $contents = $this->getForStorage();
+        $this->client->set($this->key, $contents);
+
+        if ($this->expire !== null) {
+            $this->client->expire($this->key, $this->expire);
+        }
+    }
+}

+ 75 - 0
vendor/league/flysystem-cached-adapter/src/Storage/Predis.php

@@ -0,0 +1,75 @@
+<?php
+
+namespace League\Flysystem\Cached\Storage;
+
+use Predis\Client;
+
+class Predis extends AbstractCache
+{
+    /**
+     * @var \Predis\Client Predis Client
+     */
+    protected $client;
+
+    /**
+     * @var string storage key
+     */
+    protected $key;
+
+    /**
+     * @var int|null seconds until cache expiration
+     */
+    protected $expire;
+
+    /**
+     * Constructor.
+     *
+     * @param \Predis\Client $client predis client
+     * @param string         $key    storage key
+     * @param int|null       $expire seconds until cache expiration
+     */
+    public function __construct(Client $client = null, $key = 'flysystem', $expire = null)
+    {
+        $this->client = $client ?: new Client();
+        $this->key = $key;
+        $this->expire = $expire;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function load()
+    {
+        if (($contents = $this->executeCommand('get', [$this->key])) !== null) {
+            $this->setFromStorage($contents);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        $contents = $this->getForStorage();
+        $this->executeCommand('set', [$this->key, $contents]);
+
+        if ($this->expire !== null) {
+            $this->executeCommand('expire', [$this->key, $this->expire]);
+        }
+    }
+
+    /**
+     * Execute a Predis command.
+     *
+     * @param string $name
+     * @param array  $arguments
+     *
+     * @return string
+     */
+    protected function executeCommand($name, array $arguments)
+    {
+        $command = $this->client->createCommand($name, $arguments);
+
+        return $this->client->executeCommand($command);
+    }
+}

+ 59 - 0
vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace League\Flysystem\Cached\Storage;
+
+use Psr\Cache\CacheItemPoolInterface;
+
+class Psr6Cache extends AbstractCache
+{
+    /**
+     * @var CacheItemPoolInterface
+     */
+    private $pool;
+
+    /**
+     * @var string storage key
+     */
+    protected $key;
+
+    /**
+     * @var int|null seconds until cache expiration
+     */
+    protected $expire;
+
+    /**
+     * Constructor.
+     *
+     * @param CacheItemPoolInterface $pool
+     * @param string                 $key    storage key
+     * @param int|null               $expire seconds until cache expiration
+     */
+    public function __construct(CacheItemPoolInterface $pool, $key = 'flysystem', $expire = null)
+    {
+        $this->pool = $pool;
+        $this->key = $key;
+        $this->expire = $expire;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        $item = $this->pool->getItem($this->key);
+        $item->set($this->getForStorage());
+        $item->expiresAfter($this->expire);
+        $this->pool->save($item);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function load()
+    {
+        $item = $this->pool->getItem($this->key);
+        if ($item->isHit()) {
+            $this->setFromStorage($item->get());
+        }
+    }
+}

+ 60 - 0
vendor/league/flysystem-cached-adapter/src/Storage/Stash.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace League\Flysystem\Cached\Storage;
+
+use Stash\Pool;
+
+class Stash extends AbstractCache
+{
+    /**
+     * @var string storage key
+     */
+    protected $key;
+
+    /**
+     * @var int|null seconds until cache expiration
+     */
+    protected $expire;
+
+    /**
+     * @var \Stash\Pool Stash pool instance
+     */
+    protected $pool;
+
+    /**
+     * Constructor.
+     *
+     * @param \Stash\Pool $pool
+     * @param string      $key    storage key
+     * @param int|null    $expire seconds until cache expiration
+     */
+    public function __construct(Pool $pool, $key = 'flysystem', $expire = null)
+    {
+        $this->key = $key;
+        $this->expire = $expire;
+        $this->pool = $pool;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function load()
+    {
+        $item = $this->pool->getItem($this->key);
+        $contents = $item->get();
+
+        if ($item->isMiss() === false) {
+            $this->setFromStorage($contents);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function save()
+    {
+        $contents = $this->getForStorage();
+        $item = $this->pool->getItem($this->key);
+        $item->set($contents, $this->expire);
+    }
+}

+ 104 - 0
vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php

@@ -0,0 +1,104 @@
+<?php
+
+use League\Flysystem\Cached\Storage\Adapter;
+use PHPUnit\Framework\TestCase;
+
+class AdapterCacheTests extends TestCase
+{
+    public function testLoadFail()
+    {
+        $adapter = Mockery::mock('League\Flysystem\AdapterInterface');
+        $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(false);
+        $cache = new Adapter($adapter, 'file.json', 10);
+        $cache->load();
+        $this->assertFalse($cache->isComplete('', false));
+    }
+
+    public function testLoadExpired()
+    {
+        $response = ['contents' => json_encode([[], ['' => true], 1234567890]), 'path' => 'file.json'];
+        $adapter = Mockery::mock('League\Flysystem\AdapterInterface');
+        $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true);
+        $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response);
+        $adapter->shouldReceive('delete')->once()->with('file.json');
+        $cache = new Adapter($adapter, 'file.json', 10);
+        $cache->load();
+        $this->assertFalse($cache->isComplete('', false));
+    }
+
+    public function testLoadSuccess()
+    {
+        $response = ['contents' => json_encode([[], ['' => true], 9876543210]), 'path' => 'file.json'];
+        $adapter = Mockery::mock('League\Flysystem\AdapterInterface');
+        $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true);
+        $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response);
+        $cache = new Adapter($adapter, 'file.json', 10);
+        $cache->load();
+        $this->assertTrue($cache->isComplete('', false));
+    }
+
+    public function testSaveExists()
+    {
+        $response = json_encode([[], [], null]);
+        $adapter = Mockery::mock('League\Flysystem\AdapterInterface');
+        $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true);
+        $adapter->shouldReceive('update')->once()->with('file.json', $response, Mockery::any());
+        $cache = new Adapter($adapter, 'file.json', null);
+        $cache->save();
+    }
+
+    public function testSaveNew()
+    {
+        $response = json_encode([[], [], null]);
+        $adapter = Mockery::mock('League\Flysystem\AdapterInterface');
+        $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(false);
+        $adapter->shouldReceive('write')->once()->with('file.json', $response, Mockery::any());
+        $cache = new Adapter($adapter, 'file.json', null);
+        $cache->save();
+    }
+
+    public function testStoreContentsRecursive()
+    {
+        $adapter = Mockery::mock('League\Flysystem\AdapterInterface');
+        $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(false);
+        $adapter->shouldReceive('write')->once()->with('file.json', Mockery::any(), Mockery::any());
+
+        $cache = new Adapter($adapter, 'file.json', null);
+
+        $contents = [
+            ['path' => 'foo/bar', 'dirname' => 'foo'],
+            ['path' => 'afoo/bang', 'dirname' => 'afoo'],
+        ];
+
+        $cache->storeContents('foo', $contents, true);
+
+        $this->assertTrue($cache->isComplete('foo', true));
+        $this->assertFalse($cache->isComplete('afoo', true));
+    }
+
+    public function testDeleteDir()
+    {
+        $cache_data = [
+            'foo' => ['path' => 'foo', 'type' => 'dir', 'dirname' => ''],
+            'foo/bar' => ['path' => 'foo/bar', 'type' => 'file', 'dirname' => 'foo'],
+            'foobaz' => ['path' => 'foobaz', 'type' => 'file', 'dirname' => ''],
+        ];
+
+        $response = [
+            'contents' => json_encode([$cache_data, [], null]),
+            'path' => 'file.json',
+        ];
+
+        $adapter = Mockery::mock('League\Flysystem\AdapterInterface');
+        $adapter->shouldReceive('has')->zeroOrMoreTimes()->with('file.json')->andReturn(true);
+        $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response);
+        $adapter->shouldReceive('update')->once()->with('file.json', Mockery::any(), Mockery::any())->andReturn(true);
+
+        $cache = new Adapter($adapter, 'file.json', null);
+        $cache->load();
+
+        $cache->deleteDir('foo', true);
+
+        $this->assertSame(1, count($cache->listContents('', true)));
+    }
+}

部分文件因文件數量過多而無法顯示